fix: collapse duplicate trace assistant rows
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user