fix: stream live trace pages while polling turns

This commit is contained in:
lyon
2026-06-17 00:45:06 +08:00
parent 084445b4c9
commit e31851bfb5
3 changed files with 157 additions and 22 deletions
+60 -1
View File
@@ -56,6 +56,8 @@ export function parseProbeArgs(argv) {
conversationId: process.env.HWLAB_WEB_PROBE_CONVERSATION_ID ?? "",
waitMessagesMs: 2500,
waitAgentTerminalMs: Number(process.env.HWLAB_WEB_PROBE_WAIT_AGENT_TERMINAL_MS ?? "0"),
traceSampleCount: envNonNegativeInteger("HWLAB_WEB_PROBE_TRACE_SAMPLE_COUNT", 0),
traceSampleIntervalMs: envNonNegativeInteger("HWLAB_WEB_PROBE_TRACE_SAMPLE_INTERVAL_MS", 0),
reportPath: tempReportPath("web-live-dom-probe.json"),
screenshotPath: tempReportPath("web-live-dom-probe.png"),
jobId: ""
@@ -90,6 +92,8 @@ export function parseProbeArgs(argv) {
else if (arg === "--wait-after-submit-ms" || arg.startsWith("--wait-after-submit-ms=")) args.waitAfterSubmitMs = positiveInteger(readValue("--wait-after-submit-ms"), "--wait-after-submit-ms");
else if (arg === "--wait-messages-ms" || arg.startsWith("--wait-messages-ms=")) args.waitMessagesMs = positiveInteger(readValue("--wait-messages-ms"), "--wait-messages-ms");
else if (arg === "--wait-agent-terminal-ms" || arg.startsWith("--wait-agent-terminal-ms=")) args.waitAgentTerminalMs = positiveInteger(readValue("--wait-agent-terminal-ms"), "--wait-agent-terminal-ms");
else if (arg === "--trace-sample-count" || arg.startsWith("--trace-sample-count=")) args.traceSampleCount = nonNegativeInteger(readValue("--trace-sample-count"), "--trace-sample-count");
else if (arg === "--trace-sample-interval-ms" || arg.startsWith("--trace-sample-interval-ms=")) args.traceSampleIntervalMs = nonNegativeInteger(readValue("--trace-sample-interval-ms"), "--trace-sample-interval-ms");
else if (arg === "--timeout-ms" || arg.startsWith("--timeout-ms=")) args.timeoutMs = positiveInteger(readValue("--timeout-ms"), "--timeout-ms");
else if (arg === "--fresh-session") args.freshSession = true;
else if (arg === "--no-cancel-running") args.cancelRunning = false;
@@ -219,6 +223,7 @@ export async function runProbe(args) {
let browser;
let page;
let result;
let traceSamples = [];
if (args.credentialError) {
result = {
ok: false,
@@ -258,7 +263,9 @@ export async function runProbe(args) {
if (args.conversationId) await selectConversationInBrowserSession(page, args, actions);
if (args.freshSession) await createFreshSession(page, args, actions);
if (args.message) await submitPrompt(page, args, actions);
if (args.message && args.traceSampleCount > 0) traceSamples = await sampleTraceTimeline(page, args, actions);
if (args.waitAgentTerminalMs) await waitForAgentTerminal(page, args, actions);
if (args.message && args.cancelRunning) await maybeClick(page, ".message-action-cancel", actions, "cancel-running-message");
await waitForMessageCards(page, args, actions);
await page.screenshot({ path: screenshotPath, fullPage: true });
const dom = await collectDomEvidence(page, args);
@@ -276,6 +283,7 @@ export async function runProbe(args) {
actions,
credentials: args.credentials,
bootstrapRoutes: summarizeBootstrapRoutes(bootstrapRoutes),
traceSamples,
dom,
promptValidation,
artifacts: { reportPath, screenshotPath },
@@ -286,6 +294,7 @@ export async function runProbe(args) {
conversationSelected: Boolean(args.conversationId),
freshSessionRequested: args.freshSession,
promptSubmitted: Boolean(args.message),
traceSamplingRequested: args.traceSampleCount > 0,
cancelRunningRequested: args.cancelRunning
}
};
@@ -346,6 +355,7 @@ async function collectFailureDomEvidence(page) {
function validatePromptFinalResponse(dom, args) {
if (!args.message) return { ok: true, applicable: false, reason: "no-prompt-submitted" };
if (args.traceSampleCount > 0 && !args.waitAgentTerminalMs) return { ok: true, applicable: false, reason: "trace-sampling-without-terminal-wait" };
const messages = Array.isArray(dom.messages) ? dom.messages : [];
const latestAgent = [...messages].reverse().find((message) => message.role === "agent");
const finalResponse = latestAgent?.finalResponse ?? null;
@@ -402,6 +412,7 @@ async function startProbe(args) {
"--wait-messages-ms", String(args.waitMessagesMs)
];
if (args.waitAgentTerminalMs > 0) childArgs.push("--wait-agent-terminal-ms", String(args.waitAgentTerminalMs));
if (args.traceSampleCount > 0) childArgs.push("--trace-sample-count", String(args.traceSampleCount), "--trace-sample-interval-ms", String(args.traceSampleIntervalMs));
if (args.freshSession) childArgs.push("--fresh-session");
if (!args.cancelRunning) childArgs.push("--no-cancel-running");
if (args.conversationId) childArgs.push("--conversation-id", args.conversationId);
@@ -425,6 +436,7 @@ async function startProbe(args) {
url: args.url,
projectId: args.projectId,
conversationId: args.conversationId || null,
traceSampling: args.traceSampleCount > 0 ? { count: args.traceSampleCount, intervalMs: args.traceSampleIntervalMs } : null,
reportPath,
screenshotPath,
stdoutPath,
@@ -659,7 +671,42 @@ async function submitPrompt(page, args, actions) {
const afterClick = await collectCommandState(page);
actions.push({ action: "submit-prompt", chars: args.message.length, readyBefore, afterFill, afterClick });
await page.waitForTimeout(args.waitAfterSubmitMs);
if (args.cancelRunning) await maybeClick(page, ".message-action-cancel", actions, "cancel-running-message");
}
async function sampleTraceTimeline(page, args, actions) {
const samples = [];
for (let index = 0; index < args.traceSampleCount; index += 1) {
if (index > 0 && args.traceSampleIntervalMs > 0) await page.waitForTimeout(args.traceSampleIntervalMs);
samples.push(await collectTraceTimelineSample(page, index));
}
actions.push({ action: "trace-interval-samples", count: samples.length, intervalMs: args.traceSampleIntervalMs, samples });
return samples;
}
async function collectTraceTimelineSample(page, index) {
return page.evaluate((sampleIndex) => {
const cards = [...document.querySelectorAll(".message-card")];
const latestAgent = [...cards].reverse().find((card) => card.getAttribute("data-message-role") === "agent" || card.classList.contains("message-agent"));
const trace = latestAgent?.querySelector(".trace-timeline") ?? null;
const details = trace?.querySelector("details") ?? null;
const rows = latestAgent ? [...latestAgent.querySelectorAll(".trace-render-row")] : [];
const latestRow = rows.at(-1) ?? null;
const empty = latestAgent?.querySelector(".trace-empty") ?? null;
const finalResponse = latestAgent?.querySelector(".message-final-response") ?? null;
return {
index: sampleIndex,
sampledAt: new Date().toISOString(),
messageCount: cards.length,
agentStatus: latestAgent?.getAttribute("data-message-status") ?? null,
tracePresent: Boolean(trace),
traceStatus: trace?.getAttribute("data-status") ?? null,
traceOpen: details instanceof HTMLDetailsElement ? details.open : null,
rowCount: rows.length,
emptyLabel: empty?.textContent?.replace(/\s+/gu, " ").trim() ?? null,
latestRowPreview: latestRow?.textContent?.replace(/\s+/gu, " ").trim().slice(0, 240) ?? null,
finalTextChars: finalResponse?.textContent?.trim().length ?? 0
};
}, index);
}
async function waitForCommandReady(page, args, actions) {
@@ -930,6 +977,17 @@ function positiveInteger(value, label) {
return parsed;
}
function nonNegativeInteger(value, label) {
const parsed = Number(value);
if (!Number.isInteger(parsed) || parsed < 0) throw new Error(`${label} must be a non-negative integer`);
return parsed;
}
function envNonNegativeInteger(name, fallback) {
const parsed = Number(process.env[name] ?? fallback);
return Number.isInteger(parsed) && parsed >= 0 ? parsed : fallback;
}
function processAlive(pid) {
try {
process.kill(pid, 0);
@@ -953,6 +1011,7 @@ function helpPayload() {
usage: [
"node scripts/web-live-dom-probe.mjs run --url http://74.48.78.17:19666/ --fresh-session --report /tmp/hwlab-dev-gate/web-live-dom-probe.json",
"node scripts/web-live-dom-probe.mjs start --url http://74.48.78.17:19666/ --fresh-session --message '...' --no-cancel-running --wait-agent-terminal-ms 180000",
"node scripts/web-live-dom-probe.mjs run --url https://hwlab.pikapython.com/ --fresh-session --message '...' --no-cancel-running --trace-sample-count 6 --trace-sample-interval-ms 5000",
"node scripts/web-live-dom-probe.mjs run --url http://74.48.78.17:19666/#/workspace --conversation-id cnv_...",
"node scripts/web-live-dom-probe.mjs start --url http://74.48.78.17:19666/ --fresh-session",
"node scripts/web-live-dom-probe.mjs status <jobId>"