fix: stream live trace pages while polling turns
This commit is contained in:
@@ -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>"
|
||||
|
||||
Reference in New Issue
Block a user