From fcfc7e84e507e34cce1a976b5b7ba6e0e695975d Mon Sep 17 00:00:00 2001 From: Codex Date: Tue, 2 Jun 2026 12:15:21 +0800 Subject: [PATCH] fix: preserve AgentRun command tool calls in trace --- internal/cloud/code-agent-agentrun-adapter.ts | 15 ++++++++------- internal/cloud/server-agent-chat.test.ts | 2 +- tools/hwlab-cli/client.test.ts | 9 +++++++-- web/hwlab-cloud-web/app-trace.ts | 15 +++++++++++---- 4 files changed, 27 insertions(+), 14 deletions(-) diff --git a/internal/cloud/code-agent-agentrun-adapter.ts b/internal/cloud/code-agent-agentrun-adapter.ts index c3a825a3..7167fe21 100644 --- a/internal/cloud/code-agent-agentrun-adapter.ts +++ b/internal/cloud/code-agent-agentrun-adapter.ts @@ -1041,20 +1041,21 @@ function mapAgentRunEvent(event, mapping = {}) { function agentRunCommandExecutionEvent(base, payload = {}) { const item = payload.item && typeof payload.item === "object" ? payload.item : null; - if (item?.type !== "commandExecution") return null; + const payloadToolType = firstNonEmpty(payload.toolName, payload.type, payload.name); + if (item?.type !== "commandExecution" && payloadToolType !== "commandExecution") return null; const method = String(payload.method ?? ""); - const completed = method === "item/completed" || item.status === "completed"; - const output = firstNonEmpty(item.aggregatedOutput, item.stdout, item.output, payload.summary?.text, payload.itemPreview); + const completed = method === "item/completed" || payload.status === "completed" || item?.status === "completed"; + const output = firstNonEmpty(item?.aggregatedOutput, item?.stdout, item?.output, payload.outputSummary, payload.stdoutSummary, payload.summary?.text, payload.itemPreview); return { ...base, type: "tool_call", status: completed ? "completed" : "started", label: completed ? "item/commandExecution:completed" : "item/commandExecution:started", toolName: "commandExecution", - itemId: item.id ?? null, - command: item.command ?? item.commandLine ?? null, - exitCode: Number.isInteger(item.exitCode) ? item.exitCode : undefined, - durationMs: typeof item.durationMs === "number" ? item.durationMs : undefined, + itemId: item?.id ?? payload.itemId ?? null, + command: firstNonEmpty(item?.command, item?.commandLine, payload.command, payload.commandLine), + exitCode: Number.isInteger(item?.exitCode) ? item.exitCode : Number.isInteger(payload.exitCode) ? payload.exitCode : undefined, + durationMs: typeof item?.durationMs === "number" ? item.durationMs : typeof payload.durationMs === "number" ? payload.durationMs : undefined, outputBytes: typeof payload.outputBytes === "number" ? payload.outputBytes : payload.summary?.outputBytes, stdoutSummary: output, outputSummary: output, diff --git a/internal/cloud/server-agent-chat.test.ts b/internal/cloud/server-agent-chat.test.ts index 7100c09b..b8bbe082 100644 --- a/internal/cloud/server-agent-chat.test.ts +++ b/internal/cloud/server-agent-chat.test.ts @@ -249,7 +249,7 @@ test("cloud api /v1/agent/chat delegates v0.2 turns to AgentRun v0.1 over adapte ] }); return send({ items: [ { id: "evt_1", runId: "run_hwlab_adapter", seq: 1, type: "backend_status", payload: { phase: "runner-job-created", commandId: "cmd_hwlab_adapter", attemptId: "attempt_hwlab_adapter", jobName: "agentrun-v01-runner-hwlab-adapter", namespace: "agentrun-v01" }, createdAt: "2026-06-01T00:00:00.000Z" }, - { id: "evt_tool", runId: "run_hwlab_adapter", seq: 2, type: "tool_call", payload: { method: "item/completed", item: { type: "commandExecution", id: "call_agentrun_tool", command: "/bin/sh -lc 'hwpod profile list'", status: "completed", exitCode: 0, durationMs: 708, aggregatedOutput: '{"ok":true,"action":"profile.list"}' }, summary: { outputBytes: 42, outputTruncated: false }, commandId: "cmd_hwlab_adapter", runnerId: "runner_hwlab_adapter", attemptId: "attempt_hwlab_adapter" }, createdAt: "2026-06-01T00:00:00.500Z" }, + { id: "evt_tool", runId: "run_hwlab_adapter", seq: 2, type: "tool_call", payload: { method: "item/completed", type: "commandExecution", toolName: "commandExecution", itemId: "call_agentrun_tool", command: "/bin/sh -lc 'hwpod profile list'", status: "completed", exitCode: 0, durationMs: 708, outputSummary: '{"ok":true,"action":"profile.list"}', summary: { outputBytes: 42, outputTruncated: false }, commandId: "cmd_hwlab_adapter", runnerId: "runner_hwlab_adapter", attemptId: "attempt_hwlab_adapter" }, createdAt: "2026-06-01T00:00:00.500Z" }, { id: "evt_noise", runId: "run_hwlab_adapter", seq: 3, type: "backend_status", payload: { phase: "thread/status/changed", commandId: "cmd_hwlab_adapter" }, createdAt: "2026-06-01T00:00:00.750Z" }, { id: "evt_2", runId: "run_hwlab_adapter", seq: 4, type: "assistant_message", payload: { commandId: "cmd_hwlab_adapter", text: "AgentRun adapter 已接管 HWLAB Code Agent。" }, createdAt: "2026-06-01T00:00:01.000Z" }, { id: "evt_3", runId: "run_hwlab_adapter", seq: 5, type: "terminal_status", payload: { commandId: "cmd_hwlab_adapter", terminalStatus: "completed" }, createdAt: "2026-06-01T00:00:02.000Z" } diff --git a/tools/hwlab-cli/client.test.ts b/tools/hwlab-cli/client.test.ts index 944c65ab..ac026600 100644 --- a/tools/hwlab-cli/client.test.ts +++ b/tools/hwlab-cli/client.test.ts @@ -606,19 +606,24 @@ test("hwlab-cli Web trace render reports suppressed noise and command tool detai { traceId: "trc_render_agentrun", seq: 1, label: "agentrun:backend:command-created", status: "running", type: "backend", createdAt: "2026-06-01T13:00:00.000Z" }, { traceId: "trc_render_agentrun", seq: 2, label: "agentrun:backend:thread/status/changed", status: "running", type: "backend", createdAt: "2026-06-01T13:00:01.000Z" }, { traceId: "trc_render_agentrun", seq: 3, label: "item/commandExecution:completed", type: "tool_call", toolName: "commandExecution", status: "completed", itemId: "call_1", command: "/bin/sh -lc 'hwpod profile list'", exitCode: 0, stdoutSummary: '{"ok":true,"action":"profile.list"}', createdAt: "2026-06-01T13:00:02.000Z" }, - { traceId: "trc_render_agentrun", seq: 4, label: "agentrun:assistant:message", type: "assistant", status: "running", message: "当前可见 device-pod。", createdAt: "2026-06-01T13:00:03.000Z" } + { traceId: "trc_render_agentrun", seq: 4, label: "agentrun:tool:item/started", type: "tool_call", toolName: "commandExecution", status: "started", itemId: "call_2", command: "/bin/sh -lc 'printf TOOL_RENDER_OK'", createdAt: "2026-06-01T13:00:02.100Z" }, + { traceId: "trc_render_agentrun", seq: 5, label: "agentrun:tool:item/completed", type: "tool_call", toolName: "commandExecution", status: "completed", itemId: "call_2", command: "/bin/sh -lc 'printf TOOL_RENDER_OK'", exitCode: 0, stdoutSummary: "TOOL_RENDER_OK", outputBytes: 14, createdAt: "2026-06-01T13:00:02.200Z" }, + { traceId: "trc_render_agentrun", seq: 6, label: "agentrun:assistant:message", type: "assistant", status: "running", message: "当前可见 device-pod。", createdAt: "2026-06-01T13:00:03.000Z" } ] }), { status: 200 }) }); assert.equal(result.exitCode, 0); assert.equal(result.payload.body.render, "web"); - assert.equal(result.payload.body.sourceEventCount, 4); + assert.equal(result.payload.body.sourceEventCount, 6); assert.equal(result.payload.body.noiseEventCount, 2); const text = JSON.stringify(result.payload.body.rows); assert.equal(text.includes("thread/status/changed"), false); assert.match(text, /hwpod profile list/u); assert.match(text, /profile\.list/u); + assert.match(text, /TOOL_RENDER_OK/u); + assert.equal(text.includes("toolName=item/started"), false); + assert.equal(text.includes("toolName=item/completed"), false); assert.match(text, /当前可见 device-pod/u); }); diff --git a/web/hwlab-cloud-web/app-trace.ts b/web/hwlab-cloud-web/app-trace.ts index 3ddf56ec..296a310e 100644 --- a/web/hwlab-cloud-web/app-trace.ts +++ b/web/hwlab-cloud-web/app-trace.ts @@ -303,7 +303,7 @@ export function traceDisplayRows(trace, events) { if (isCommandExecutionTraceEvent(event)) { const groupKey = traceCommandGroupKey(event); const group = commandGroups.get(groupKey) ?? { key: groupKey, started: event, completed: event, outputs: [] }; - const renderHere = event.label === "item/commandExecution:started" || group.started === event; + const renderHere = isCommandExecutionStartedTraceEvent(event) || group.started === event; if (renderHere && !renderedCommands.has(groupKey)) { const row = traceCommandSummaryRow(trace, group); if (row) rows.push(row); @@ -383,7 +383,7 @@ function traceCommandExecutionGroups(events) { if (isCommandExecutionTraceEvent(event)) { const key = traceCommandGroupKey(event, activeKey); const group = ensureGroup(key, event); - if (event.label === "item/commandExecution:started") { + if (isCommandExecutionStartedTraceEvent(event)) { group.started = group.started ?? event; activeKey = key; } else { @@ -966,8 +966,15 @@ function traceCommandExecutionBody(event) { } function isCommandExecutionTraceEvent(event) { - return event?.type === "tool_call" && event?.toolName === "commandExecution" && - (event?.label === "item/commandExecution:started" || event?.label === "item/commandExecution:completed"); + if (event?.type !== "tool_call" || event?.toolName !== "commandExecution") return false; + const label = String(event?.label ?? ""); + return label === "item/commandExecution:started" || label === "item/commandExecution:completed" || + label === "agentrun:tool:item/started" || label === "agentrun:tool:item/completed"; +} + +function isCommandExecutionStartedTraceEvent(event) { + const label = String(event?.label ?? ""); + return label === "item/commandExecution:started" || label === "agentrun:tool:item/started"; } function isGenericToolCallTraceEvent(event) {