fix: collapse duplicate trace assistant rows

This commit is contained in:
Codex
2026-06-02 22:46:05 +08:00
parent c1350bb7ac
commit 570c9963ec
2 changed files with 113 additions and 5 deletions
+66
View File
@@ -290,6 +290,67 @@ test("trace display rows render non-noise AgentRun tool item details", () => {
assert.match(text, / 2 device-pod/u);
});
test("trace display rows collapse repeated AgentRun assistant snapshots and notification replay", () => {
const events = [
event(1, "agentrun:request:accepted", { message: "accepted" }),
event(2, "agentrun:assistant:message", {
type: "assistant",
status: "running",
itemId: "msg_progress_1",
message: "我尝试拉取这个 issue 的内容。"
}),
commandEvent(3, "item/commandExecution:completed", {
itemId: "cmd_issue_read",
command: "gh api repos/pikasTech/agentrun/issues/42",
status: "completed",
exitCode: 0,
stdoutSummary: "success=true"
}),
event(4, "agentrun:assistant:message", {
type: "assistant",
status: "running",
itemId: "msg_summary",
message: "已经拉到完整内容。Issue 元信息。"
}),
event(5, "agentrun:assistant:message", {
type: "assistant",
status: "running",
itemId: "msg_summary",
message: "已经拉到完整内容。Issue 元信息。6 条评论时间线。"
}),
event(6, "agentrun:assistant:message", {
type: "assistant",
status: "running",
itemId: "msg_summary",
message: "已经拉到完整内容。Issue 元信息。6 条评论时间线。验收完成。"
}),
event(7, "agentrun:backend:turn/completed", { message: "turn/completed" }),
event(8, "agentrun:assistant:message", {
type: "assistant",
status: "running",
itemId: "msg_progress_1_replay",
message: "我尝试拉取这个 issue 的内容。"
}),
event(9, "agentrun:assistant:message", {
type: "assistant",
status: "running",
itemId: "msg_summary",
message: "已经拉到完整内容。Issue 元信息。6 条评论时间线。验收完成。"
}),
event(10, "agentrun:terminal:completed", { type: "result", terminal: true, status: "completed" })
];
const rows = traceDisplayRows({ traceId: "trc_issue_720_duplicate", events }, events);
const assistantRows = rows.filter((row) => //u.test(row.header));
const text = rows.map((row) => `${row.header}\n${row.body ?? ""}`).join("\n---\n");
assert.equal(assistantRows.length, 2);
assert.deepEqual(assistantRows.map((row) => row.seq), [2, 6]);
assert.equal(matchCount(text, / issue /u), 1);
assert.equal(matchCount(text, //u), 1);
assert.match(text, //u);
});
function traceEvents() {
return [
event(1, "request:accepted", { promptSummary: "请检查 trace" }),
@@ -341,3 +402,8 @@ function event(seq, label, fields = {}) {
...fields
};
}
function matchCount(text, pattern) {
const flags = pattern.flags.includes("g") ? pattern.flags : `${pattern.flags}g`;
return [...String(text ?? "").matchAll(new RegExp(pattern.source, flags))].length;
}
+47 -5
View File
@@ -291,6 +291,8 @@ export function traceDisplayRows(trace, events) {
const requestEvent = tracePrimaryRequestEvent(events);
const hasTerminalAssistant = events.some((event) => isTerminalAssistantTraceEvent(event));
const terminalAssistantOnly = hasTerminalAssistant && !events.some((event) => isCommandExecutionTraceEvent(event));
const latestAssistantByIdentity = traceLatestAssistantEventsByIdentity(events);
const renderedAssistantTextKeys = new Set();
const renderedCommands = new Set();
const renderedToolCalls = new Set();
let requestRendered = false;
@@ -323,9 +325,17 @@ export function traceDisplayRows(trace, events) {
continue;
}
if (isAssistantMessageTraceEvent(event)) {
const row = traceAssistantSummaryRow(trace, event, assistantIndex);
assistantIndex += 1;
if (row) rows.push(row);
const assistantIdentity = traceAssistantMessageIdentity(event);
if (assistantIdentity && latestAssistantByIdentity.get(assistantIdentity) !== event) continue;
const assistantText = traceAssistantTextForEvent(trace, event, assistantIndex);
const assistantTextKey = traceAssistantComparableText(assistantText);
if (!isTerminalAssistantTraceEvent(event) && assistantTextKey && renderedAssistantTextKeys.has(assistantTextKey)) continue;
const row = traceAssistantSummaryRow(trace, event, assistantIndex, assistantText);
if (row) {
rows.push(row);
assistantIndex += 1;
if (assistantTextKey) renderedAssistantTextKeys.add(assistantTextKey);
}
continue;
}
if (isRequestTraceEvent(event)) {
@@ -416,6 +426,38 @@ function traceGenericToolCallGroups(events) {
return groups;
}
function traceLatestAssistantEventsByIdentity(events) {
const latest = new Map();
const firstCompletionIndex = events.findIndex((event) => isCompletionTraceEvent(event));
for (const event of events) {
if (!isAssistantMessageTraceEvent(event)) continue;
const identity = traceAssistantMessageIdentity(event);
if (!identity) continue;
const previous = latest.get(identity);
if (!previous || traceAssistantEventShouldReplace(previous, event, firstCompletionIndex, events)) latest.set(identity, event);
}
return latest;
}
function traceAssistantEventShouldReplace(previous, event, firstCompletionIndex, events) {
if (isTerminalAssistantTraceEvent(event)) return true;
if (isTerminalAssistantTraceEvent(previous)) return false;
const previousAfterCompletion = traceEventAfterIndex(previous, firstCompletionIndex, events);
const eventAfterCompletion = traceEventAfterIndex(event, firstCompletionIndex, events);
if (previousAfterCompletion !== eventAfterCompletion) return previousAfterCompletion && !eventAfterCompletion;
return true;
}
function traceEventAfterIndex(event, index, events) {
if (index < 0) return false;
const eventIndex = events.findIndex((item) => item === event || (item?.seq !== undefined && item.seq === event?.seq));
return eventIndex > index;
}
function traceAssistantMessageIdentity(event) {
return traceNonEmptyString(event?.itemId ?? event?.messageId ?? event?.id);
}
function traceCommandGroupKey(event, fallback = null) {
return traceNonEmptyString(event?.itemId ?? event?.callId ?? event?.commandId ?? event?.id) || fallback || `cmd:${event?.seq ?? event?.createdAt ?? Math.random()}`;
}
@@ -689,9 +731,9 @@ function traceCompletionSummaryRow(trace, event) {
};
}
function traceAssistantSummaryRow(trace, event, index = 0) {
function traceAssistantSummaryRow(trace, event, index = 0, textOverride = undefined) {
const terminal = isTerminalAssistantTraceEvent(event);
const text = traceAssistantTextForEvent(trace, event, index);
const text = textOverride === undefined ? traceAssistantTextForEvent(trace, event, index) : textOverride;
if (!text && !terminal && traceHasLaterTerminalAssistant(trace, event)) return null;
const elapsed = formatTraceDuration(traceRelativeMs(trace, event));
return {