Merge pull request #721 from pikasTech/fix/issue-720-trace-message-dedupe
修复 WEB trace assistant message 重复渲染
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);
|
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() {
|
function traceEvents() {
|
||||||
return [
|
return [
|
||||||
event(1, "request:accepted", { promptSummary: "请检查 trace" }),
|
event(1, "request:accepted", { promptSummary: "请检查 trace" }),
|
||||||
@@ -341,3 +402,8 @@ function event(seq, label, fields = {}) {
|
|||||||
...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 requestEvent = tracePrimaryRequestEvent(events);
|
||||||
const hasTerminalAssistant = events.some((event) => isTerminalAssistantTraceEvent(event));
|
const hasTerminalAssistant = events.some((event) => isTerminalAssistantTraceEvent(event));
|
||||||
const terminalAssistantOnly = hasTerminalAssistant && !events.some((event) => isCommandExecutionTraceEvent(event));
|
const terminalAssistantOnly = hasTerminalAssistant && !events.some((event) => isCommandExecutionTraceEvent(event));
|
||||||
|
const latestAssistantByIdentity = traceLatestAssistantEventsByIdentity(events);
|
||||||
|
const renderedAssistantTextKeys = new Set();
|
||||||
const renderedCommands = new Set();
|
const renderedCommands = new Set();
|
||||||
const renderedToolCalls = new Set();
|
const renderedToolCalls = new Set();
|
||||||
let requestRendered = false;
|
let requestRendered = false;
|
||||||
@@ -323,9 +325,17 @@ export function traceDisplayRows(trace, events) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (isAssistantMessageTraceEvent(event)) {
|
if (isAssistantMessageTraceEvent(event)) {
|
||||||
const row = traceAssistantSummaryRow(trace, event, assistantIndex);
|
const assistantIdentity = traceAssistantMessageIdentity(event);
|
||||||
assistantIndex += 1;
|
if (assistantIdentity && latestAssistantByIdentity.get(assistantIdentity) !== event) continue;
|
||||||
if (row) rows.push(row);
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
if (isRequestTraceEvent(event)) {
|
if (isRequestTraceEvent(event)) {
|
||||||
@@ -416,6 +426,38 @@ function traceGenericToolCallGroups(events) {
|
|||||||
return groups;
|
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) {
|
function traceCommandGroupKey(event, fallback = null) {
|
||||||
return traceNonEmptyString(event?.itemId ?? event?.callId ?? event?.commandId ?? event?.id) || fallback || `cmd:${event?.seq ?? event?.createdAt ?? Math.random()}`;
|
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 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;
|
if (!text && !terminal && traceHasLaterTerminalAssistant(trace, event)) return null;
|
||||||
const elapsed = formatTraceDuration(traceRelativeMs(trace, event));
|
const elapsed = formatTraceDuration(traceRelativeMs(trace, event));
|
||||||
return {
|
return {
|
||||||
|
|||||||
Reference in New Issue
Block a user