fix(v02): dedupe replayed trace tool rows (#860)
Co-authored-by: Codex Agent <codex@hwlab.local>
This commit is contained in:
@@ -12,15 +12,27 @@ type TraceEvent = Record<string, unknown>;
|
||||
|
||||
export function traceDisplayRows(trace: Record<string, unknown> = {}, events: TraceEvent[] = []): TraceEventRow[] {
|
||||
const rows: TraceEventRow[] = [];
|
||||
const renderedSourceEvents = new Set<string>();
|
||||
const renderedToolIdentities = new Set<string>();
|
||||
let requestRendered = false;
|
||||
let setupRendered = false;
|
||||
let lastAssistantRowIndex = -1;
|
||||
let completionEvent: TraceEvent | null = null;
|
||||
for (let index = 0; index < events.length; index += 1) {
|
||||
const event = events[index];
|
||||
const sourceEventKey = traceSourceEventKey(event);
|
||||
if (sourceEventKey) {
|
||||
if (renderedSourceEvents.has(sourceEventKey)) continue;
|
||||
renderedSourceEvents.add(sourceEventKey);
|
||||
}
|
||||
if (!event || isNoisyTraceEvent(event)) continue;
|
||||
if (isToolTraceEvent(event)) {
|
||||
if (isSupersededToolStart(event, events, index)) continue;
|
||||
const identity = toolIdentity(event);
|
||||
if (identity) {
|
||||
if (renderedToolIdentities.has(identity)) continue;
|
||||
renderedToolIdentities.add(identity);
|
||||
}
|
||||
rows.push(traceToolCallRow(trace, event));
|
||||
continue;
|
||||
}
|
||||
@@ -222,6 +234,14 @@ function toolIdentity(event: TraceEvent): string | null {
|
||||
return nonEmptyString(event.itemId ?? event.id ?? event.command);
|
||||
}
|
||||
|
||||
function traceSourceEventKey(event: TraceEvent | null | undefined): string | null {
|
||||
if (!event || typeof event !== "object") return null;
|
||||
const source = nonEmptyString(event.source);
|
||||
const sourceSeq = nonEmptyString(event.sourceSeq);
|
||||
if (!source || !sourceSeq) return null;
|
||||
return `${source}:${sourceSeq}`;
|
||||
}
|
||||
|
||||
function isSupersededToolStart(event: TraceEvent, events: TraceEvent[], index: number): boolean {
|
||||
if (!isToolStartTraceEvent(event)) return false;
|
||||
const identity = toolIdentity(event);
|
||||
@@ -233,6 +253,7 @@ function isNoisyTraceEvent(event: TraceEvent): boolean {
|
||||
const label = String(event.label ?? "");
|
||||
if (isRequestTraceEvent(event) || isSetupTraceEvent(event) || isCompletionTraceEvent(event) || isTerminalAssistantTraceEvent(event) || isAssistantTraceEvent(event)) return false;
|
||||
if (/token_count|outputDelta:chunk/iu.test(label)) return true;
|
||||
if (/^agentrun:output:(stdout|stderr)$/u.test(label)) return true;
|
||||
if (/^agentrun:backend:(run-created|command-created|runner-job-created|thread\/status\/changed|thread\/tokenUsage\/updated|account\/rateLimits\/updated|remoteControl\/status\/changed|configWarning|codex-app-server-closed|codex-app-server-notifications-suppressed|session-updated|command-terminal|backend-turn-finished|item\/agentMessage:(started|completed)|thread\/goal\/cleared)$/u.test(label)) return true;
|
||||
return event.type === "event" && !event.outputSummary && !event.message && !event.errorCode;
|
||||
}
|
||||
|
||||
@@ -65,3 +65,32 @@ test("web trace rows collapse tool start/completed and clean shell command escap
|
||||
assert.match(body, /exitCode=0/u);
|
||||
assert.doesNotMatch(text, /agentrun:tool:item\/started/u);
|
||||
});
|
||||
|
||||
test("web trace rows dedupe AgentRun replayed source events and keep one tool row", () => {
|
||||
const events: Record<string, unknown>[] = [
|
||||
{ seq: 1, label: "agentrun:request:accepted", status: "accepted", createdAt: "2026-06-04T13:40:28.612Z" },
|
||||
{ seq: 9, label: "agentrun:backend:run-claimed", type: "backend", status: "running", source: "agentrun", sourceSeq: 4, message: "run-claimed", createdAt: "2026-06-04T13:40:34.601Z" },
|
||||
{ seq: 22, label: "agentrun:assistant:message", type: "assistant", status: "running", source: "agentrun", sourceSeq: 17, message: "我先确认 `hwpod` 可用。", createdAt: "2026-06-04T13:40:43.286Z" },
|
||||
{ seq: 23, label: "item/commandExecution:started", type: "tool_call", toolName: "commandExecution", status: "started", source: "agentrun", sourceSeq: 18, itemId: "call_function_gsuuqbm1yp0s_1", command: "/bin/sh -lc 'command -v hwpod && hwpod profile list'", createdAt: "2026-06-04T13:40:43.458Z" },
|
||||
{ seq: 24, label: "agentrun:output:stdout", type: "output", status: "running", source: "agentrun", sourceSeq: 19, message: "raw stdout should stay inside the completed tool row", createdAt: "2026-06-04T13:40:44.852Z" },
|
||||
{ seq: 25, label: "item/commandExecution:completed", type: "tool_call", toolName: "commandExecution", status: "completed", source: "agentrun", sourceSeq: 20, itemId: "call_function_gsuuqbm1yp0s_1", command: "/bin/sh -lc 'command -v hwpod && hwpod profile list'", stdoutSummary: '{"ok":true,"action":"profile.list"}', exitCode: 0, createdAt: "2026-06-04T13:40:44.972Z" },
|
||||
{ seq: 29, label: "agentrun:backend:run-claimed", type: "backend", status: "running", source: "agentrun", sourceSeq: 4, message: "run-claimed", createdAt: "2026-06-04T13:40:34.601Z" },
|
||||
{ seq: 42, label: "agentrun:assistant:message", type: "assistant", status: "running", source: "agentrun", sourceSeq: 17, message: "我先确认 `hwpod` 可用。", createdAt: "2026-06-04T13:40:43.286Z" },
|
||||
{ seq: 43, label: "item/commandExecution:started", type: "tool_call", toolName: "commandExecution", status: "started", source: "agentrun", sourceSeq: 18, itemId: "call_function_gsuuqbm1yp0s_1", command: "/bin/sh -lc 'command -v hwpod && hwpod profile list'", createdAt: "2026-06-04T13:40:43.458Z" },
|
||||
{ seq: 44, label: "agentrun:output:stdout", type: "output", status: "running", source: "agentrun", sourceSeq: 19, message: "raw stdout should stay inside the completed tool row", createdAt: "2026-06-04T13:40:44.852Z" },
|
||||
{ seq: 45, label: "item/commandExecution:completed", type: "tool_call", toolName: "commandExecution", status: "completed", source: "agentrun", sourceSeq: 20, itemId: "call_function_gsuuqbm1yp0s_1", command: "/bin/sh -lc 'command -v hwpod && hwpod profile list'", stdoutSummary: '{"ok":true,"action":"profile.list"}', exitCode: 0, createdAt: "2026-06-04T13:40:44.972Z" }
|
||||
];
|
||||
|
||||
const rows = traceDisplayRows({ startedAt: "2026-06-04T13:40:28.612Z" }, events);
|
||||
const text = JSON.stringify(rows);
|
||||
const toolRows = rows.filter((row) => row.rowId === "tool:call_function_gsuuqbm1yp0s_1");
|
||||
const assistantRows = rows.filter((row) => row.bodyFormat === "markdown");
|
||||
|
||||
assert.equal(toolRows.length, 1);
|
||||
assert.equal(assistantRows.length, 1);
|
||||
assert.equal(rows.filter((row) => /run-claimed/u.test(row.body ?? "")).length, 1);
|
||||
assert.match(toolRows[0]?.body ?? "", /command -v hwpod && hwpod profile list/u);
|
||||
assert.match(toolRows[0]?.body ?? "", /profile\.list/u);
|
||||
assert.doesNotMatch(toolRows[0]?.body ?? "", /\/bin\/sh -lc/u);
|
||||
assert.doesNotMatch(text, /agentrun:output:stdout|raw stdout should stay inside/u);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user