import { randomUUID } from "node:crypto"; import { M3_IO_CONTROL_ROUTE, M3_STATUS_ROUTE, describeM3StatusLive, handleM3IoControl } from "./m3-io-control.ts"; import { agentRunSessionEvidence, cancelAgentRunChatTurn, codeAgentAgentRunAdapterEnabled, initialAgentRunChatResult, loadPersistedAgentRunResult, refreshAgentRunTrace, steerAgentRunChatTurn, submitAgentRunChatTurn, syncAgentRunChatResult } from "./code-agent-agentrun-adapter.ts"; import { createCodeAgentErrorPayload, handleCodeAgentChat } from "./code-agent-chat.ts"; import { defaultCodeAgentTraceStore } from "./code-agent-trace-store.ts"; import { codeAgentSessionLifecycleSummary } from "./code-agent-session-lifecycle.ts"; import { firstHeaderValue, getHeader, parsePositiveInteger, positiveInteger, readBody, safeConversationId, safeOpaqueId, safeSessionId, safeTraceId, sendJson, truthyFlag } from "./server-http-utils.ts"; const DEFAULT_CODE_AGENT_RESULT_TRACE_EVENT_LIMIT = 120; const DEFAULT_CODE_AGENT_PROVIDER_PROFILE = "deepseek"; const CODE_AGENT_PROVIDER_PROFILE_IDS = Object.freeze(["deepseek", "codex-api", "minimax-m3", "runtime-default"]); const CODE_AGENT_PROVIDER_PROFILE_LABELS = Object.freeze({ "deepseek": "DeepSeek", "codex-api": "Codex API", "minimax-m3": "MiniMax-M3 via AgentRun", "runtime-default": "运行默认" }); const DEFAULT_CODE_AGENT_CODEX_API_MODEL = "gpt-5.5"; const DEFAULT_CODE_AGENT_CODEX_API_BASE_URL = "http://127.0.0.1:49280/responses"; const DEFAULT_CODE_AGENT_DEEPSEEK_MODEL = "deepseek-chat"; const DEFAULT_CODE_AGENT_MINIMAX_M3_MODEL = "MiniMax-M3"; export async function handleCodeAgentChatHttp(request, response, options) { const body = await readBody(request, options.bodyLimitBytes); let params = {}; try { params = body ? JSON.parse(body) : {}; } catch (error) { sendJson(response, 400, { ...createCodeAgentErrorPayload({ code: "parse_error", message: "Invalid JSON body", reason: error.message, traceId: getHeader(request, "x-trace-id") || "trc_unassigned", model: options.env?.HWLAB_CODE_AGENT_MODEL || options.env?.OPENAI_MODEL || process.env.HWLAB_CODE_AGENT_MODEL || process.env.OPENAI_MODEL || "unknown", layer: "api", retryable: true }) }); return; } if (!params || typeof params !== "object" || Array.isArray(params)) { sendJson(response, 400, { ...createCodeAgentErrorPayload({ code: "invalid_params", message: "Code Agent chat body must be a JSON object", traceId: getHeader(request, "x-trace-id") || "trc_unassigned", model: options.env?.HWLAB_CODE_AGENT_MODEL || options.env?.OPENAI_MODEL || process.env.HWLAB_CODE_AGENT_MODEL || process.env.OPENAI_MODEL || "unknown", layer: "api", retryable: true }) }); return; } const traceId = safeTraceId(getHeader(request, "x-trace-id") || params.traceId) || `trc_${randomUUID()}`; const chatParams = { ...params, traceId, ownerUserId: options.actor?.id ?? params.ownerUserId, ownerRole: options.actor?.role ?? params.ownerRole }; const workspaceClaim = await claimWorkbenchWorkspaceTurn({ params: chatParams, options, traceId, response }); if (workspaceClaim?.blocked) return; if (codeAgentChatShortConnectionRequested(request, params, options)) { submitCodeAgentChatTurn({ params: chatParams, options, traceId }); const traceUrl = `/v1/agent/chat/trace/${encodeURIComponent(traceId)}`; sendJson(response, 202, { accepted: true, status: "running", shortConnection: true, controlSemantics: "submit-and-poll", traceId, workspaceId: workspaceClaim?.workspace?.id ?? null, workspaceRevision: workspaceClaim?.workspace?.revision ?? null, conversationId: safeConversationId(params.conversationId) || null, sessionId: safeSessionId(params.sessionId) || null, traceUrl, resultUrl: `/v1/agent/chat/result/${encodeURIComponent(traceId)}`, streamUrl: `${traceUrl}/stream`, cancelUrl: "/v1/agent/chat/cancel", polling: { resultIntervalMs: parsePositiveInteger(options.env?.HWLAB_CODE_AGENT_RESULT_POLL_INTERVAL_MS, 1000), traceIntervalMs: parsePositiveInteger(options.env?.HWLAB_CODE_AGENT_TRACE_POLL_INTERVAL_MS, 1000) }, runnerTrace: (options.traceStore ?? defaultCodeAgentTraceStore).snapshot(traceId) }); return; } const payload = await runCodeAgentChat(chatParams, options); await recordCodeAgentSessionOwner({ payload, params: chatParams, options, status: payload.status === "completed" ? "active" : payload.status }); const responsePayload = annotateOwner(payload, chatParams); sendJson(response, responsePayload.status === "failed" && responsePayload.error?.code === "invalid_params" ? 400 : 200, responsePayload); } async function runCodeAgentChat(params, options) { return handleCodeAgentChat(params, await codeAgentChatExecutionOptions(options, params)); } async function codeAgentChatExecutionOptions(options = {}, params = {}) { const env = codeAgentProviderProfileEnv(options.env ?? process.env, params); Object.assign(env, await codeAgentDevicePodAuthEnv(options, env)); return { callProvider: options.callCodeAgentProvider, timeoutMs: options.codeAgentTimeoutMs, hardTimeoutMs: options.codeAgentHardTimeoutMs, env, workspace: options.workspace, skillsDirs: options.skillsDirs, skillsDirsExact: options.skillsDirsExact, skillsDiscoveryDelayMs: options.skillsDiscoveryDelayMs, sessionRegistry: options.sessionRegistry, m3IoSkillRequestJson: options.m3IoSkillRequestJson ?? createCodeAgentM3HwlabApiRequestJson(options), codexStdioManager: options.codexStdioManager, traceStore: options.traceStore, gatewayRegistry: options.gatewayRegistry }; } async function codeAgentDevicePodAuthEnv(options = {}, env = process.env) { if (typeof options.accessController?.createCodeAgentDevicePodSession !== "function") return {}; const auth = await options.accessController.createCodeAgentDevicePodSession(); if (!auth?.token) return {}; const runtimeNamespace = runtimeNamespaceForEnv(env); const runtimeLane = runtimeLaneForNamespace(runtimeNamespace) ?? runtimeLaneForEnv(env); const inClusterApiUrl = internalCodeAgentApiUrl(env, runtimeNamespace); const apiUrl = firstNonEmptyValue( codeAgentAgentRunAdapterEnabled(env) ? inClusterApiUrl : null, env.HWLAB_RUNTIME_API_URL, env.HWLAB_CODE_AGENT_DEVICE_POD_API_URL, sameRuntimeEndpoint(env.HWLAB_DEVICE_POD_API_URL, runtimeNamespace, runtimeLane), sameRuntimeEndpoint(env.HWLAB_CLOUD_API_URL, runtimeNamespace, runtimeLane), env.HWLAB_PUBLIC_ENDPOINT, localCloudApiUrl(env) ); return { HWLAB_DEVICE_POD_API_URL: apiUrl, HWLAB_CLOUD_API_URL: apiUrl, HWLAB_RUNTIME_API_URL: apiUrl, HWLAB_RUNTIME_WEB_URL: apiUrl, HWLAB_RUNTIME_NAMESPACE: runtimeNamespace, HWLAB_RUNTIME_LANE: runtimeLane, HWLAB_RUNTIME_ENDPOINT_SOURCE: codeAgentAgentRunAdapterEnabled(env) ? "runtime-namespace" : "runtime-env", HWLAB_RUNTIME_ENDPOINT_LOCKED: "1", HWLAB_CODE_AGENT_ASSEMBLED_RUNTIME: "1", HWLAB_DEVICE_POD_SESSION_TOKEN: auth.token }; } function internalCodeAgentApiUrl(env = process.env, namespace = runtimeNamespaceForEnv(env)) { // Fetch/undici blocks cloud-api's 6667 as a bad port, so AgentRun runners use the internal web proxy. const serviceName = firstNonEmptyValue(env.HWLAB_CODE_AGENT_INTERNAL_API_SERVICE_NAME, env.HWLAB_CLOUD_WEB_SERVICE_NAME, "hwlab-cloud-web"); const port = parsePositiveInteger(firstNonEmptyValue(env.HWLAB_CODE_AGENT_INTERNAL_API_PORT, env.HWLAB_CLOUD_WEB_PORT), 8080); return `http://${serviceName}.${namespace}.svc.cluster.local:${port}`; } function runtimeNamespaceForEnv(env = process.env) { return firstNonEmptyValue( env.HWLAB_RUNTIME_NAMESPACE, env.POD_NAMESPACE, env.HWLAB_NAMESPACE, defaultRuntimeNamespace(env) ); } function runtimeLaneForEnv(env = process.env) { const profile = String(firstNonEmptyValue(env.HWLAB_RUNTIME_LANE, env.HWLAB_GITOPS_LANE, env.HWLAB_GITOPS_PROFILE, env.HWLAB_ENVIRONMENT, env.HWLAB_BOOT_REF, "dev") ?? "dev").trim().toLowerCase(); if (profile === "prod" || profile === "production") return "prod"; if (profile === "v02" || profile === "v0.2" || profile === "0.2") return "v02"; return "dev"; } function runtimeLaneForNamespace(namespace) { if (namespace === "hwlab-v02") return "v02"; if (namespace === "hwlab-prod") return "prod"; if (namespace === "hwlab-dev") return "dev"; return null; } function sameRuntimeEndpoint(value, namespace, lane) { const text = firstNonEmptyValue(value); if (!text) return null; const endpointLane = runtimeLaneForEndpoint(text); const endpointNamespace = runtimeNamespaceForEndpoint(text); if (namespace && endpointNamespace && endpointNamespace !== namespace) return null; if (lane && endpointLane && endpointLane !== lane) return null; return text; } function runtimeNamespaceForEndpoint(value) { const match = String(value ?? "").match(/\.((?:hwlab-[a-z0-9-]+))\.svc\.cluster\.local(?::|\/|$)/iu); return match?.[1] ?? null; } function runtimeLaneForEndpoint(value) { const text = String(value ?? "").toLowerCase(); if (/hwlab-v02|:19666(?:\/|$)|:19667(?:\/|$)/u.test(text)) return "v02"; if (/hwlab-prod|:18666(?:\/|$)|:18667(?:\/|$)/u.test(text)) return "prod"; if (/hwlab-dev|:17666(?:\/|$)|:17667(?:\/|$)|:16666(?:\/|$)|:16667(?:\/|$)/u.test(text)) return "dev"; return null; } function defaultRuntimeNamespace(env = process.env) { const profile = runtimeLaneForEnv(env); if (profile === "prod") return "hwlab-prod"; if (profile === "v02") return "hwlab-v02"; return "hwlab-dev"; } function localCloudApiUrl(env = process.env) { const port = parsePositiveInteger(env.HWLAB_CLOUD_API_PORT, 6667); return `http://127.0.0.1:${port}`; } function codeAgentProviderProfileEnv(env = process.env, params = {}) { const profile = normalizeCodeAgentProviderProfile(params.providerProfile ?? params.codeAgentProviderProfile ?? env.HWLAB_CODE_AGENT_DEFAULT_PROVIDER_PROFILE); if (profile === "runtime-default") return env; const overlay = { ...env }; overlay.HWLAB_CODE_AGENT_SELECTED_PROVIDER_PROFILE = profile; overlay.HWLAB_CODE_AGENT_PROVIDER_PROFILE_LABEL = CODE_AGENT_PROVIDER_PROFILE_LABELS[profile] ?? profile; overlay.HWLAB_CODE_AGENT_PROVIDER = "codex-stdio"; if (profile === "codex-api") { overlay.HWLAB_CODE_AGENT_MODEL = firstNonEmptyValue(env.HWLAB_CODE_AGENT_CODEX_API_MODEL, env.HWLAB_CODE_AGENT_LEGACY_CODEX_MODEL, DEFAULT_CODE_AGENT_CODEX_API_MODEL); overlay.HWLAB_CODE_AGENT_OPENAI_BASE_URL = firstNonEmptyValue(env.HWLAB_CODE_AGENT_CODEX_API_BASE_URL, env.HWLAB_CODE_AGENT_LEGACY_OPENAI_BASE_URL, DEFAULT_CODE_AGENT_CODEX_API_BASE_URL); } else if (profile === "minimax-m3") { overlay.HWLAB_CODE_AGENT_MODEL = firstNonEmptyValue(env.HWLAB_CODE_AGENT_MINIMAX_M3_MODEL, DEFAULT_CODE_AGENT_MINIMAX_M3_MODEL); } else { overlay.HWLAB_CODE_AGENT_MODEL = firstNonEmptyValue(env.HWLAB_CODE_AGENT_DEEPSEEK_MODEL, DEFAULT_CODE_AGENT_DEEPSEEK_MODEL); overlay.HWLAB_CODE_AGENT_OPENAI_BASE_URL = firstNonEmptyValue(env.HWLAB_CODE_AGENT_DEEPSEEK_BASE_URL, defaultDeepSeekBaseUrlForEnv(env)); } overlay.NO_PROXY = mergeNoProxyEntries(env.NO_PROXY, codeAgentClusterNoProxyEntries(env)); overlay.no_proxy = mergeNoProxyEntries(env.no_proxy ?? env.NO_PROXY, codeAgentClusterNoProxyEntries(env)); return overlay; } function normalizeCodeAgentProviderProfile(value) { const text = String(value ?? DEFAULT_CODE_AGENT_PROVIDER_PROFILE).trim().toLowerCase(); return CODE_AGENT_PROVIDER_PROFILE_IDS.includes(text) ? text : DEFAULT_CODE_AGENT_PROVIDER_PROFILE; } function defaultDeepSeekBaseUrlForEnv(env = process.env) { const namespace = env.HWLAB_GITOPS_PROFILE === "prod" || env.HWLAB_ENVIRONMENT === "prod" ? "hwlab-prod" : "hwlab-dev"; return `http://hwlab-deepseek-proxy.${namespace}.svc.cluster.local:4000/v1/responses`; } function codeAgentClusterNoProxyEntries(env = process.env) { const namespace = env.HWLAB_GITOPS_PROFILE === "prod" || env.HWLAB_ENVIRONMENT === "prod" ? "hwlab-prod" : "hwlab-dev"; return [ `${namespace}.svc.cluster.local`, ".svc", ".cluster.local", "127.0.0.1", "localhost", "::1", "10.0.0.0/8", "10.42.0.0/16", "10.43.0.0/16", "hyueapi.com", ".hyueapi.com", "hyui.com", ".hyui.com", "api.minimaxi.com", ".minimaxi.com" ]; } function mergeNoProxyEntries(current, additions = []) { return uniqueStrings([...String(current ?? "").split(","), ...additions]).join(","); } function firstNonEmptyValue(...values) { for (const value of values) { const text = String(value ?? "").trim(); if (text) return text; } return null; } function submitCodeAgentChatTurn({ params, options, traceId }) { const traceStore = options.traceStore ?? defaultCodeAgentTraceStore; const results = options.codeAgentChatResults ?? createCodeAgentChatResultStore(); if (codeAgentAgentRunAdapterEnabled(options.env ?? process.env)) { const initial = initialAgentRunChatResult({ params, options, traceId }); results.set(traceId, annotateOwner(initial, params)); const run = async () => { let executionOptions = options; try { executionOptions = { ...options, ...(await codeAgentChatExecutionOptions(options, params)) }; const payload = await submitAgentRunChatTurn({ params, options: executionOptions, traceId, traceStore, results }); if (isCodeAgentResultCanceled(results.get(traceId))) return; const owned = annotateOwner(payload, params); await recordCodeAgentSessionOwner({ payload: owned, params, options: executionOptions, status: "running" }); results.set(traceId, owned); } catch (error) { if (isCodeAgentResultCanceled(results.get(traceId))) return; const payload = annotateOwner({ ...initial, status: "failed", error: { code: error?.code ?? "agentrun_dispatch_failed", layer: "agentrun", retryable: true, message: error?.message ?? "AgentRun dispatch failed", userMessage: "AgentRun 调度失败;trace/result 轮询已保留错误。" }, blocker: { code: error?.code ?? "agentrun_dispatch_failed", layer: "agentrun", retryable: true, summary: error?.message ?? "AgentRun dispatch failed" }, runnerTrace: traceStore.snapshot(traceId), updatedAt: new Date().toISOString() }, params); recordCodeAgentConversationFact(payload, executionOptions); results.set(traceId, payload); traceStore.append(traceId, { type: "result", status: "failed", label: "agentrun:dispatch:failed", errorCode: payload.error.code, message: payload.error.message, terminal: true }); await recordCodeAgentSessionOwner({ payload, params, options: executionOptions, status: "failed" }); } }; setImmediate(() => { run(); }); return; } results.set(traceId, { accepted: true, status: "running", traceId, conversationId: safeConversationId(params.conversationId) || null, sessionId: safeSessionId(params.sessionId) || null, updatedAt: new Date().toISOString() }); traceStore.ensure(traceId, { runnerKind: "codex-app-server-stdio-runner", workspace: options.workspace ?? options.env?.HWLAB_CODE_AGENT_CODEX_WORKSPACE ?? options.env?.HWLAB_CODE_AGENT_WORKSPACE ?? null, sandbox: options.env?.HWLAB_CODE_AGENT_CODEX_SANDBOX ?? null, sessionMode: "codex-app-server-stdio-long-lived", implementationType: "repo-owned-codex-app-server-stdio-session" }); traceStore.append(traceId, { type: "request", status: "accepted", label: "request:accepted-short-connection", message: "Code Agent request accepted; client must poll trace/result with short HTTP requests.", waitingFor: "codex-stdio" }); const run = async () => { try { const payload = await runCodeAgentChat(params, options); if (isCodeAgentResultCanceled(results.get(traceId))) return; await recordCodeAgentSessionOwner({ payload, params, options, status: payload.status === "completed" ? "active" : payload.status }); results.set(traceId, annotateOwner(payload, params)); traceStore.append(traceId, { type: "result", status: payload.status === "completed" ? "completed" : "failed", label: `result:${payload.status}`, message: payload.status === "completed" ? "Code Agent result is ready for short-connection polling." : payload.error?.message ?? "Code Agent completed without a successful reply.", sessionId: payload.sessionId ?? payload.session?.sessionId, sessionStatus: payload.session?.status, terminal: true }); } catch (error) { if (isCodeAgentResultCanceled(results.get(traceId))) return; const payload = { status: "failed", traceId, conversationId: safeConversationId(params.conversationId) || null, sessionId: safeSessionId(params.sessionId) || null, error: { code: "code_agent_background_failed", layer: "api", retryable: true, message: error?.message ?? "Code Agent background turn failed", userMessage: "Code Agent 后台任务失败;trace/result 轮询已保留错误。" }, updatedAt: new Date().toISOString() }; results.set(traceId, payload); traceStore.append(traceId, { type: "result", status: "failed", label: "result:failed", errorCode: payload.error.code, message: payload.error.message, terminal: true }); } }; setImmediate(() => { run(); }); } function codeAgentChatShortConnectionRequested(request, params, options = {}) { const header = firstHeaderValue(request, "prefer"); const explicit = firstHeaderValue(request, "x-hwlab-short-connection"); const envValue = options.env?.HWLAB_CODE_AGENT_CHAT_SHORT_CONNECTION; return codeAgentAgentRunAdapterEnabled(options.env ?? process.env) || /(?:^|,)\s*respond-async\s*(?:,|$)/iu.test(String(header ?? "")) || truthyFlag(explicit) || params?.shortConnection === true || truthyFlag(envValue); } export async function handleCodeAgentChatResultHttp(request, response, url, options) { const parts = url.pathname.split("/").filter(Boolean); const traceId = decodeURIComponent(parts[4] ?? ""); if (!safeTraceId(traceId)) { sendJson(response, 400, { error: { code: "invalid_trace_id", message: "traceId must start with trc_ and contain only safe identifier characters" } }); return; } const results = options.codeAgentChatResults; const result = results?.get(traceId) ?? null; if (result && !canAccessOwnedResult(result, options.actor)) { sendJson(response, 403, { error: { code: "agent_session_owner_required", message: "Only the session owner or admin can read this Code Agent result" } }); return; } if ((result?.agentRun?.runId && result.status === "running") || (!result && codeAgentAgentRunAdapterEnabled(options.env ?? process.env))) { try { const synced = await syncAgentRunChatResult({ traceId, currentResult: result, options, traceStore: options.traceStore ?? defaultCodeAgentTraceStore }); if (synced.result && !canAccessOwnedResult(synced.result, options.actor)) { sendJson(response, 403, { error: { code: "agent_session_owner_required", message: "Only the session owner or admin can read this Code Agent result" } }); return; } if (synced.result && synced.result.status !== "running") { recordCodeAgentConversationFact(synced.result, options); await recordCodeAgentSessionOwner({ payload: synced.result, params: synced.result, options, status: synced.result.status === "completed" ? "active" : synced.result.status }); sendJson(response, 200, compactCodeAgentChatResultPayload(synced.result, options)); return; } if (synced.result) { sendJson(response, 202, { accepted: true, status: "running", shortConnection: true, traceId, agentRun: synced.result.agentRun, runnerTrace: compactRunnerTraceForResult(synced.runnerTrace, resultTraceEventLimit(options)), waitingFor: synced.runnerTrace?.waitingFor ?? "agentrun-result" }); return; } } catch (error) { const traceStore = options.traceStore ?? defaultCodeAgentTraceStore; traceStore.append(traceId, { type: "result", status: "degraded", label: "agentrun:result:poll-failed", errorCode: error?.code ?? "agentrun_result_poll_failed", message: error?.message ?? "AgentRun result polling failed", waitingFor: "agentrun-result" }); if (result?.agentRun) { sendJson(response, 202, { accepted: true, status: "running", shortConnection: true, traceId, agentRun: result.agentRun, runnerTrace: compactRunnerTraceForResult(traceStore.snapshot(traceId), resultTraceEventLimit(options)), waitingFor: "agentrun-result", degraded: true, error: { code: error?.code ?? "agentrun_result_poll_failed", message: error?.message ?? "AgentRun result polling failed" } }); return; } } } if (result && result.status !== "running") { sendJson(response, 200, compactCodeAgentChatResultPayload(result, options)); return; } const traceStore = options.traceStore ?? defaultCodeAgentTraceStore; const runnerTrace = traceStore.snapshot(traceId); if (result || runnerTrace.status !== "missing") { sendJson(response, 202, { accepted: true, status: "running", shortConnection: true, traceId, runnerTrace: compactRunnerTraceForResult(runnerTrace, resultTraceEventLimit(options)), waitingFor: runnerTrace.waitingFor ?? "codex-stdio" }); return; } sendJson(response, 404, { error: { code: "code_agent_result_not_found", message: `No Code Agent result is registered for ${traceId}` } }); } export async function handleCodeAgentInspectHttp(request, response, url, options) { const query = { conversationId: safeConversationId(url.searchParams.get("conversationId")), sessionId: safeSessionId(url.searchParams.get("sessionId")), threadId: safeOpaqueId(url.searchParams.get("threadId")), traceId: safeTraceId(url.searchParams.get("traceId")) }; const sessionRegistry = options.sessionRegistry; const traceStore = options.traceStore ?? defaultCodeAgentTraceStore; const manager = options.codexStdioManager; const managerDescription = manager && typeof manager.describe === "function" ? manager.describe() : null; const managerRecentSessions = Array.isArray(managerDescription?.recentSessions) ? managerDescription.recentSessions : []; const directManagerSession = query.sessionId && manager && typeof manager.get === "function" ? manager.get(query.sessionId, { conversationId: query.conversationId }) : null; const matchedManagerSession = directManagerSession ?? managerRecentSessions.find((session) => sessionMatchesInspectQuery(session, query)) ?? null; const registryInspect = sessionRegistry && typeof sessionRegistry.inspect === "function" ? sessionRegistry.inspect(query) : { ok: false, status: "unavailable", traceIds: [], session: null, 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([ requestedTraceFound ? query.traceId : null, session?.currentTraceId, session?.lastTraceId, conversationFacts?.latestTraceId, resultEvidence.latestTraceId, ...(Array.isArray(conversationFacts?.traceIds) ? conversationFacts.traceIds : []), ...(Array.isArray(registryInspect.traceIds) ? registryInspect.traceIds : []) ]); const latestTraceId = traceIds[0] ?? null; const runnerTrace = latestTraceId ? latestTraceId === query.traceId && requestedRunnerTrace ? requestedRunnerTrace : traceStore.snapshot(latestTraceId) : null; const found = Boolean(registryInspect.ok || session || latestTraceId); sendJson(response, found ? 200 : 404, { ok: found, action: "code-agent.chat.inspect", status: found ? "found" : "not_found", query, session, conversationFacts, traceIds, latestTraceId, traceUrl: latestTraceId ? `/v1/agent/chat/trace/${encodeURIComponent(latestTraceId)}` : null, resultUrl: latestTraceId ? `/v1/agent/chat/result/${encodeURIComponent(latestTraceId)}` : null, runnerTrace, valuesRedacted: true, secretMaterialStored: false }); } 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; if (query.threadId && session.threadId === query.threadId) return true; if (query.conversationId && session.conversationId === query.conversationId) return true; if (query.traceId && (session.currentTraceId === query.traceId || session.lastTraceId === query.traceId)) return true; return false; } export async function handleCodeAgentCancelHttp(request, response, options) { const body = await readBody(request, options.bodyLimitBytes); let params = {}; try { params = body ? JSON.parse(body) : {}; } catch (error) { sendJson(response, 400, { ...createCodeAgentErrorPayload({ code: "parse_error", message: "Invalid JSON body", reason: error.message, traceId: getHeader(request, "x-trace-id") || "trc_unassigned", layer: "api", retryable: true }) }); return; } if (!params || typeof params !== "object" || Array.isArray(params)) { sendJson(response, 400, { ...createCodeAgentErrorPayload({ code: "invalid_params", message: "Code Agent cancel body must be a JSON object", traceId: getHeader(request, "x-trace-id") || "trc_unassigned", layer: "api", retryable: true }) }); return; } const traceId = safeTraceId(getHeader(request, "x-trace-id") || params.traceId); const traceStore = options.traceStore ?? defaultCodeAgentTraceStore; const snapshot = traceId ? traceStore.snapshot(traceId) : null; const currentResult = traceId ? options.codeAgentChatResults?.get(traceId) ?? await loadPersistedAgentRunResult(traceId, options) : null; if (currentResult?.agentRun?.commandId) { const payload = await cancelAgentRunChatTurn({ traceId, currentResult, options, traceStore }); if (payload) { await recordCodeAgentSessionOwner({ payload, params: { ...params, traceId, ownerUserId: options.actor?.id, ownerRole: options.actor?.role }, options, status: "canceled" }); sendJson(response, 200, payload); return; } } const sessionId = safeSessionId(params.sessionId) || safeSessionId(snapshot?.sessionId); const conversationId = safeConversationId(params.conversationId); const manager = options.codexStdioManager; if (!traceId) { sendJson(response, 400, cancelBlockedPayload({ code: "cancel_trace_missing", message: "traceId is required to cancel the current Code Agent request.", traceId: "trc_unassigned", conversationId, sessionId })); return; } if (!manager || typeof manager.get !== "function" || typeof manager.cancel !== "function") { traceStore.append(traceId, { type: "cancel", status: "unsupported", label: "cancel:unsupported", errorCode: "cancel_unsupported", message: "Codex stdio cancel/interrupt backend is not available on this runtime.", waitingFor: "session-control" }); const runnerTrace = traceStore.snapshot(traceId); sendJson(response, 501, cancelBlockedPayload({ code: "cancel_unsupported", message: "当前后端不支持 Code Agent interrupt/cancel;本次按 unsupported/degraded 返回,不会静默假装已中断。", traceId, conversationId, sessionId, runnerTrace, status: "degraded", unsupported: true, degraded: true })); return; } if (!sessionId) { traceStore.append(traceId, { type: "cancel", status: "blocked", label: "cancel:not_cancelable", errorCode: "cancel_session_missing", message: "Cancel request did not include a bound Codex stdio sessionId.", waitingFor: "session-binding" }); sendJson(response, 409, cancelBlockedPayload({ code: "cancel_session_missing", message: "当前请求尚未暴露可取消的 Codex stdio sessionId;页面不能只隐藏 UI,已保留输入和 trace。", traceId, conversationId, sessionId: null, runnerTrace: traceStore.snapshot(traceId) })); return; } const currentSession = manager.get(sessionId, { conversationId }) ?? null; if (!currentSession || !["busy", "creating"].includes(currentSession.status)) { traceStore.append(traceId, { type: "cancel", status: "blocked", label: "cancel:not_in_flight", errorCode: "cancel_not_in_flight", message: `Session ${sessionId} is not an in-flight Codex stdio request.`, sessionId, sessionStatus: currentSession?.status ?? "missing" }); sendJson(response, 409, cancelBlockedPayload({ code: currentSession ? "cancel_not_in_flight" : "cancel_session_not_found", message: currentSession ? `当前 session 状态为 ${currentSession.status},没有可取消的 in-flight Codex stdio 请求。` : `没有找到 sessionId=${sessionId} 的 Codex stdio session。`, traceId, conversationId, sessionId, session: currentSession, runnerTrace: traceStore.snapshot(traceId) })); return; } const canceledSession = manager.cancel(sessionId, { traceId, conversationId, reason: "user_cancel" }); traceStore.append(traceId, { type: "cancel", status: "canceled", label: "cancel:canceled", message: "User canceled the current Codex stdio request.", sessionId, sessionStatus: canceledSession?.status ?? "canceled", sessionLifecycleStatus: codeAgentSessionLifecycleSummary({ session: canceledSession, status: "canceled" }).status, waitingFor: "user-retry", terminal: true }); const runnerTraceSnapshot = traceStore.snapshot(traceId); const sessionSummary = codeAgentSessionLifecycleSummary({ session: canceledSession, runnerTrace: runnerTraceSnapshot, status: "canceled" }); const runnerTrace = { ...runnerTraceSnapshot, sessionLifecycleStatus: sessionSummary.status }; const payload = { accepted: true, canceled: true, status: "canceled", conversationId: conversationId ?? canceledSession?.conversationId ?? null, sessionId, traceId, session: canceledSession, sessionLifecycleStatus: sessionSummary.status, sessionLifecycle: sessionSummary, sessionSummary, runnerTrace, lastTraceEvent: runnerTrace.lastEvent, retryable: true, error: { code: "codex_stdio_canceled", layer: "session", category: "canceled", retryable: true, message: "user canceled current Code Agent request", userMessage: "当前 Codex stdio 请求已取消;输入、sessionId、traceId 和最后 trace event 已保留,可重试上一条消息。", traceId, route: "/v1/agent/chat/cancel", toolName: "codex-stdio.cancel" }, userMessage: "当前 Codex stdio 请求已取消;输入、sessionId、traceId 和最后 trace event 已保留,可重试上一条消息。", updatedAt: new Date().toISOString() }; await recordCodeAgentSessionOwner({ payload, params: { ...params, traceId, ownerUserId: options.actor?.id, ownerRole: options.actor?.role }, options, status: "canceled" }); recordCodeAgentConversationFact(payload, options); options.codeAgentChatResults?.set(traceId, annotateOwner(payload, { ownerUserId: options.actor?.id, ownerRole: options.actor?.role })); sendJson(response, 200, payload); } function recordCodeAgentConversationFact(payload = {}, options = {}) { const registry = options.sessionRegistry; const conversationId = safeConversationId(payload.conversationId); if (!conversationId || typeof registry?.recordFact !== "function") return null; try { return registry.recordFact(conversationId, { kind: payload.status === "completed" ? "runner_turn" : "runner_turn_terminal", conversationId, sessionId: payload.sessionId, traceId: payload.traceId, provider: payload.provider, backend: payload.backend, workspace: payload.workspace, sandbox: payload.sandbox, session: payload.session, sessionMode: payload.sessionMode, sessionReuse: payload.sessionReuse, implementationType: payload.implementationType, runner: payload.runner, runnerTrace: payload.runnerTrace, partialContext: payload.partialContext ?? payload.runnerTrace?.partialContext, capabilityLevel: payload.capabilityLevel, toolCalls: payload.toolCalls, skills: payload.skills }, { now: options.now }); } catch { return null; } } export async function handleCodeAgentSteerHttp(request, response, options) { const body = await readBody(request, options.bodyLimitBytes); let params = {}; try { params = body ? JSON.parse(body) : {}; } catch (error) { sendJson(response, 400, { ...createCodeAgentErrorPayload({ code: "parse_error", message: "Invalid JSON body", reason: error.message, traceId: getHeader(request, "x-trace-id") || "trc_unassigned", layer: "api", retryable: true, route: "/v1/agent/chat/steer" }) }); return; } if (!params || typeof params !== "object" || Array.isArray(params)) { sendJson(response, 400, { ...createCodeAgentErrorPayload({ code: "invalid_params", message: "Code Agent steer body must be a JSON object", traceId: getHeader(request, "x-trace-id") || "trc_unassigned", layer: "api", retryable: true, route: "/v1/agent/chat/steer" }) }); return; } const traceId = safeTraceId(getHeader(request, "x-trace-id") || params.traceId || params.targetTraceId); const message = firstNonEmptyValue(params.message, params.prompt, params.text); if (!traceId || !message) { sendJson(response, 400, { ...createCodeAgentErrorPayload({ code: !traceId ? "steer_trace_missing" : "steer_message_missing", message: !traceId ? "traceId is required to steer the current Code Agent turn." : "message is required to steer the current Code Agent turn.", traceId: traceId || "trc_unassigned", layer: "api", retryable: true, route: "/v1/agent/chat/steer" }) }); return; } const traceStore = options.traceStore ?? defaultCodeAgentTraceStore; const currentResult = options.codeAgentChatResults?.get?.(traceId) ?? await loadPersistedAgentRunResult(traceId, options); if (!canAccessOwnedResult(currentResult, options.actor)) { sendJson(response, 403, { ok: false, status: "forbidden", traceId, error: { code: "code_agent_trace_forbidden", message: "Code Agent trace is not visible to the current actor" } }); return; } if (!currentResult?.agentRun?.runId || !currentResult?.agentRun?.commandId) { sendJson(response, 404, { ok: false, status: "not_found", traceId, error: { code: "steer_trace_not_found", message: "No AgentRun-backed Code Agent turn was found for the requested traceId." }, route: "/v1/agent/chat/steer", runnerTrace: traceStore.snapshot(traceId) }); return; } try { const payload = await steerAgentRunChatTurn({ traceId, currentResult, params: { ...params, message, ownerUserId: options.actor?.id ?? params.ownerUserId, ownerRole: options.actor?.role ?? params.ownerRole }, options, traceStore }); await recordCodeAgentSessionOwner({ payload: currentResult, params: { ...params, traceId, ownerUserId: options.actor?.id, ownerRole: options.actor?.role }, options, status: "running" }); sendJson(response, 202, payload); } catch (error) { traceStore.append(traceId, { type: "backend", status: "failed", label: "agentrun:steer:failed", errorCode: error?.code ?? "agentrun_steer_failed", message: error?.message ?? "AgentRun steer command failed.", terminal: false, valuesPrinted: false }); sendJson(response, error?.statusCode === 404 ? 404 : 409, { ok: false, accepted: false, status: "failed", traceId, route: "/v1/agent/chat/steer", conversationId: safeConversationId(currentResult.conversationId ?? params.conversationId) || null, sessionId: safeSessionId(currentResult.sessionId ?? params.sessionId) || null, threadId: safeOpaqueId(currentResult.threadId ?? params.threadId) || null, agentRun: currentResult.agentRun, runnerTrace: traceStore.snapshot(traceId), error: { code: error?.code ?? "agentrun_steer_failed", layer: "agentrun", category: "steer_failed", retryable: true, message: error?.message ?? "AgentRun steer command failed.", traceId, route: "/v1/agent/chat/steer", toolName: "agentrun.command.steer" } }); } } async function recordCodeAgentSessionOwner({ payload = {}, params = {}, options = {}, status = "active" } = {}) { const ownerUserId = options.actor?.id ?? params.ownerUserId; if (!ownerUserId || !options.accessController?.recordAgentSessionOwner) return null; const traceId = safeTraceId(payload.traceId ?? params.traceId); const session = payload.session && typeof payload.session === "object" ? payload.session : null; const sessionReuse = payload.sessionReuse && typeof payload.sessionReuse === "object" ? payload.sessionReuse : null; const sessionId = safeSessionId(payload.sessionId ?? session?.sessionId ?? sessionReuse?.sessionId ?? params.sessionId) || null; const conversationId = safeConversationId(payload.conversationId ?? session?.conversationId ?? sessionReuse?.conversationId ?? params.conversationId) || null; const threadId = safeOpaqueId(session?.threadId ?? sessionReuse?.threadId ?? payload.threadId ?? params.threadId) || null; try { return await options.accessController.recordAgentSessionOwner({ ownerUserId, ownerRole: options.actor?.role ?? params.ownerRole ?? null, sessionId, projectId: params.projectId ?? payload.projectId ?? null, conversationId, threadId, traceId, status, session: codeAgentSessionOwnerEvidence(payload, params) }); } catch (error) { const traceStore = options.traceStore ?? defaultCodeAgentTraceStore; if (traceId) { traceStore.append(traceId, { type: "session-owner", status: "degraded", label: "session-owner:persist-failed", errorCode: "agent_session_owner_persist_failed", message: error?.message ?? "Code Agent session owner binding persistence failed.", valuesPrinted: false }); } return null; } } async function claimWorkbenchWorkspaceTurn({ params = {}, options = {}, traceId, response } = {}) { const workspaceId = safeWorkspaceId(params.workspaceId); if (!workspaceId || !options.accessController?.store || !options.actor) return null; const store = options.accessController.store; const workspace = await store.getWorkspaceForUser?.({ workspaceId, ownerUserId: options.actor.id, actorRole: options.actor.role }); if (!workspace) { sendJson(response, 404, { ok: false, status: "failed", error: { code: "workbench_workspace_not_found", message: "Workbench workspace is not visible to the current actor" }, workspaceId }); return { blocked: true }; } const expectedRevision = Number.parseInt(String(params.expectedWorkspaceRevision ?? params.workspaceRevision ?? ""), 10); if (Number.isInteger(expectedRevision) && expectedRevision > 0 && workspace.revision !== expectedRevision) { sendJson(response, 409, { ok: false, status: "revision_conflict", error: { code: "workspace_revision_conflict", message: "Workbench workspace revision changed; restore workspace before submitting a new turn." }, workspaceId, expectedRevision, workspaceRevision: workspace.revision }); return { blocked: true }; } const activeTraceId = safeTraceId(workspace.activeTraceId); const activeResult = activeTraceId ? await resolveWorkbenchActiveTraceResult(activeTraceId, options) : null; const activeTraceSnapshot = activeTraceId ? (options.traceStore ?? defaultCodeAgentTraceStore).snapshot(activeTraceId) : null; const activeTraceOrphaned = Boolean(activeTraceId && !activeResult && isWorkbenchOrphanActiveTrace(activeTraceSnapshot)); const selectedConversationId = safeConversationId(workspace.selectedConversationId); const shouldInspectSelectedConversation = Boolean( activeTraceId && !activeResult && !activeTraceOrphaned && selectedConversationId && options.accessController?.visibleConversationForActor ); const selectedConversation = shouldInspectSelectedConversation ? await options.accessController.visibleConversationForActor(options.actor, selectedConversationId, workspace.projectId) : null; const activeTraceBlocksTurn = Boolean( activeTraceId && !activeTraceOrphaned && (activeResult?.status === "running" || (!activeResult && isActiveWorkbenchConversation(selectedConversation))) ); if (activeTraceOrphaned) { await store.updateWorkspace?.({ workspaceId, ownerUserId: options.actor.id, actorRole: options.actor.role, selectedConversationId: workspace.selectedConversationId, selectedAgentSessionId: workspace.selectedAgentSessionId, selectedDevicePodId: workspace.selectedDevicePodId, activeTraceId: null, providerProfile: workspace.providerProfile, patch: { ...(workspace.workspace && typeof workspace.workspace === "object" ? workspace.workspace : {}), activeTraceId: null, staleActiveTraceId: activeTraceId, staleActiveTraceReason: "orphan-cancel-session-missing", sessionStatus: "failed", updatedAt: new Date().toISOString(), source: "code-agent-submit-active-trace-repair", valuesRedacted: true, secretMaterialStored: false }, updatedBySessionId: options.authSession?.id ?? null, updatedByClient: "code-agent-submit-active-trace-repair", now: new Date().toISOString() }); } if (activeTraceBlocksTurn) { const conversationId = safeConversationId(params.conversationId) || selectedConversationId || activeResult?.conversationId || null; const sessionId = safeSessionId(params.sessionId) || safeSessionId(workspace.selectedAgentSessionId) || activeResult?.sessionId || null; const threadId = safeOpaqueId(params.threadId) || activeResult?.threadId || null; sendJson(response, 409, { ok: false, status: "workspace_agent_busy", error: { code: "workspace_agent_busy", message: "Workbench workspace already has an active Code Agent turn; follow the active trace instead of opening a new conversation." }, workspaceId, workspaceRevision: workspace.revision, activeTraceId, conversationId, sessionId, threadId, providerTrace: activeResult?.providerTrace ?? activeResult?.agentRun?.providerTrace ?? null, resultUrl: `/v1/agent/chat/result/${encodeURIComponent(activeTraceId)}`, traceUrl: `/v1/agent/chat/trace/${encodeURIComponent(activeTraceId)}` }); return { blocked: true }; } const updated = await store.updateWorkspace?.({ workspaceId, ownerUserId: options.actor.id, actorRole: options.actor.role, selectedConversationId: safeConversationId(params.conversationId) || workspace.selectedConversationId, selectedAgentSessionId: safeSessionId(params.sessionId) || workspace.selectedAgentSessionId, activeTraceId: traceId, providerProfile: textValue(params.providerProfile) || workspace.providerProfile, patch: { selectedConversationId: safeConversationId(params.conversationId) || workspace.selectedConversationId, selectedAgentSessionId: safeSessionId(params.sessionId) || workspace.selectedAgentSessionId, activeTraceId: traceId, providerProfile: textValue(params.providerProfile) || workspace.providerProfile, sessionStatus: "running", updatedAt: new Date().toISOString(), source: "code-agent-submit", valuesRedacted: true, secretMaterialStored: false }, updatedBySessionId: options.authSession?.id ?? null, updatedByClient: textValue(params.updatedByClient ?? params.client) || "code-agent-chat", now: new Date().toISOString() }); return { workspace: updated ?? workspace }; } function isActiveWorkbenchConversation(conversation = null) { if (!conversation) return false; const values = [conversation.status, conversation.session?.status, conversation.snapshot?.sessionStatus]; for (const message of Array.isArray(conversation.messages) ? conversation.messages.slice(-5) : []) { values.push(message?.status, message?.runnerTrace?.sessionStatus, message?.sessionStatus); } return values.some((value) => ["running", "busy", "pending", "queued"].includes(textValue(value).toLowerCase())); } function isWorkbenchOrphanActiveTrace(snapshot) { if (!snapshot || typeof snapshot !== "object") return false; const last = snapshot.lastEvent && typeof snapshot.lastEvent === "object" ? snapshot.lastEvent : null; if (!last) return false; if (last.errorCode !== "cancel_session_missing" && last.label !== "cancel:not_cancelable") return false; return !snapshot.sessionId && !snapshot.runId && !snapshot.commandId && !snapshot.sessionStatus && !snapshot.turn; } async function resolveWorkbenchActiveTraceResult(traceId, options = {}) { if (!safeTraceId(traceId)) return null; const cached = options.codeAgentChatResults?.get?.(traceId) ?? null; if (cached?.agentRun?.runId && cached.status === "running") { try { const synced = await syncAgentRunChatResult({ traceId, currentResult: cached, options, traceStore: options.traceStore ?? defaultCodeAgentTraceStore }); return synced.result ?? cached; } catch { return cached; } } if (cached) return cached; if (!codeAgentAgentRunAdapterEnabled(options.env ?? process.env)) return null; try { const persisted = await loadPersistedAgentRunResult(traceId, options); if (!persisted?.agentRun?.runId) return persisted; const synced = await syncAgentRunChatResult({ traceId, currentResult: persisted, options, traceStore: options.traceStore ?? defaultCodeAgentTraceStore }); return synced.result ?? persisted; } catch { return null; } } function safeWorkspaceId(value) { const text = textValue(value); return /^wsp_[A-Za-z0-9_.:-]+$/u.test(text) ? text : ""; } function textValue(value) { return String(value ?? "").trim(); } function codeAgentSessionOwnerEvidence(payload = {}, params = {}) { return { provider: payload.provider ?? null, model: payload.model ?? null, backend: payload.backend ?? null, status: payload.status ?? null, sessionMode: payload.sessionMode ?? payload.session?.sessionMode ?? null, sessionLifecycleStatus: payload.sessionLifecycleStatus ?? null, runnerKind: payload.runner?.kind ?? payload.runnerTrace?.runnerKind ?? null, conversationId: payload.conversationId ?? params.conversationId ?? null, sessionId: payload.sessionId ?? payload.session?.sessionId ?? payload.sessionReuse?.sessionId ?? params.sessionId ?? null, threadId: payload.session?.threadId ?? payload.sessionReuse?.threadId ?? payload.threadId ?? params.threadId ?? null, traceId: payload.traceId ?? params.traceId ?? null, projectId: payload.projectId ?? params.projectId ?? null, ...agentRunSessionEvidence(payload), secretMaterialStored: false, valuesRedacted: true }; } function annotateOwner(payload, params = {}) { if (!params.ownerUserId) return payload; return { ...payload, ownerUserId: params.ownerUserId, ownerRole: params.ownerRole ?? null }; } function canAccessOwnedResult(result, actor) { if (!result?.ownerUserId || !actor) return true; return actor.role === "admin" || actor.id === result.ownerUserId; } function isCodeAgentResultCanceled(result) { return result?.status === "canceled" || result?.canceled === true || result?.error?.code === "codex_stdio_canceled"; } function cancelBlockedPayload({ code, message, traceId, conversationId = null, sessionId = null, session = null, runnerTrace = null, status = "failed", unsupported = false, degraded = false }) { const sessionSummary = codeAgentSessionLifecycleSummary({ session, runnerTrace, status, unsupported, degraded, error: { code, userMessage: message, message } }); return { accepted: false, canceled: false, status, conversationId, sessionId, traceId, session, sessionLifecycleStatus: sessionSummary.status, sessionLifecycle: sessionSummary, sessionSummary, unsupported, degraded, runnerTrace, lastTraceEvent: runnerTrace?.lastEvent ?? null, error: { code, layer: "session", category: "cancel_blocked", retryable: true, userMessage: message, message, traceId, route: "/v1/agent/chat/cancel", toolName: "codex-stdio.cancel" }, blocker: { code, layer: "session", category: "cancel_blocked", retryable: true, summary: message, userMessage: message, traceId, route: "/v1/agent/chat/cancel", toolName: "codex-stdio.cancel" } }; } export async function handleCodeAgentTraceHttp(request, response, url, options) { const parts = url.pathname.split("/").filter(Boolean); const traceId = decodeURIComponent(parts[4] ?? ""); const traceStore = options.traceStore ?? defaultCodeAgentTraceStore; if (!/^trc_[A-Za-z0-9_.:-]+$/u.test(traceId)) { sendJson(response, 400, { error: { code: "invalid_trace_id", message: "traceId must start with trc_ and contain only safe identifier characters" } }); return; } const result = options.codeAgentChatResults?.get(traceId) ?? null; if (result && !canAccessOwnedResult(result, options.actor)) { sendJson(response, 403, { error: { code: "agent_session_owner_required", message: "Only the session owner or admin can read this Code Agent trace" } }); return; } const agentRunResult = result?.agentRun ? result : await loadPersistedAgentRunResult(traceId, options); if (agentRunResult?.agentRun) { await refreshAgentRunTrace({ traceId, result: agentRunResult, options, traceStore }); } if (parts[5] === "stream") { sendTraceSse(response, traceStore, traceId, options); return; } sendJson(response, 200, traceStore.snapshot(traceId)); } function sendTraceSse(response, traceStore, traceId, options = {}) { response.writeHead(200, { "content-type": "text/event-stream; charset=utf-8", "cache-control": "no-store", connection: "keep-alive", "x-accel-buffering": "no" }); const writeEvent = (eventName, payload) => { response.write(`event: ${eventName}\n`); response.write(`data: ${JSON.stringify(payload)}\n\n`); }; writeEvent("snapshot", traceStore.snapshot(traceId)); const unsubscribe = traceStore.subscribe(traceId, (event, snapshot) => { writeEvent("runnerTrace", { event, snapshot }); }); const heartbeatMs = parsePositiveInteger(options.env?.HWLAB_CODE_AGENT_TRACE_HEARTBEAT_MS, 15000); const heartbeat = setInterval(() => { writeEvent("heartbeat", traceStore.snapshot(traceId)); }, heartbeatMs); response.on("close", () => { clearInterval(heartbeat); unsubscribe(); }); } export function createCodeAgentChatResultStore({ maxResults = 256 } = {}) { const limit = positiveInteger(maxResults, 256); const results = new Map(); function set(traceId, payload) { const id = safeTraceId(traceId); if (!id) return; results.set(id, { ...payload, traceId: id, cachedAt: new Date().toISOString() }); while (results.size > limit) { const oldest = results.keys().next().value; if (!oldest) break; results.delete(oldest); } } return { set, get: (traceId) => results.get(safeTraceId(traceId)) ?? null, clear: () => results.clear() }; } function compactCodeAgentChatResultPayload(payload, options = {}) { if (!payload || typeof payload !== "object") return payload; const limit = resultTraceEventLimit(options); return { ...payload, ...(payload.runnerTrace && typeof payload.runnerTrace === "object" ? { runnerTrace: compactRunnerTraceForResult(payload.runnerTrace, limit) } : {}) }; } function compactRunnerTraceForResult(runnerTrace, limit = DEFAULT_CODE_AGENT_RESULT_TRACE_EVENT_LIMIT) { if (!runnerTrace || typeof runnerTrace !== "object") return runnerTrace; const events = Array.isArray(runnerTrace.events) ? runnerTrace.events : []; const total = Number.isInteger(runnerTrace.eventCount) ? runnerTrace.eventCount : events.length; const effectiveLimit = Math.max(8, positiveInteger(limit, DEFAULT_CODE_AGENT_RESULT_TRACE_EVENT_LIMIT)); if (events.length <= effectiveLimit) { return { ...runnerTrace, eventCount: total, eventsCompacted: false }; } const headCount = Math.min(24, Math.max(1, Math.floor(effectiveLimit / 4))); const tailCount = Math.max(1, effectiveLimit - headCount - 1); const omitted = Math.max(0, events.length - headCount - tailCount); const head = events.slice(0, headCount); const tail = events.slice(events.length - tailCount); const marker = { traceId: runnerTrace.traceId ?? head[0]?.traceId ?? tail[0]?.traceId ?? null, type: "trace", stage: "trace", status: "truncated", label: "trace:compacted", createdAt: tail[0]?.createdAt ?? runnerTrace.updatedAt ?? new Date().toISOString(), message: `Result polling response compacted ${omitted} trace events; fetch /v1/agent/chat/trace/${encodeURIComponent(runnerTrace.traceId ?? "")} for the full trace.`, omittedEventCount: omitted, retainedEventCount: head.length + tail.length, totalEventCount: total, valuesPrinted: false }; const compactEvents = [...head, marker, ...tail]; return { ...runnerTrace, eventCount: total, events: compactEvents, eventLabels: compactEvents.map(traceEventLabel).filter(Boolean), eventsCompacted: true, eventWindow: { mode: "head-tail", retained: compactEvents.length, omitted, total } }; } function traceEventLabel(event) { return typeof event === "string" ? event : event?.label; } function resultTraceEventLimit(options = {}) { return positiveInteger( options.env?.HWLAB_CODE_AGENT_RESULT_TRACE_EVENT_LIMIT, DEFAULT_CODE_AGENT_RESULT_TRACE_EVENT_LIMIT ); } function createCodeAgentM3HwlabApiRequestJson(options = {}) { return async (targetUrl, request = {}) => { const url = new URL(targetUrl); const route = url.pathname; if (route === M3_IO_CONTROL_ROUTE && request.method === "POST") { return { ok: true, status: 200, body: await handleM3IoControl(request.body ?? {}, { runtimeStore: options.runtimeStore, now: options.now, env: options.env, requestJson: options.m3IoRequestJson }) }; } if (route === M3_STATUS_ROUTE && request.method === "GET") { return { ok: true, status: 200, body: await describeM3StatusLive(options) }; } return { ok: false, status: 404, body: { error: { code: "not_found", message: `Unsupported Code Agent M3 HWLAB API route ${route}` } }, error: `Unsupported Code Agent M3 HWLAB API route ${route}` }; }; } function uniqueStrings(values) { return [...new Set(values.map((value) => String(value ?? "").trim()).filter(Boolean))]; }