fix: tighten web trace reading

This commit is contained in:
lyon
2026-06-15 14:44:47 +08:00
parent 62216341e1
commit 53f8d94471
6 changed files with 195 additions and 92 deletions
+8
View File
@@ -1616,7 +1616,10 @@ test("hwlab-cli client agent trace can render with the Web trace row path", asyn
assert.equal(result.payload.body.sourceEventCount, 3);
assert.ok(result.payload.body.renderedRowCount >= 1);
assert.ok(result.payload.body.rows.some((row: any) => //u.test(row.header)));
assert.equal(JSON.stringify(result.payload.body.rows).includes("request accepted"), false);
assert.equal(JSON.stringify(result.payload.body.rows).includes("request:accepted"), false);
assert.equal(JSON.stringify(result.payload.body.rows).includes("session:reused"), false);
assert.equal(result.payload.body.rows[0].body, "当前有两个 HWPOD 可用。");
assert.equal(result.payload.traceStatus, "completed");
assert.equal(result.payload.rendered.traceStatus, "completed");
assert.equal(result.payload.rendered.render, "web");
@@ -1710,6 +1713,8 @@ test("hwlab-cli Web trace render does not treat AgentRun result-ready as final a
assert.equal(text.includes("助手最终消息"), false);
assert.equal(text.includes("助手消息"), false);
assert.match(text, //u);
assert.match(text, / 00:00:02/u);
assert.equal(text.includes("总耗时 00:00:00"), false);
assert.equal(text.includes("AgentRun result is ready for HWLAB short-connection polling"), false);
});
@@ -1785,6 +1790,9 @@ test("hwlab-cli Web trace render keeps AgentRun middle assistant message", async
assert.match(text, / HWPOD /u);
assert.match(text, / 2 HWPOD /u);
assert.match(text, //u);
assert.match(text, / 00:00:03/u);
assert.equal(text.includes("agentrun:request:accepted"), false);
assert.equal(text.includes("总耗时 00:00:00"), false);
});
test("hwlab-cli Web trace render keeps the full final-response body without truncating long agent summaries", async () => {
+33 -54
View File
@@ -11,12 +11,11 @@ export interface TraceEventRow {
type TraceEvent = Record<string, unknown>;
export function traceDisplayRows(trace: Record<string, unknown> = {}, events: TraceEvent[] = []): TraceEventRow[] {
const effectiveTrace = traceWithInferredStart(trace, events);
const rows: TraceEventRow[] = [];
const renderedSourceEvents = new Set<string>();
const renderedToolIdentities = new Set<string>();
const assistantRows: AssistantRowState[] = [];
let requestRendered = false;
let setupRendered = false;
let lastAssistantRowIndex = -1;
let completionEvent: TraceEvent | null = null;
for (let index = 0; index < events.length; index += 1) {
@@ -34,30 +33,22 @@ export function traceDisplayRows(trace: Record<string, unknown> = {}, events: Tr
if (renderedToolIdentities.has(identity)) continue;
renderedToolIdentities.add(identity);
}
rows.push(traceToolCallRow(trace, event));
rows.push(traceToolCallRow(effectiveTrace, event));
continue;
}
if (isRequestTraceEvent(event)) {
if (!requestRendered) {
rows.push(traceRequestSummaryRow(event));
requestRendered = true;
}
continue;
}
if (isSetupTraceEvent(event)) {
if (!setupRendered) {
rows.push(traceSetupSummaryRow(event));
setupRendered = true;
}
continue;
}
if (isTerminalAssistantTraceEvent(event)) {
const assistantRowIndex = upsertAssistantMessageRow(rows, assistantRows, trace, event, { terminal: true });
const assistantRowIndex = upsertAssistantMessageRow(rows, assistantRows, effectiveTrace, event, { terminal: true });
if (assistantRowIndex >= 0) lastAssistantRowIndex = assistantRowIndex;
continue;
}
if (isAssistantTraceEvent(event)) {
const assistantRowIndex = upsertAssistantMessageRow(rows, assistantRows, trace, event, { terminal: false });
const assistantRowIndex = upsertAssistantMessageRow(rows, assistantRows, effectiveTrace, event, { terminal: false });
if (assistantRowIndex >= 0) lastAssistantRowIndex = assistantRowIndex;
continue;
}
@@ -65,21 +56,21 @@ export function traceDisplayRows(trace: Record<string, unknown> = {}, events: Tr
completionEvent = event;
continue;
}
rows.push(traceDisplayRow(trace, event));
rows.push(traceDisplayRow(effectiveTrace, event));
}
if (completionEvent) {
const finalResponseText = traceFinalResponseText(trace);
const finalResponseText = traceFinalResponseText(effectiveTrace);
if (finalResponseText) {
lastAssistantRowIndex = upsertAuthoritativeFinalResponseRow(rows, assistantRows, completionEvent, finalResponseText);
}
const lastAssistantRow = lastAssistantRowIndex >= 0 ? rows[lastAssistantRowIndex] : undefined;
if (lastAssistantRow) rows[lastAssistantRowIndex] = markAssistantRowTerminal(trace, lastAssistantRow, completionEvent);
else rows.push(traceCompletionSummaryRow(trace, completionEvent));
if (lastAssistantRow) rows[lastAssistantRowIndex] = markAssistantRowTerminal(effectiveTrace, lastAssistantRow, completionEvent);
else rows.push(traceCompletionSummaryRow(effectiveTrace, completionEvent));
}
if (rows.length > 0) return rows;
if (events.length === 0) return [];
if (traceNoiseEventCount(events) === events.length) return [traceNoiseSummaryRow(trace, events)];
return events.filter((event) => !isNoisyTraceEvent(event)).map((event) => traceDisplayRow(trace, event));
if (traceNoiseEventCount(events) === events.length) return [traceNoiseSummaryRow(effectiveTrace, events)];
return events.filter((event) => !isNoisyTraceEvent(event) && !isRequestTraceEvent(event) && !isSetupTraceEvent(event)).map((event) => traceDisplayRow(effectiveTrace, event));
}
export function renderTraceRowsMarkdown(rows: TraceEventRow[] = []): string {
@@ -140,7 +131,7 @@ function traceToolCallRow(trace: Record<string, unknown>, event: TraceEvent): Tr
rowId: `tool:${event.itemId ?? event.seq ?? `${event.label ?? event.type ?? "tool"}:${event.createdAt ?? "unknown"}`}`,
seq: numberOrNull(event.seq),
tone: traceEventTone(event),
header: `${traceClock(event.createdAt)} total=${formatTraceDuration(traceRelativeMs(trace, event))} ${status} ${toolName}`.trim(),
header: `${traceEventClock(event)} total=${formatTraceDuration(traceRelativeMs(trace, event))} ${status} ${toolName}`.trim(),
body,
bodyFormat: "text"
};
@@ -161,33 +152,6 @@ export function traceNoiseEventCount(events: TraceEvent[] = []): number {
return Array.isArray(events) ? events.filter((event) => isNoisyTraceEvent(event)).length : 0;
}
function traceRequestSummaryRow(event: TraceEvent): TraceEventRow {
const prompt = cleanTraceText(event.promptSummary ?? event.prompt ?? "");
return {
rowId: `trace-request:${event.seq ?? "accepted"}`,
seq: numberOrNull(event.seq),
tone: traceEventTone(event),
header: `${traceClock(event.createdAt)} 请求接受${prompt ? `,提示词:"${compactTraceOneLine(prompt, 120)}"` : ""}`,
body: null
};
}
function traceSetupSummaryRow(event: TraceEvent): TraceEventRow {
const label = String(event.label ?? "");
const parts = [
/session:reused|thread\/resume/iu.test(label) ? "" : "",
/prompt:sent|sent prompt/iu.test(label) ? "" : null,
/turn[:/]start|turn:started|turn\/start/iu.test(label) ? "" : null
].filter(Boolean).join("");
return {
rowId: `trace-setup:${event.seq ?? "session-turn"}`,
seq: numberOrNull(event.seq),
tone: "source",
header: `${traceClock(event.createdAt)} ${parts || "会话准备完成"}`,
body: null
};
}
function traceAssistantMessageRow(trace: Record<string, unknown>, event: TraceEvent, { terminal }: { terminal: boolean }): TraceEventRow {
const text = cleanTraceText(event.message ?? event.outputSummary ?? assistantStreamText(trace, event) ?? "");
const index = Number.isInteger(event.messageIndex) ? Number(event.messageIndex) : null;
@@ -196,7 +160,7 @@ function traceAssistantMessageRow(trace: Record<string, unknown>, event: TraceEv
rowId: `event:${event.seq ?? `${event.label ?? event.type ?? "assistant"}:${event.createdAt ?? "unknown"}`}`,
seq: numberOrNull(event.seq),
tone: "ok",
header: `${traceClock(event.createdAt)} ${terminal ? "助手最终消息" : "助手消息"}${index ? ` ${index}${count ? `/${count}` : ""}` : ""}`,
header: `${traceEventClock(event)} ${terminal ? "助手最终消息" : "助手消息"}${index ? ` ${index}${count ? `/${count}` : ""}` : ""}`,
terminal: terminal ? true : undefined,
body: text || null,
bodyFormat: "markdown"
@@ -274,7 +238,7 @@ function upsertAuthoritativeFinalResponseRow(rows: TraceEventRow[], assistantRow
body: finalText,
terminal: true,
tone: "ok",
header: `${traceClock(completionEvent.createdAt)} 助手最终消息`
header: `${traceEventClock(completionEvent)} 助手最终消息`
};
matchedState.comparableText = comparableText;
matchedState.derivedSnapshotSuffix = false;
@@ -285,7 +249,7 @@ function upsertAuthoritativeFinalResponseRow(rows: TraceEventRow[], assistantRow
rowId: `trace-final-response:${completionEvent.seq ?? "completed"}`,
seq: numberOrNull(completionEvent.seq),
tone: "ok",
header: `${traceClock(completionEvent.createdAt)} 助手最终消息`,
header: `${traceEventClock(completionEvent)} 助手最终消息`,
terminal: true,
body: finalText,
bodyFormat: "markdown"
@@ -348,7 +312,7 @@ function traceCompletionSummaryRow(trace: Record<string, unknown>, event: TraceE
rowId: `trace-completion:${event.seq ?? "turn"}`,
seq: numberOrNull(event.seq),
tone: traceEventTone(event),
header: `${traceClock(event.createdAt)} 轮次完成(总耗时 ${formatTraceDuration(traceRelativeMs(trace, event))}`,
header: `${traceEventClock(event)} 轮次完成(总耗时 ${formatTraceDuration(traceRelativeMs(trace, event))}`,
body: null
};
}
@@ -361,7 +325,7 @@ function traceNoiseSummaryRow(trace: Record<string, unknown>, events: TraceEvent
rowId: "trace-noise-summary",
seq: numberOrNull(lastEvent?.seq),
tone: lastEvent ? traceEventTone(lastEvent) : "source",
header: `${traceClock(lastEvent?.createdAt)} Trace ${status},等待可读事件`,
header: `${traceEventClock(lastEvent)} Trace ${status},等待可读事件`,
body: `已隐藏 ${events.length} 条 AgentRun backend 状态事件。最新原始事件:${lastLabel}`,
bodyFormat: "text"
};
@@ -373,7 +337,7 @@ function traceDisplayRow(trace: Record<string, unknown>, event: TraceEvent): Tra
rowId: `event:${event.seq ?? `${event.label ?? event.type ?? "event"}:${event.createdAt ?? "unknown"}`}`,
seq: numberOrNull(event.seq),
tone: traceEventTone(event),
header: `${traceClock(event.createdAt)} total=${formatTraceDuration(traceRelativeMs(trace, event))} ${traceStatusToken(event)} ${label}`.trim(),
header: `${traceEventClock(event)} total=${formatTraceDuration(traceRelativeMs(trace, event))} ${traceStatusToken(event)} ${label}`.trim(),
body: traceDisplayBody(event)
};
}
@@ -561,10 +525,25 @@ function traceClock(value: unknown): string {
return Number.isNaN(date.getTime()) ? "--:--:--" : date.toISOString().slice(11, 19);
}
function traceEventClock(event: TraceEvent | null | undefined): string {
return traceClock(traceEventTimestamp(event));
}
function traceWithInferredStart(trace: Record<string, unknown>, events: TraceEvent[]): Record<string, unknown> {
if (nonEmptyString(trace.startedAt ?? trace.createdAt)) return trace;
const firstTimestamp = events.map((event) => traceEventTimestamp(event)).find(Boolean);
return firstTimestamp ? { ...trace, startedAt: firstTimestamp } : trace;
}
function traceEventTimestamp(event: TraceEvent | null | undefined): string | null {
if (!event) return null;
return nonEmptyString(event.createdAt ?? event.ts ?? event.timestamp ?? event.observedAt);
}
function traceRelativeMs(trace: Record<string, unknown>, event: TraceEvent): number {
if (typeof event.elapsedMs === "number") return event.elapsedMs;
const start = Date.parse(String(trace.startedAt ?? trace.createdAt ?? ""));
const current = Date.parse(String(event.createdAt ?? ""));
const current = Date.parse(String(traceEventTimestamp(event) ?? ""));
return Number.isNaN(start) || Number.isNaN(current) ? 0 : Math.max(0, current - start);
}