Merge pull request #714 from pikasTech/issue701-trace-details
fix: expose AgentRun prompt assembly trace details
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user