fix: 展示真实文件编辑事件
This commit is contained in:
@@ -653,7 +653,8 @@ export function mapAgentRunEvent(event, mapping = {}) {
|
||||
outputSummary: output,
|
||||
message: output || command || textPayload(payload, "tool call"),
|
||||
outputBytes: typeof payload.outputBytes === "number" ? payload.outputBytes : payload.summary?.outputBytes,
|
||||
outputTruncated: payload.outputTruncated === true || payload.summary?.outputTruncated === true
|
||||
outputTruncated: payload.outputTruncated === true || payload.summary?.outputTruncated === true,
|
||||
...(Array.isArray(payload.changes) ? { changes: payload.changes, fileCount: Number.isInteger(payload.fileCount) ? payload.fileCount : null } : {})
|
||||
};
|
||||
}
|
||||
if (event.type === "command_output") {
|
||||
@@ -661,7 +662,17 @@ export function mapAgentRunEvent(event, mapping = {}) {
|
||||
return { ...base, type: "output", eventType: "status", status: "running", label: `agentrun:output:${stream}`, stream, message: textPayload(payload, ""), outputTruncated: payload.outputTruncated === true };
|
||||
}
|
||||
if (event.type === "diff") {
|
||||
return { ...base, type: "diff", eventType: "status", status: "running", label: "agentrun:diff", message: textPayload(payload, "diff") };
|
||||
return {
|
||||
...base,
|
||||
type: "diff",
|
||||
eventType: "diff",
|
||||
status: String(payload.status ?? "updated"),
|
||||
label: "agentrun:diff",
|
||||
message: String(payload.diff ?? textPayload(payload, "diff")),
|
||||
diff: String(payload.diff ?? ""),
|
||||
threadId: payload.threadId ?? null,
|
||||
turnId: payload.turnId ?? null
|
||||
};
|
||||
}
|
||||
if (event.type === "error") {
|
||||
const failureKind = normalizedFailureKind(payload.failureKind ?? payload.errorCode ?? "backend") ?? "backend";
|
||||
|
||||
@@ -103,6 +103,43 @@ test("projects AgentRun assistant_message Kafka event into HWLAB trace event", (
|
||||
assert.equal(projected.valuesPrinted, false);
|
||||
});
|
||||
|
||||
test("projects AgentRun fileChange and diff events without dropping paths or unified diff", () => {
|
||||
const changes = [
|
||||
{ path: "src/example.ts", kind: "update", diff: "@@ -1 +1 @@\n-export const value = 1;\n+export const value = 2;" },
|
||||
{ path: "docs/说明.md", kind: "add", diff: "@@ -0,0 +1 @@\n+# 文件编辑可见" }
|
||||
];
|
||||
const fileChange = projectAgentRunKafkaEventToHwlabEvent({
|
||||
schema: "agentrun.event.v1",
|
||||
eventType: "agentrun.event.committed",
|
||||
eventId: "evt_file_change_completed",
|
||||
traceId: "trc_file_change",
|
||||
hwlabSessionId: "ses_file_change",
|
||||
runId: "run_file_change",
|
||||
commandId: "cmd_file_change",
|
||||
event: { id: "evt_file_change_completed", seq: 21, type: "tool_call", payload: { method: "item/completed", itemId: "item_file_change", type: "fileChange", toolName: "fileChange", status: "completed", changes, fileCount: 2 } }
|
||||
}, { source: "hwlab-test", sourceTopic: "agentrun.event.debug.v1", sourceOffset: "21" });
|
||||
assert.equal(fileChange.event.type, "tool");
|
||||
assert.equal(fileChange.event.toolName, "fileChange");
|
||||
assert.equal(fileChange.event.itemId, "item_file_change");
|
||||
assert.deepEqual(fileChange.event.changes, changes);
|
||||
assert.equal(fileChange.event.fileCount, 2);
|
||||
|
||||
const diff = projectAgentRunKafkaEventToHwlabEvent({
|
||||
schema: "agentrun.event.v1",
|
||||
eventType: "agentrun.event.committed",
|
||||
eventId: "evt_turn_diff_updated",
|
||||
traceId: "trc_file_change",
|
||||
hwlabSessionId: "ses_file_change",
|
||||
runId: "run_file_change",
|
||||
commandId: "cmd_file_change",
|
||||
event: { id: "evt_turn_diff_updated", seq: 22, type: "diff", payload: { phase: "turn/diff/updated", threadId: "thread_file_change", turnId: "turn_file_change", status: "updated", diff: changes.map((change) => change.diff).join("\n") } }
|
||||
}, { source: "hwlab-test", sourceTopic: "agentrun.event.debug.v1", sourceOffset: "22" });
|
||||
assert.equal(diff.event.type, "diff");
|
||||
assert.equal(diff.event.eventType, "diff");
|
||||
assert.equal(diff.event.threadId, "thread_file_change");
|
||||
assert.match(diff.event.diff, /docs\/说明\.md|文件编辑可见/u);
|
||||
});
|
||||
|
||||
test("projects AgentRun assistant_progress as an identity-preserving HWLAB assistant lifecycle event", () => {
|
||||
const text = "p".repeat(500);
|
||||
const projected = projectAgentRunKafkaEventToHwlabEvent({
|
||||
|
||||
@@ -1444,6 +1444,21 @@ function mapAgentRunSourceEventToHwlabEvent({ input, sourceEvent, payload, run,
|
||||
commandTruncated: payload.commandTruncated === true,
|
||||
outputBytes: integerValue(payload.outputBytes ?? payload.summary?.outputBytes),
|
||||
outputTruncated: payload.outputTruncated === true || payload.summary?.outputTruncated === true,
|
||||
...(Array.isArray(payload.changes) ? { changes: payload.changes, fileCount: integerValue(payload.fileCount) } : {}),
|
||||
terminal: false
|
||||
};
|
||||
}
|
||||
if (type === "diff") {
|
||||
return {
|
||||
...base,
|
||||
type: "diff",
|
||||
eventType: "diff",
|
||||
status: firstText(payload.status) || "updated",
|
||||
label: "agentrun:diff",
|
||||
message: firstText(payload.diff) || "",
|
||||
diff: firstText(payload.diff) || "",
|
||||
threadId: firstText(payload.threadId),
|
||||
turnId: firstText(payload.turnId),
|
||||
terminal: false
|
||||
};
|
||||
}
|
||||
|
||||
@@ -517,6 +517,51 @@ test("shared tool summary model keeps status semantic and visible content status
|
||||
assert.match(markdown, /aria-label="工具调用失败:/u);
|
||||
});
|
||||
|
||||
test("fileChange and turn diff render real paths and unified diff as valid Markdown cards", () => {
|
||||
const fileDiff = "@@ -1 +1 @@\n-export const value = 1;\n+export const value = 2;";
|
||||
const aggregateDiff = `${fileDiff}\n@@ -0,0 +1 @@\n+# 文件编辑可见`;
|
||||
const rows = traceDisplayRows({ eventSource: "hwlab-kafka-sse" }, [
|
||||
{
|
||||
sourceSeq: 21,
|
||||
sourceEventId: "evt_file_change_completed",
|
||||
type: "tool_call",
|
||||
eventType: "tool_call",
|
||||
status: "completed",
|
||||
label: "agentrun:tool:fileChange",
|
||||
toolName: "fileChange",
|
||||
toolType: "fileChange",
|
||||
itemId: "item_file_change",
|
||||
changes: [
|
||||
{ path: "src/example.ts", kind: "update", diff: fileDiff },
|
||||
{ path: "docs/说明.md", kind: "add", diff: "@@ -0,0 +1 @@\n+# 文件编辑可见" }
|
||||
],
|
||||
createdAt: "2026-07-12T15:00:01.000Z"
|
||||
},
|
||||
{
|
||||
sourceSeq: 22,
|
||||
sourceEventId: "evt_turn_diff_updated",
|
||||
type: "diff",
|
||||
eventType: "diff",
|
||||
status: "updated",
|
||||
label: "agentrun:diff",
|
||||
diff: aggregateDiff,
|
||||
message: aggregateDiff,
|
||||
createdAt: "2026-07-12T15:00:02.000Z"
|
||||
}
|
||||
]);
|
||||
const markdown = renderTraceRowsMarkdown(rows);
|
||||
assert.equal(rows.length, 2);
|
||||
assert.match(markdown, /fileChange src\/example\.ts, docs\/说明\.md/u);
|
||||
assert.match(markdown, /path: src\/example\.ts/u);
|
||||
assert.match(markdown, /path: docs\/说明\.md/u);
|
||||
assert.match(markdown, /\+export const value = 2;/u);
|
||||
assert.match(markdown, /turn\/diff\/updated/u);
|
||||
assert.match(markdown, /\+# 文件编辑可见/u);
|
||||
assert.equal((markdown.match(/<details>/gu) ?? []).length, 2);
|
||||
assert.equal((markdown.match(/<\/details>/gu) ?? []).length, 2);
|
||||
assert.equal((markdown.match(/```text/gu) ?? []).length, 2);
|
||||
});
|
||||
|
||||
test("outer final response suppression follows formal assistant identity instead of equal text", () => {
|
||||
const finalText = "Hi. What do you want to work on?";
|
||||
const rows = traceDisplayRows({ eventSource: "hwlab-kafka-sse" }, [
|
||||
|
||||
@@ -68,6 +68,10 @@ export function traceDisplayRows(trace: Record<string, unknown> = {}, events: Tr
|
||||
rows.push(traceToolCallRow(event, displayOptions));
|
||||
continue;
|
||||
}
|
||||
if (isDiffTraceEvent(event)) {
|
||||
rows.push(traceDiffRow(event, displayOptions));
|
||||
continue;
|
||||
}
|
||||
if (isRequestTraceEvent(event)) {
|
||||
continue;
|
||||
}
|
||||
@@ -245,7 +249,7 @@ function traceToolCallRow(event: TraceEvent, options: ResolvedTraceDisplayRowsOp
|
||||
const command = cleanShellCommand(event.command);
|
||||
const toolState = traceToolState(event);
|
||||
const toolName = traceToolName(event, command);
|
||||
const body = traceToolCallBody(event, command);
|
||||
const body = isFileChangeTraceEvent(event) ? traceFileChangeBody(event) : traceToolCallBody(event, command);
|
||||
const eventKey = traceEventIdentityToken(event, options.sequenceAuthority);
|
||||
return {
|
||||
rowId: `tool:${event.itemId ?? eventKey ?? `${event.label ?? event.type ?? "tool"}:${event.createdAt ?? "unknown"}`}`,
|
||||
@@ -256,7 +260,50 @@ function traceToolCallRow(event: TraceEvent, options: ResolvedTraceDisplayRowsOp
|
||||
statusLabel: traceToolStatusLabel(toolState),
|
||||
header: traceEventClock(event, options),
|
||||
body,
|
||||
preview: traceToolPreview(command, toolName),
|
||||
preview: isFileChangeTraceEvent(event) ? traceFileChangePreview(event) : traceToolPreview(command, toolName),
|
||||
bodyFormat: "text"
|
||||
};
|
||||
}
|
||||
|
||||
function isFileChangeTraceEvent(event: TraceEvent): boolean {
|
||||
return nonEmptyString(event.toolName ?? event.toolType) === "fileChange";
|
||||
}
|
||||
|
||||
function traceFileChangePreview(event: TraceEvent): string {
|
||||
const paths = (Array.isArray(event.changes) ? event.changes : [])
|
||||
.filter(isRecord)
|
||||
.map((change) => nonEmptyString(change.path))
|
||||
.filter((path): path is string => Boolean(path));
|
||||
return compactTraceOneLine(paths.length > 0 ? `fileChange ${paths.join(", ")}` : "fileChange", 140);
|
||||
}
|
||||
|
||||
function traceFileChangeBody(event: TraceEvent): string | null {
|
||||
const changes = (Array.isArray(event.changes) ? event.changes : []).filter(isRecord);
|
||||
if (changes.length === 0) return null;
|
||||
return changes.map((change) => {
|
||||
const path = nonEmptyString(change.path) ?? "(path missing)";
|
||||
const kind = nonEmptyString(change.kind) ?? "update";
|
||||
const diff = nonEmptyString(change.diff) ?? "";
|
||||
return [`path: ${path}`, `kind: ${kind}`, "diff:", diff].filter(Boolean).join("\n");
|
||||
}).join("\n\n");
|
||||
}
|
||||
|
||||
function isDiffTraceEvent(event: TraceEvent): boolean {
|
||||
return event.type === "diff" || event.eventType === "diff" || nonEmptyString(event.label) === "agentrun:diff";
|
||||
}
|
||||
|
||||
function traceDiffRow(event: TraceEvent, options: ResolvedTraceDisplayRowsOptions): TraceEventRow {
|
||||
const eventKey = traceEventIdentityToken(event, options.sequenceAuthority);
|
||||
return {
|
||||
rowId: `tool:diff:${eventKey ?? event.createdAt ?? "updated"}`,
|
||||
seq: traceEventDisplaySeq(event, options.sequenceAuthority),
|
||||
sequenceAuthority: options.sequenceAuthority,
|
||||
tone: traceEventTone(event),
|
||||
toolState: "success",
|
||||
statusLabel: "文件 diff 已更新",
|
||||
header: traceEventClock(event, options),
|
||||
body: nonEmptyString(event.diff ?? event.message),
|
||||
preview: "turn/diff/updated",
|
||||
bodyFormat: "text"
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user