From 93b53e7d5674ff2c10cef98d19cf4594963c97ef Mon Sep 17 00:00:00 2001 From: Codex Date: Tue, 2 Jun 2026 21:11:27 +0800 Subject: [PATCH] fix: expose AgentRun prompt assembly trace details --- internal/cloud/code-agent-agentrun-adapter.ts | 60 ++++++++++++++++++- internal/cloud/code-agent-trace-store.ts | 21 +++++++ internal/cloud/server-agent-chat.test.ts | 13 ++++ 3 files changed, 93 insertions(+), 1 deletion(-) diff --git a/internal/cloud/code-agent-agentrun-adapter.ts b/internal/cloud/code-agent-agentrun-adapter.ts index 28bef8f5..924e3ece 100644 --- a/internal/cloud/code-agent-agentrun-adapter.ts +++ b/internal/cloud/code-agent-agentrun-adapter.ts @@ -1021,7 +1021,15 @@ function mapAgentRunEvent(event, mapping = {}) { }; if (event.type === "backend_status") { const phase = String(payload.phase ?? "status"); - return { ...base, type: "backend", status: "running", label: `agentrun:backend:${phase}`, message: textPayload(payload, phase), waitingFor: waitingForForPhase(phase) }; + return { + ...base, + type: "backend", + status: "running", + label: `agentrun:backend:${phase}`, + message: textPayload(payload, phase), + waitingFor: waitingForForPhase(phase), + details: agentRunBackendStatusDetails(phase, payload) + }; } if (event.type === "assistant_message") { const terminal = payload.replyAuthority === true || payload.final === true; @@ -1072,6 +1080,56 @@ function mapAgentRunEvent(event, mapping = {}) { return { ...base, type: "backend", status: "running", label: `agentrun:event:${String(event.type ?? "unknown")}`, message: textPayload(payload, "AgentRun event") }; } +function agentRunBackendStatusDetails(phase, payload = {}) { + if (phase === "initial-prompt-assembly") { + return compactObject({ + initialPromptInjected: payload.initialPromptInjected === true, + reason: firstNonEmpty(payload.reason), + initialPrompt: agentRunResourceSummary(payload.initialPrompt), + valuesPrinted: false + }); + } + if (phase === "resource-bundle-materialized") { + return compactObject({ + kind: firstNonEmpty(payload.kind), + commitId: firstNonEmpty(payload.commitId), + toolAliases: agentRunResourceSummary(payload.toolAliases), + promptRefs: agentRunResourceSummary(payload.promptRefs), + skillRefs: agentRunResourceSummary(payload.skillRefs), + initialPrompt: agentRunResourceSummary(payload.initialPrompt), + valuesPrinted: false + }); + } + return undefined; +} + +function agentRunResourceSummary(value) { + if (!value || typeof value !== "object" || Array.isArray(value)) return undefined; + return compactObject({ + available: typeof value.available === "boolean" ? value.available : undefined, + count: Number.isInteger(value.count) ? value.count : undefined, + bytes: Number.isInteger(value.bytes) ? value.bytes : undefined, + sha256: firstNonEmpty(value.sha256), + promptRefCount: Number.isInteger(value.promptRefCount) ? value.promptRefCount : undefined, + skillRefCount: Number.isInteger(value.skillRefCount) ? value.skillRefCount : undefined, + names: Array.isArray(value.names) ? value.names.map((item) => firstNonEmpty(item)).filter(Boolean).slice(0, 20) : undefined, + requiredMissing: Array.isArray(value.requiredMissing) ? value.requiredMissing.map((item) => firstNonEmpty(item)).filter(Boolean).slice(0, 20) : undefined, + valuesPrinted: false + }); +} + +function compactObject(value) { + if (!value || typeof value !== "object" || Array.isArray(value)) return undefined; + const result = {}; + for (const [key, item] of Object.entries(value)) { + if (item === undefined || item === null) continue; + if (Array.isArray(item) && item.length === 0) continue; + if (typeof item === "object" && !Array.isArray(item) && Object.keys(item).length === 0) continue; + result[key] = item; + } + return Object.keys(result).length > 0 ? result : undefined; +} + function agentRunCommandExecutionEvent(base, payload = {}) { const item = payload.item && typeof payload.item === "object" ? payload.item : null; const payloadToolType = firstNonEmpty(payload.toolName, payload.type, payload.name); diff --git a/internal/cloud/code-agent-trace-store.ts b/internal/cloud/code-agent-trace-store.ts index 1e13a633..5267caa1 100644 --- a/internal/cloud/code-agent-trace-store.ts +++ b/internal/cloud/code-agent-trace-store.ts @@ -345,6 +345,7 @@ function normalizeTraceEvent(event, { traceId, seq, now, fallbackRunnerKind } = errorCode: safeText(event.errorCode, 120), diagnosisCode: safeText(event.diagnosisCode ?? event.diagnosis?.code, 160), diagnosis: normalizeDiagnosis(event.diagnosis), + details: normalizeDetails(event.details), waitingFor: safeText(event.waitingFor, 220), timeoutMs: typeof event.timeoutMs === "number" ? Math.trunc(event.timeoutMs) : undefined, hardTimeoutMs: typeof event.hardTimeoutMs === "number" ? Math.trunc(event.hardTimeoutMs) : undefined, @@ -356,6 +357,26 @@ function normalizeTraceEvent(event, { traceId, seq, now, fallbackRunnerKind } = }); } +function normalizeDetails(value, depth = 0) { + if (value === undefined || value === null || depth > 3) return undefined; + if (typeof value === "boolean") return value; + if (typeof value === "number") return Number.isFinite(value) ? Math.trunc(value) : undefined; + if (typeof value === "string") return safeText(value, 240) || undefined; + if (Array.isArray(value)) { + const items = value.slice(0, 40).map((item) => normalizeDetails(item, depth + 1)).filter((item) => item !== undefined); + return items.length > 0 ? items : undefined; + } + if (typeof value !== "object") return undefined; + const result = {}; + for (const [key, item] of Object.entries(value).slice(0, 80)) { + const safeKey = safeToken(key); + if (!safeKey || safeKey === "prompt" || safeKey === "content" || safeKey === "text") continue; + const normalized = normalizeDetails(item, depth + 1); + if (normalized !== undefined) result[safeKey] = normalized; + } + return Object.keys(result).length > 0 ? result : undefined; +} + function normalizeAssistantDelta(delta, { traceId, now, fallbackRunnerKind } = {}) { const chunk = safeText(delta.chunk ?? delta.delta, 400); if (!chunk) return null; diff --git a/internal/cloud/server-agent-chat.test.ts b/internal/cloud/server-agent-chat.test.ts index 3a4c656a..cd081eb4 100644 --- a/internal/cloud/server-agent-chat.test.ts +++ b/internal/cloud/server-agent-chat.test.ts @@ -277,13 +277,16 @@ test("cloud api /v1/agent/chat delegates v0.2 turns to AgentRun v0.1 over adapte if (second) return send({ items: [ { id: "evt_old_tail", runId: "run_hwlab_adapter", seq: 6, type: "assistant_message", payload: { commandId: "cmd_hwlab_adapter", text: "旧 command 尾部不应进入第二轮。" }, createdAt: "2026-06-01T00:00:02.500Z" }, { id: "evt_7", runId: "run_hwlab_adapter", seq: 7, type: "backend_status", payload: { phase: "turn-started", commandId: "cmd_hwlab_adapter_second", attemptId: "attempt_hwlab_adapter", jobName: "agentrun-v01-runner-hwlab-adapter", namespace: "agentrun-v01" }, createdAt: "2026-06-01T00:00:03.000Z" }, + { id: "evt_7_prompt", runId: "run_hwlab_adapter", seq: 8, type: "backend_status", payload: { phase: "initial-prompt-assembly", commandId: "cmd_hwlab_adapter_second", initialPromptInjected: false, reason: "thread-resume", initialPrompt: { available: true, bytes: 512, sha256: "prompt-sha", promptRefCount: 1, skillRefCount: 2, valuesPrinted: false } }, createdAt: "2026-06-01T00:00:03.500Z" }, { id: "evt_8", runId: "run_hwlab_adapter", seq: 8, type: "assistant_message", payload: { commandId: "cmd_hwlab_adapter_second", text: "AgentRun adapter 复用已有 runner 完成第二轮。" }, createdAt: "2026-06-01T00:00:04.000Z" }, { id: "evt_9", runId: "run_hwlab_adapter", seq: 9, type: "terminal_status", payload: { commandId: "cmd_hwlab_adapter_second", terminalStatus: "completed" }, createdAt: "2026-06-01T00:00:05.000Z" } ] }); 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_bundle", runId: "run_hwlab_adapter", seq: 2, type: "backend_status", payload: { phase: "resource-bundle-materialized", commandId: "cmd_hwlab_adapter", kind: "git", commitId: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", toolAliases: { count: 2, names: ["hwpod", "unidesk-ssh"], valuesPrinted: false }, promptRefs: { count: 1, names: ["hwlab-v02-runtime"], valuesPrinted: false }, skillRefs: { count: 2, names: ["device-pod-cli", "hwlab-agent-runtime"], valuesPrinted: false }, initialPrompt: { available: true, bytes: 512, sha256: "prompt-sha", promptRefCount: 1, skillRefCount: 2, valuesPrinted: false } }, createdAt: "2026-06-01T00:00:00.250Z" }, { 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_prompt", runId: "run_hwlab_adapter", seq: 4, type: "backend_status", payload: { phase: "initial-prompt-assembly", commandId: "cmd_hwlab_adapter", initialPromptInjected: true, reason: "thread-start", initialPrompt: { available: true, bytes: 512, sha256: "prompt-sha", promptRefCount: 1, skillRefCount: 2, valuesPrinted: false } }, createdAt: "2026-06-01T00:00:00.875Z" }, { 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" } ] }); @@ -407,6 +410,15 @@ test("cloud api /v1/agent/chat delegates v0.2 turns to AgentRun v0.1 over adapte assert.equal(payload.agentRun.jobName, "agentrun-v01-runner-hwlab-adapter"); assert.match(payload.reply.content, /AgentRun adapter/u); assert.ok(payload.runnerTrace.events.some((event) => event.label === "agentrun:backend:runner-job-created")); + const bundleEvent = payload.runnerTrace.events.find((event) => event.label === "agentrun:backend:resource-bundle-materialized"); + assert.deepEqual(bundleEvent?.details?.toolAliases?.names, ["hwpod", "unidesk-ssh"]); + assert.deepEqual(bundleEvent?.details?.promptRefs?.names, ["hwlab-v02-runtime"]); + assert.deepEqual(bundleEvent?.details?.skillRefs?.names, ["device-pod-cli", "hwlab-agent-runtime"]); + const promptEvent = payload.runnerTrace.events.find((event) => event.label === "agentrun:backend:initial-prompt-assembly"); + assert.equal(promptEvent?.details?.initialPromptInjected, true); + assert.equal(promptEvent?.details?.reason, "thread-start"); + assert.equal(promptEvent?.details?.initialPrompt?.promptRefCount, 1); + assert.equal(promptEvent?.details?.initialPrompt?.skillRefCount, 2); assert.ok(calls.some((call) => call.path === "/api/v1/runs/run_hwlab_adapter/runner-jobs")); const inspectByTrace = await fetch(`http://127.0.0.1:${port}/v1/agent/chat/inspect?traceId=${traceId}`); @@ -428,6 +440,7 @@ test("cloud api /v1/agent/chat delegates v0.2 turns to AgentRun v0.1 over adapte const traceBody = await trace.json(); assert.ok(traceBody.events.some((event) => event.runId === "run_hwlab_adapter")); assert.ok(traceBody.events.some((event) => event.label === "agentrun:assistant:message")); + assert.equal(traceBody.events.some((event) => event.details?.initialPromptInjected === true), true); const commandTraceEvent = traceBody.events.find((event) => event.label === "item/commandExecution:completed"); assert.equal(commandTraceEvent.toolName, "commandExecution"); assert.equal(commandTraceEvent.createdAt, "2026-06-01T00:00:00.500Z");