diff --git a/internal/cloud/server-agent-chat.test.ts b/internal/cloud/server-agent-chat.test.ts index 94c0b35e..94be3444 100644 --- a/internal/cloud/server-agent-chat.test.ts +++ b/internal/cloud/server-agent-chat.test.ts @@ -334,6 +334,20 @@ test("cloud api /v1/agent/chat delegates v0.2 turns to AgentRun v0.1 over adapte assert.ok(payload.runnerTrace.events.some((event) => event.label === "agentrun:backend:runner-job-created")); 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}`); + assert.equal(inspectByTrace.status, 200); + const inspectByTraceBody = await inspectByTrace.json(); + assert.equal(inspectByTraceBody.ok, true); + assert.equal(inspectByTraceBody.latestTraceId, traceId); + assert.equal(inspectByTraceBody.session.sessionId, "ses_server-test-agentrun"); + assert.equal(inspectByTraceBody.session.conversationId, "cnv_server-test-agentrun"); + assert.equal(inspectByTraceBody.session.threadId, "019e8078-db67-7750-a5d9-1a99f3abd445"); + assert.equal(inspectByTraceBody.conversationFacts.conversationId, "cnv_server-test-agentrun"); + assert.equal(inspectByTraceBody.conversationFacts.threadId, "019e8078-db67-7750-a5d9-1a99f3abd445"); + assert.equal(inspectByTraceBody.runnerTrace.traceId, traceId); + assert.equal(inspectByTraceBody.valuesRedacted, true); + assert.equal(JSON.stringify(inspectByTraceBody).includes("test-device-pod-session-token"), false); + const trace = await fetch(`http://127.0.0.1:${port}/v1/agent/chat/trace/${traceId}`); assert.equal(trace.status, 200); const traceBody = await trace.json(); diff --git a/internal/cloud/server-code-agent-http.ts b/internal/cloud/server-code-agent-http.ts index 654817f6..0844326d 100644 --- a/internal/cloud/server-code-agent-http.ts +++ b/internal/cloud/server-code-agent-http.ts @@ -509,8 +509,9 @@ export async function handleCodeAgentInspectHttp(request, response, url, options const registryInspect = sessionRegistry && typeof sessionRegistry.inspect === "function" ? sessionRegistry.inspect(query) : { ok: false, status: "unavailable", traceIds: [], session: null, conversationFacts: null }; - const session = matchedManagerSession ?? registryInspect.session ?? null; - const conversationFacts = registryInspect.conversationFacts ?? null; + const resultEvidence = await codeAgentInspectResultEvidence(query.traceId, options); + const session = matchedManagerSession ?? registryInspect.session ?? resultEvidence.session ?? null; + const conversationFacts = registryInspect.conversationFacts ?? resultEvidence.conversationFacts ?? null; const requestedRunnerTrace = query.traceId ? traceStore.snapshot(query.traceId) : null; const requestedTraceFound = Boolean(requestedRunnerTrace && requestedRunnerTrace.status !== "missing"); const traceIds = uniqueStrings([ @@ -518,6 +519,7 @@ export async function handleCodeAgentInspectHttp(request, response, url, options session?.currentTraceId, session?.lastTraceId, conversationFacts?.latestTraceId, + resultEvidence.latestTraceId, ...(Array.isArray(conversationFacts?.traceIds) ? conversationFacts.traceIds : []), ...(Array.isArray(registryInspect.traceIds) ? registryInspect.traceIds : []) ]); @@ -545,6 +547,48 @@ export async function handleCodeAgentInspectHttp(request, response, url, options }); } +async function codeAgentInspectResultEvidence(traceId, options = {}) { + if (!safeTraceId(traceId)) return { session: null, conversationFacts: null, latestTraceId: null }; + const cached = options.codeAgentChatResults?.get?.(traceId) ?? null; + const persisted = cached ? null : await loadPersistedAgentRunResult(traceId, options); + const result = cached ?? persisted ?? null; + if (!result || typeof result !== "object") return { session: null, conversationFacts: null, latestTraceId: null }; + const agentRun = result.agentRun && typeof result.agentRun === "object" ? result.agentRun : null; + const resultSession = result.session && typeof result.session === "object" ? result.session : null; + const sessionReuse = result.sessionReuse && typeof result.sessionReuse === "object" ? result.sessionReuse : null; + const providerTrace = result.providerTrace && typeof result.providerTrace === "object" ? result.providerTrace : null; + const sessionId = safeSessionId(result.sessionId ?? resultSession?.sessionId ?? sessionReuse?.sessionId ?? agentRun?.sessionId) || null; + const conversationId = safeConversationId(result.conversationId ?? resultSession?.conversationId ?? sessionReuse?.conversationId ?? agentRun?.conversationId) || null; + const threadId = safeOpaqueId(result.threadId ?? resultSession?.threadId ?? sessionReuse?.threadId ?? providerTrace?.threadId ?? agentRun?.threadId) || null; + const status = textValue(resultSession?.status ?? result.status ?? agentRun?.status) || null; + const updatedAt = textValue(result.updatedAt ?? agentRun?.updatedAt ?? resultSession?.updatedAt) || null; + const session = sessionId || conversationId || threadId ? { + sessionId, + conversationId, + threadId, + status, + lastTraceId: traceId, + source: "code-agent-result", + agentRun: agentRun ? agentRunSessionEvidence(result).agentRun : null, + valuesRedacted: true, + secretMaterialStored: false + } : null; + const conversationFacts = conversationId ? { + conversationId, + sessionId, + threadId, + latestTraceId: traceId, + traceIds: [traceId], + turnCount: 1, + source: "code-agent-result", + latestStatus: status, + updatedAt, + valuesRedacted: true, + secretMaterialStored: false + } : null; + return { session, conversationFacts, latestTraceId: traceId }; +} + function sessionMatchesInspectQuery(session, query) { if (!session || typeof session !== "object") return false; if (query.sessionId && session.sessionId === query.sessionId) return true;