fix: tighten web trace reading
This commit is contained in:
@@ -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 () => {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user