// SPEC: PJ2026-0104010803 Workbench唯一投影 draft-2026-06-20-p2-terminal-outbox-recovery; PJ2026-010403 API契约 draft-2026-06-20-p2-terminal-outbox-recovery; PJ2026-010205 HWLAB接入 draft-2026-06-25-p0-session-warm-runner-contract; PJ2026-0102 Agent编排 draft-2026-06-17-r0; PJ2026-01060505 Workbench Performance draft-2026-06-17-p0. // Responsibility: Code Agent HTTP session and turn admission. import { createHash, 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, steerAgentRunChatTurn, submitAgentRunChatTurn } 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 { messageAuthorityTextValue } from "./code-agent-agentrun-prompt.ts"; import { promoteWorkbenchTurnAdmission as persistWorkbenchTurnAdmissionPromotion, writeWorkbenchSessionAdmissionFact } from "./workbench-projection-writer.ts"; import { createWorkbenchReadModel } from "./workbench-read-model.ts"; import { workbenchRealtimeCapabilities } from "./workbench-realtime-capabilities.ts"; import { codeAgentOtelTraceFields, emitCodeAgentOtelSpan } from "./otel-trace.ts"; import { firstHeaderValue, getHeader, parsePositiveInteger, positiveInteger, readBody, safeConversationId, safeOpaqueId, safeSessionId, safeTraceId, sendJson, truthyFlag } from "./server-http-utils.ts"; import { normalizeCodeAgentProviderProfile, firstNonEmptyValue, recordCodeAgentSessionInputFact, buildCodeAgentSessionInputFact, stableCodeAgentInputId, codeAgentInputDelivery, codeAgentInputStatus, codeAgentResultTimingFields, firstTimestampIso, firstLatestTimestampIso, codeAgentChatShortConnectionRequested, preflightCodeAgentBilling, recordCodeAgentBillingUsage, finalizeCodeAgentBillingUsage, releaseCodeAgentBillingReservation, withCodeAgentBillingReservation, codeAgentBillingEnabled, codeAgentBillingMetadata, sanitizeBillingReservation, sanitizeBillingRecord, sanitizeBillingRelease, codeAgentUsedTokens, normalizeTurnStatus, recordCodeAgentConversationFact, recordCodeAgentSessionOwner, codeAgentOwnerStatusForResult, textValue, timestampIsoOrNow, timestampIsoOrNull, codeAgentSessionOwnerEvidence, codeAgentContinuationEvidence, codeAgentFreshContinuationFailure, normalizeCodeAgentFailureKind, codeAgentFailureRequiresFreshContinuation, codeAgentProviderProfileEvidence, codeAgentTraceResultEvidence, codeAgentPromptMetadata, codeAgentPromptMetadataFromText, codeAgentPromptMetadataFromParts, codeAgentPromptFields, codeAgentPromptId, clippedPromptText, sha256Text, compactCodeAgentObject, codeAgentBillingReservationEvidence, codeAgentConversationMessagesEvidence, boundedConversationMessageText, conversationText, conversationTextRaw, codeAgentMessageTraceSuffix, codeAgentTurnLifecycleFields, codeAgentLifecycleMessageId, safeTurnId, safeMessageId, codeAgentConversationAgentMessageStatus, codeAgentPayloadHasSealedFinalResponse, codeAgentConversationRunnerTraceEvidence, codeAgentFinalResponseEvidence, codeAgentFinalResponseText, codeAgentTraceSummaryEvidence, annotateOwner, canAccessOwnedResult, isCodeAgentResultCanceled, codeAgentCancelScopeMismatch, cancelExpectedValues, cancelScopeFieldMismatch, cancelBlockedPayload, isTraceCommandTerminalStatus, traceSnapshotWithTerminalEvidence, traceSnapshotWithAttachedTerminalEvidence, agentRunTerminalTraceEvidence, agentRunTerminalFinalResponse, traceRetentionSummary, numberOrNull, createCodeAgentChatResultStore, compactCodeAgentChatResultPayload, terminalEvidencePayload, compactRunnerTraceForResult, traceEventLabel, resultTraceEventLimit, createCodeAgentM3HwlabApiRequestJson, recordCodeAgentProjectionMetrics, recordCodeAgentTurnReadMetric, turnReadDegradedReason, responseBodyBytes, statusClassLabel, sourceLatestSeqFromResult, sourceLatestAtFromResult, terminalSourceAtFromResult, projectionReason, nowMs, uniqueStrings } from "./server-code-agent-http-support.ts"; const DEFAULT_CODE_AGENT_RESULT_TRACE_EVENT_LIMIT = 120; const DEFAULT_CODE_AGENT_TRACE_PAGE_LIMIT = 100; const MAX_CODE_AGENT_TRACE_PAGE_LIMIT = 100; const DEFAULT_CODE_AGENT_TURN_STATUS_REFRESH_TIMEOUT_MS = 2500; const DEFAULT_CODE_AGENT_PROJECT_ID = "prj_hwpod_workbench"; const DEFAULT_CODE_AGENT_PROVIDER_PROFILE = "deepseek"; const CODE_AGENT_TERMINAL_STATUSES = new Set(["completed", "failed", "blocked", "timeout", "cancelled", "canceled"]); const CODE_AGENT_PROVIDER_PROFILE_ALIASES = Object.freeze({ codex: "codex-api" }); const CODE_AGENT_PROVIDER_PROFILE_ID_PATTERN = /^[a-z0-9][a-z0-9.-]{0,63}$/u; const CODE_AGENT_PROVIDER_PROFILE_LABELS = Object.freeze({ "deepseek": "DeepSeek", "codex-api": "Codex API", "gpt.pika": "gpt.pika", "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 perf = options.backendPerformance; const body = perf ? await perf.measure("body_read", () => readBody(request, options.bodyLimitBytes)) : 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; } params = withMdtodoExecutionPrompt(params); const traceId = safeTraceId(getHeader(request, "x-trace-id") || params.traceId) || `trc_${randomUUID()}`; const lifecycle = codeAgentTurnLifecycleFields(traceId, params); const chatParams = { ...params, traceId, ...lifecycle, ownerUserId: options.actor?.id ?? params.ownerUserId, ownerRole: options.actor?.role ?? params.ownerRole }; const manualSession = perf ? await perf.measure("manual_session", () => prepareManualCodeAgentSession({ params: chatParams, options, traceId, response })) : await prepareManualCodeAgentSession({ params: chatParams, options, traceId, response }); if (manualSession?.blocked) return; const nativeSessionChatParams = stripSyntheticConversationContext(chatParams, traceId, options); if (codeAgentChatShortConnectionRequested(request, params, options)) { if (perf) perf.recordPhase({ phase: "short_connection_accept", durationMs: 0, outcome: "ok" }); let submitted = null; try { submitted = await submitCodeAgentChatTurn({ params: nativeSessionChatParams, options, traceId }); } catch (error) { if (isCodeAgentSubmissionUnavailableError(error)) { sendJson(response, error.statusCode ?? 503, error.payload); return; } throw error; } const traceUrl = `/v1/agent/traces/${encodeURIComponent(traceId)}`; const turnUrl = `/v1/agent/turns/${encodeURIComponent(traceId)}`; const workbenchEventsUrl = `/v1/workbench/events?sessionId=${encodeURIComponent(safeSessionId(nativeSessionChatParams.sessionId) || "")}&traceId=${encodeURIComponent(traceId)}`; const statusCode = 202; const admittedTiming = codeAgentAdmissionTimingFields(submitted?.createdAt ?? submitted?.updatedAt); void emitCodeAgentOtelSpan("POST /v1/agent/chat", traceId, options.env ?? process.env, { kind: 2, attributes: { "http.method": "POST", "http.route": "/v1/agent/chat", "http.status_code": statusCode, sessionId: safeSessionId(nativeSessionChatParams.sessionId) || null, turnId: lifecycle.turnId, ...agentChatOtelAttributes(nativeSessionChatParams) } }); sendJson(response, 202, { accepted: true, status: "running", shortConnection: true, controlSemantics: "submit-and-project-sse", traceId, ...codeAgentOtelTraceFields(traceId, options.env ?? process.env), ...lifecycle, sessionId: safeSessionId(nativeSessionChatParams.sessionId) || null, createdAt: submitted?.createdAt ?? admittedTiming.createdAt, updatedAt: submitted?.updatedAt ?? admittedTiming.updatedAt, timing: submitted?.timing ?? admittedTiming.timing, startedAt: submitted?.startedAt ?? admittedTiming.startedAt, lastEventAt: submitted?.lastEventAt ?? admittedTiming.lastEventAt, finishedAt: submitted?.finishedAt ?? admittedTiming.finishedAt, durationMs: submitted?.durationMs ?? admittedTiming.durationMs, realtimeCapabilities: workbenchRealtimeCapabilities(options.env ?? process.env), admission: submitted?.agentRun ? { state: submitted.admissionState ?? "promoted", runId: submitted.agentRun.runId ?? null, commandId: submitted.agentRun.commandId ?? null, dispatchIntentId: submitted.agentRun.dispatchIntentId ?? null, runnerJobId: submitted.agentRun.runnerJobId ?? null, durableDispatch: submitted.agentRun.durableDispatch === true, valuesRedacted: true } : null, traceUrl, turnUrl, workbenchEventsUrl, cancelUrl: "/v1/agent/chat/cancel", runnerTrace: (options.traceStore ?? defaultCodeAgentTraceStore).snapshot(traceId) }); return; } const admittedBase = codeAgentAdmittedFailureBasePayload({ params: nativeSessionChatParams, options, traceId }); try { await recordCodeAgentTurnAdmission({ payload: admittedBase, params: nativeSessionChatParams, options, traceId }); } catch (error) { if (isCodeAgentAdmissionUnavailableError(error)) { sendJson(response, error.statusCode ?? 503, error.payload); return; } throw error; } const billingPreflight = perf ? await perf.measure("billing_preflight", () => preflightCodeAgentBilling({ params: nativeSessionChatParams, options, traceId, response: null, sendResponse: false })) : await preflightCodeAgentBilling({ params: nativeSessionChatParams, options, traceId, response: null, sendResponse: false }); if (billingPreflight?.blocked) { const failed = await settleAdmittedCodeAgentFailure({ base: admittedBase, params: nativeSessionChatParams, options, traceId, results: options.codeAgentChatResults, failure: billingPreflight, traceLabel: "billing:preflight:failed" }); sendJson(response, billingPreflight.statusCode ?? 503, failed); return; } const nativeSessionChatParamsWithBilling = { ...nativeSessionChatParams, userBillingReservation: billingPreflight?.reservation ?? null }; const payload = perf ? await perf.measure("code_agent_run", () => runCodeAgentChat(nativeSessionChatParamsWithBilling, options)) : await runCodeAgentChat(nativeSessionChatParamsWithBilling, options); await (perf ? perf.measure("billing_finalize", () => finalizeCodeAgentBillingUsage({ payload, params: nativeSessionChatParamsWithBilling, options })) : finalizeCodeAgentBillingUsage({ payload, params: nativeSessionChatParamsWithBilling, options })); await (perf ? perf.measure("session_owner_record", () => recordCodeAgentSessionOwner({ payload, params: nativeSessionChatParamsWithBilling, options, status: codeAgentOwnerStatusForResult(payload) })) : recordCodeAgentSessionOwner({ payload, params: nativeSessionChatParamsWithBilling, options, status: codeAgentOwnerStatusForResult(payload) })); const responsePayload = annotateOwner(payload, nativeSessionChatParamsWithBilling); sendJson(response, responsePayload.status === "failed" && responsePayload.error?.code === "invalid_params" ? 400 : 200, responsePayload); } export async function handleCodeAgentSessionsHttp(request, response, url, options) { const match = url.pathname.match(/^\/v1\/agent\/sessions(?:\/([^/]+)(?:\/(select))?)?$/u); const sessionId = safeSessionId(match?.[1] ? decodeURIComponent(match[1]) : ""); const action = match?.[2] ?? null; if (!match) { sendJson(response, 404, manualSessionErrorPayload({ code: "session_route_not_found", message: "Code Agent session route is not implemented." })); return; } if (request.method === "GET" && !sessionId) { await listManualCodeAgentSessions(request, response, url, options); return; } if (request.method === "POST" && !sessionId) { await createManualCodeAgentSession(request, response, options); return; } if (request.method === "GET" && sessionId && !action) { await getManualCodeAgentSession(response, options, sessionId); return; } if (request.method === "DELETE" && sessionId && !action) { await archiveManualCodeAgentSession(response, options, sessionId); return; } if (request.method === "POST" && sessionId && action === "select") { await selectManualCodeAgentSession(request, response, options, sessionId); return; } sendJson(response, 405, manualSessionErrorPayload({ code: "session_method_not_allowed", message: "Unsupported Code Agent session method." })); } async function listManualCodeAgentSessions(request, response, url, options) { const projectId = textValue(url.searchParams.get("projectId")) || DEFAULT_CODE_AGENT_PROJECT_ID; const limit = parsePositiveInteger(url.searchParams.get("limit"), 20); const sessions = await options.accessController?.store?.listAgentSessionsForUser?.({ ownerUserId: options.actor?.id, actorRole: options.actor?.role, ownerScoped: true, projectId, limit }) ?? []; sendJson(response, 200, { ok: true, status: "succeeded", contractVersion: "code-agent-manual-session-v1", projectId, sessions: sessions.map(publicManualAgentSession), count: sessions.length, valuesRedacted: true, secretMaterialStored: false }); } async function createManualCodeAgentSession(request, response, options) { const body = await readJsonObjectBody(request, options.bodyLimitBytes); if (!body.ok) { sendJson(response, 400, manualSessionErrorPayload({ code: body.code, message: body.message, reason: body.reason })); return; } const params = body.value; const projectId = textValue(params.projectId) || DEFAULT_CODE_AGENT_PROJECT_ID; const conversationId = safeConversationId(params.conversationId) || `cnv_${randomUUID()}`; const sessionId = safeSessionId(params.sessionId) || `ses_${randomUUID()}`; const providerProfile = normalizeCodeAgentProviderProfile(params.providerProfile ?? params.codeAgentProviderProfile); const now = new Date().toISOString(); const session = await options.accessController.recordAgentSessionOwner({ ownerUserId: options.actor?.id, ownerRole: options.actor?.role, sessionId, projectId, agentId: "hwlab-code-agent", status: "idle", conversationId, threadId: null, traceId: null, session: { source: "manual-session-create", providerProfile, sessionStatus: "idle", createdAt: now, valuesRedacted: true, secretMaterialStored: false } }); await writeWorkbenchSessionAdmissionFact({ runtimeStore: options.runtimeStore, session, ownerUserId: options.actor?.id, ownerRole: options.actor?.role, projectId, conversationId, threadId: null, status: "idle", now }); sendJson(response, 201, { ok: true, status: "created", contractVersion: "code-agent-manual-session-v1", session: publicManualAgentSession(session), nextCommands: manualSessionNextCommands(session), valuesRedacted: true, secretMaterialStored: false }); } async function getManualCodeAgentSession(response, options, sessionId) { const session = await getVisibleManualAgentSession(options, sessionId); if (!session) { sendJson(response, 404, manualSessionErrorPayload({ code: "session_not_found", message: "Code Agent session is not visible to the current actor.", sessionId })); return; } sendJson(response, 200, { ok: true, status: "found", contractVersion: "code-agent-manual-session-v1", session: publicManualAgentSession(session), valuesRedacted: true, secretMaterialStored: false }); } async function archiveManualCodeAgentSession(response, options, sessionId) { const session = await getVisibleManualAgentSession(options, sessionId); if (!session) { sendJson(response, 404, manualSessionErrorPayload({ code: "session_not_found", message: "Code Agent session is not visible to the current actor.", sessionId })); return; } const result = await options.accessController?.store?.archiveAgentConversation?.({ ownerUserId: options.actor?.id, actorRole: options.actor?.role, ownerScoped: true, sessionId, conversationId: session.conversationId, projectId: session.projectId }) ?? { count: 0 }; sendJson(response, 200, { ok: true, status: "archived", contractVersion: "code-agent-manual-session-v1", archivedCount: result.count ?? 0, session: publicManualAgentSession({ ...session, status: "archived", endedAt: session.endedAt ?? new Date().toISOString(), updatedAt: new Date().toISOString() }), valuesRedacted: true, secretMaterialStored: false }); } async function selectManualCodeAgentSession(request, response, options, sessionId) { const body = await readJsonObjectBody(request, options.bodyLimitBytes); if (!body.ok) { sendJson(response, 400, manualSessionErrorPayload({ code: body.code, message: body.message, reason: body.reason, sessionId })); return; } const session = await getVisibleManualAgentSession(options, sessionId); if (!session) { sendJson(response, 404, manualSessionErrorPayload({ code: "session_not_found", message: "Code Agent session is not visible to the current actor.", sessionId })); return; } if (!manualSessionUsable(session.status)) { sendJson(response, 409, manualSessionErrorPayload({ code: "session_not_usable", message: `Code Agent session ${sessionId} is ${session.status}; create a new session before continuing.`, sessionId, session })); return; } sendJson(response, 200, { ok: true, status: "selected", contractVersion: "code-agent-manual-session-v1", session: publicManualAgentSession(session), nextCommands: manualSessionNextCommands(session), valuesRedacted: true, secretMaterialStored: false }); } async function prepareManualCodeAgentSession({ params, options, traceId, response }) { if (!options.actor?.id) { sendJson(response, 401, manualSessionErrorPayload({ code: "auth_required", message: "Authentication is required before using an explicit HWLAB Code Agent session.", traceId, retryable: true })); return { blocked: true }; } const sessionId = safeSessionId(params.sessionId); if (!sessionId) { sendJson(response, 409, manualSessionErrorPayload({ code: "session_required", message: "Code Agent session is explicit in HWLAB v0.2; create/select a session before sending a turn.", traceId, retryable: true, nextCommands: ["hwlab-cli client agent session create --provider-profile PROFILE", "hwlab-cli client agent send --session-id --message TEXT"] })); return { blocked: true }; } const session = await getVisibleManualAgentSession(options, sessionId); if (!session) { sendJson(response, 404, manualSessionErrorPayload({ code: "session_not_found", message: "Code Agent session is not visible to the current actor.", traceId, sessionId })); return { blocked: true }; } const freshContinuation = manualSessionFreshContinuationRequired(session); if (!manualSessionUsable(session.status) && !freshContinuation) { sendJson(response, 409, manualSessionErrorPayload({ code: "session_not_usable", message: `Code Agent session ${sessionId} is ${session.status}; create a new session before continuing.`, traceId, sessionId, session })); return { blocked: true }; } const requestedProjectId = textValue(params.projectId); const sessionProjectId = textValue(session.projectId); if (requestedProjectId && sessionProjectId && requestedProjectId !== sessionProjectId) { sendJson(response, 409, manualSessionErrorPayload({ code: "session_project_mismatch", message: `sessionId ${sessionId} belongs to ${sessionProjectId}; requested ${requestedProjectId}.`, traceId, sessionId, session })); return { blocked: true }; } const storedConversationId = safeConversationId(session.conversationId); const requestedConversationId = safeConversationId(params.conversationId); if (requestedConversationId && storedConversationId && requestedConversationId !== storedConversationId) { sendJson(response, 409, manualSessionErrorPayload({ code: "session_conversation_mismatch", message: `sessionId ${sessionId} is bound to ${storedConversationId}; requested ${requestedConversationId}.`, traceId, sessionId, session })); return { blocked: true }; } const conversationId = requestedConversationId || storedConversationId; if (!conversationId) { sendJson(response, 409, manualSessionErrorPayload({ code: "session_conversation_required", message: "Code Agent session has no bound conversationId; create a new session before continuing.", traceId, sessionId, session })); return { blocked: true }; } params.conversationId = conversationId; params.sessionId = sessionId; params.projectId = requestedProjectId || sessionProjectId || DEFAULT_CODE_AGENT_PROJECT_ID; applyManualSessionProviderProfile(params, session); if (freshContinuation) { params.threadId = undefined; params.forceFreshAgentRunSession = true; params.forceFreshAgentRunSessionReason = manualSessionFreshContinuationReason(session); } else { params.threadId = safeOpaqueId(params.threadId) || manualSessionContinuationThreadId(session) || undefined; } return { session }; } function applyManualSessionProviderProfile(params = {}, session = null) { const requested = firstNonEmptyValue(params.providerProfile, params.codeAgentProviderProfile); if (requested) { const normalized = normalizeCodeAgentProviderProfile(requested); params.providerProfile = normalized; params.codeAgentProviderProfile = normalized; params.providerProfileSource = "request"; return normalized; } const stored = manualSessionStoredProviderProfile(session); if (!stored) return null; params.providerProfile = stored; params.codeAgentProviderProfile = stored; params.providerProfileSource = "session"; return stored; } function manualSessionStoredProviderProfile(session = null) { const evidence = session?.session && typeof session.session === "object" ? session.session : {}; const traceProfile = latestCompletedTraceResultProviderProfile(evidence.traceResults); const raw = firstNonEmptyValue( traceProfile, evidence.agentRun?.backendProfile, evidence.providerProfile, evidence.codeAgentProviderProfile, evidence.backendProfile ); if (!raw) return null; const normalized = normalizeCodeAgentProviderProfile(raw); return normalized === "runtime-default" ? null : normalized; } function latestCompletedTraceResultProviderProfile(traceResults = null) { if (!traceResults || typeof traceResults !== "object") return null; const entries = Object.values(traceResults).filter((value) => value && typeof value === "object"); for (const result of entries.reverse()) { const agentRun = result.agentRun && typeof result.agentRun === "object" ? result.agentRun : null; if (!agentRun) continue; const status = normalizeTurnStatus(result.status ?? agentRun.terminalStatus ?? agentRun.commandState ?? agentRun.status); if (status && status !== "completed") continue; const profile = firstNonEmptyValue(agentRun.backendProfile, result.providerProfile, result.codeAgentProviderProfile); if (profile) return profile; } return null; } function manualSessionFreshContinuationRequired(session = null) { const status = String(session?.status ?? "").trim().toLowerCase().replace(/_/gu, "-"); if (status === "thread-resume-failed") return true; const continuation = session?.session?.continuation && typeof session.session.continuation === "object" ? session.session.continuation : null; return continuation?.requiresFreshSession === true; } function manualSessionFreshContinuationReason(session = null) { const continuation = session?.session?.continuation && typeof session.session.continuation === "object" ? session.session.continuation : null; return firstNonEmptyValue(continuation?.reasonCode, continuation?.reason, session?.status, "stale-continuation") ?? "stale-continuation"; } function manualSessionContinuationThreadId(session = null) { if (manualSessionFreshContinuationRequired(session)) return null; const evidence = session?.session && typeof session.session === "object" ? session.session : {}; return safeOpaqueId(evidence.threadId) || safeOpaqueId(evidence.agentRun?.threadId) || safeOpaqueId(session?.threadId) || null; } async function getVisibleManualAgentSession(options, sessionId) { if (!safeSessionId(sessionId) || !options.actor?.id || !options.accessController?.getAgentSession) return null; const session = await options.accessController.getAgentSession(sessionId); if (!session) return null; if (session.ownerUserId === options.actor.id) return session; return null; } function publicManualAgentSession(session) { if (!session || typeof session !== "object") return null; return { sessionId: session.id, agentId: session.agentId ?? "hwlab-code-agent", status: session.status ?? null, usable: manualSessionUsable(session.status), threadId: safeOpaqueId(session.threadId) || null, lastTraceId: safeTraceId(session.lastTraceId) || null, providerProfile: textValue(session.session?.providerProfile) || null, createdAt: session.startedAt ?? null, updatedAt: session.updatedAt ?? null, valuesRedacted: true, secretMaterialStored: false }; } function manualSessionUsable(status) { const value = String(status ?? "").trim().toLowerCase().replace(/_/gu, "-"); return !["blocked", "expired", "stale"].includes(value); } function manualSessionErrorPayload({ code, message, reason = null, traceId = null, sessionId = null, session = null, retryable = true, nextCommands = undefined } = {}) { return { ok: false, accepted: false, status: "blocked", traceId: safeTraceId(traceId) || null, sessionId: safeSessionId(sessionId) || safeSessionId(session?.id) || null, session: session ? publicManualAgentSession(session) : null, error: { code, layer: "api", category: "session_blocked", retryable, message, reason, userMessage: message, route: "/v1/agent/chat" }, blocker: { code, layer: "api", retryable, summary: message }, nextCommands, valuesRedacted: true, secretMaterialStored: false }; } function manualSessionNextCommands(session) { const sessionId = session?.id ?? ""; return [ `hwlab-cli client agent send --session-id ${sessionId} --message TEXT`, `hwlab-cli client agent session status ${sessionId}` ]; } async function readJsonObjectBody(request, bodyLimitBytes) { const body = await readBody(request, bodyLimitBytes); try { const value = body ? JSON.parse(body) : {}; if (!value || typeof value !== "object" || Array.isArray(value)) return { ok: false, code: "invalid_params", message: "body must be a JSON object" }; return { ok: true, value }; } catch (error) { return { ok: false, code: "parse_error", message: "Invalid JSON body", reason: error.message }; } } async function runCodeAgentChat(params, options) { return handleCodeAgentChat(params, await codeAgentChatExecutionOptions(options, params)); } function stripSyntheticConversationContext(params = {}, traceId, options = {}) { if (Array.isArray(params.conversationContext?.messages) || Array.isArray(params.messages)) { (options.traceStore ?? defaultCodeAgentTraceStore).append(traceId, { type: "synthetic-context", status: "ignored", label: "synthetic-context:ignored", message: "Synthetic conversation context was stripped; Code Agent continuation uses only Codex stdio native thread/resume and turn/start.", valuesPrinted: false }); } const { conversationContext, conversationContextTruncated, contextSource, messages, ...nativeParams } = params; return nativeParams; } async function codeAgentChatExecutionOptions(options = {}, params = {}) { const env = codeAgentProviderProfileEnv(options.env ?? process.env, params); Object.assign(env, await codeAgentAuthEnv(options, env, params)); 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 codeAgentAuthEnv(options = {}, env = process.env, params = {}) { const ownerUserId = textValue(params.ownerUserId ?? options.actor?.id ?? ""); const store = options.accessController?.store; const key = ownerUserId && store?.findActiveDefaultApiKeyForUser ? await store.findActiveDefaultApiKeyForUser(ownerUserId) : null; const envRuntimeLane = runtimeLaneForEnv(env); const runtimeNamespace = runtimeNamespaceForEnv(env); const namespaceRuntimeLane = runtimeLaneForNamespace(runtimeNamespace); const runtimeLane = resolveRuntimeLaneAuthority({ envRuntimeLane, runtimeNamespace, namespaceRuntimeLane }); const inClusterApiUrl = internalCodeAgentApiUrl(env, runtimeNamespace); const inClusterWebUrl = internalCodeAgentWebUrl(env, runtimeNamespace); const apiUrl = firstNonEmptyValue( codeAgentAgentRunAdapterEnabled(env) ? inClusterApiUrl : null, env.HWLAB_RUNTIME_API_URL, sameRuntimeEndpoint(env.HWLAB_CLOUD_API_URL, runtimeNamespace, runtimeLane), env.HWLAB_PUBLIC_ENDPOINT, localCloudApiUrl(env) ); return { HWLAB_CLOUD_API_URL: apiUrl, HWLAB_RUNTIME_API_URL: apiUrl, HWLAB_RUNTIME_WEB_URL: firstNonEmptyValue(codeAgentAgentRunAdapterEnabled(env) ? inClusterWebUrl : null, env.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", ...(options.actorApiKeySecret ? { HWLAB_API_KEY: options.actorApiKeySecret } : key?.displaySecret ? { HWLAB_API_KEY: key.displaySecret } : {}) }; } function internalCodeAgentApiUrl(env = process.env, namespace = runtimeNamespaceForEnv(env)) { const serviceName = firstNonEmptyValue(env.HWLAB_CODE_AGENT_INTERNAL_API_SERVICE_NAME, env.HWLAB_CLOUD_API_SERVICE_NAME, "hwlab-cloud-api"); const port = parsePositiveInteger(firstNonEmptyValue(env.HWLAB_CODE_AGENT_INTERNAL_API_PORT, env.HWLAB_CLOUD_API_PORT), 6667); return `http://${serviceName}.${namespace}.svc.cluster.local:${port}`; } function internalCodeAgentWebUrl(env = process.env, namespace = runtimeNamespaceForEnv(env)) { const serviceName = firstNonEmptyValue(env.HWLAB_CODE_AGENT_INTERNAL_WEB_SERVICE_NAME, env.HWLAB_CLOUD_WEB_SERVICE_NAME, "hwlab-cloud-web"); const port = parsePositiveInteger(firstNonEmptyValue(env.HWLAB_CODE_AGENT_INTERNAL_WEB_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 raw = firstNonEmptyValue(env.HWLAB_RUNTIME_LANE, env.HWLAB_GITOPS_LANE, env.HWLAB_GITOPS_PROFILE, env.HWLAB_ENVIRONMENT, env.HWLAB_BOOT_REF); if (!raw) return null; const lane = normalizeRuntimeLane(raw); if (!lane) { throw runtimeAuthorityError("unsupported_runtime_lane", `unsupported HWLAB runtime lane: ${String(raw).trim()}`); } return lane; } function runtimeLaneForNamespace(namespace) { const value = String(namespace ?? "").trim().toLowerCase(); if (value === "hwlab-prod") return "prod"; if (value === "hwlab-dev") return "dev"; const versionMatch = value.match(/^hwlab-v(\d+)$/u); if (versionMatch) return versionRuntimeLane(versionMatch[1]); 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(); const namespaceLane = runtimeLaneForNamespace(runtimeNamespaceForEndpoint(text)); if (namespaceLane) return namespaceLane; 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 normalizeRuntimeLane(value) { const profile = String(value ?? "").trim().toLowerCase(); if (!profile) return null; if (profile === "prod" || profile === "production") return "prod"; if (profile === "dev" || profile === "development" || profile === "local") return "dev"; const dotted = profile.match(/^v?0+\.(\d+)$/u); if (dotted) return versionRuntimeLane(dotted[1]); const compact = profile.match(/^v?0*(\d+)$/u); if (compact) return versionRuntimeLane(compact[1]); return null; } function versionRuntimeLane(value) { const version = Number.parseInt(value, 10); if (!Number.isFinite(version) || version <= 0) return null; return `v${String(version).padStart(2, "0")}`; } function resolveRuntimeLaneAuthority({ envRuntimeLane, runtimeNamespace, namespaceRuntimeLane }) { if (!namespaceRuntimeLane) { throw runtimeAuthorityError("unsupported_runtime_namespace", `unsupported HWLAB runtime namespace: ${runtimeNamespace}`); } if (envRuntimeLane && envRuntimeLane !== namespaceRuntimeLane) { throw runtimeAuthorityError( "runtime_authority_mismatch", `HWLAB runtime lane ${envRuntimeLane} does not match namespace ${runtimeNamespace} (${namespaceRuntimeLane})` ); } return namespaceRuntimeLane; } function runtimeAuthorityError(code, message) { return Object.assign(new Error(message), { code, statusCode: 500, valuesPrinted: false }); } function defaultRuntimeNamespace(env = process.env) { const profile = runtimeLaneForEnv(env) ?? "dev"; if (profile === "prod") return "hwlab-prod"; if (profile === "dev") return "hwlab-dev"; return `hwlab-${profile}`; } 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 ?? params.provider ?? 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 { const dynamicProfile = profile !== "deepseek"; overlay.HWLAB_CODE_AGENT_MODEL = dynamicProfile ? firstNonEmptyValue(env[`HWLAB_CODE_AGENT_${providerProfileEnvKey(profile)}_MODEL`], profile) : firstNonEmptyValue(env.HWLAB_CODE_AGENT_DEEPSEEK_MODEL, DEFAULT_CODE_AGENT_DEEPSEEK_MODEL); if (!dynamicProfile || !codeAgentAgentRunAdapterEnabled(env)) { 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 providerProfileEnvKey(profile) { return String(profile ?? "") .trim() .toUpperCase() .replace(/[^A-Z0-9]+/gu, "_") .replace(/^_+|_+$/gu, "") || "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 safeOtelConfigText(value) { const text = firstNonEmptyValue(value); return text === null ? null : text.slice(0, 120); } function safeOtelUrlHost(value) { const text = firstNonEmptyValue(value); if (text === null) return null; try { return new URL(text).host.slice(0, 120); } catch { return null; } } function withMdtodoExecutionPrompt(params = {}) { const execution = mdtodoExecutionContextFromParams(params); if (!execution?.hwpodId || !execution?.hwpodWorkspaceArgs) return params; const message = firstNonEmptyValue(params.message, params.prompt, params.text); if (!message || /hwpodWorkspaceArgs\s*:/u.test(message)) return params; const taskRef = firstNonEmptyValue(params.taskRef, execution.taskRef) ?? "-"; const mdtodoRootRef = execution.mdtodoRootRef ?? "docs/MDTODO"; const prefix = [ "# MDTODO HWPOD 执行上下文", `TaskRef: ${taskRef}`, `sourceId: ${execution.sourceId ?? "-"}`, `hwpodId: ${execution.hwpodId}`, `nodeId: ${execution.nodeId ?? "-"}`, `mdtodoRootRef: ${mdtodoRootRef}`, `hwpodWorkspaceArgs: ${execution.hwpodWorkspaceArgs}`, "所有 hwpod/hwpod-ctl 命令都必须携带上述 hwpodWorkspaceArgs。读取 MDTODO、报告和工程文件必须通过 HWPOD workspace/node-ops 入口,不得猜测或访问普通容器本地路径,也不得创建本地 .hwlab/hwpod-spec.yaml fallback。" ].join("\n"); const nextMessage = `${prefix}\n\n${message}`; return { ...params, message: nextMessage, prompt: nextMessage, launchContext: { ...(params.launchContext && typeof params.launchContext === "object" ? params.launchContext : {}), hwpodId: execution.hwpodId, nodeId: execution.nodeId, mdtodoRootRef: execution.mdtodoRootRef, hwpodWorkspaceArgs: execution.hwpodWorkspaceArgs, contextFingerprint: execution.contextFingerprint, executionContext: execution, valuesRedacted: true } }; } function mdtodoExecutionContextFromParams(params = {}) { const launchContext = params.launchContext && typeof params.launchContext === "object" && !Array.isArray(params.launchContext) ? params.launchContext : null; const execution = launchContext?.executionContext && typeof launchContext.executionContext === "object" && !Array.isArray(launchContext.executionContext) ? launchContext.executionContext : launchContext; if (!execution || typeof execution !== "object") return null; return { sourceId: safeOtelConfigText(execution.sourceId ?? launchContext?.sourceId), sourceKind: safeOtelConfigText(execution.sourceKind ?? launchContext?.sourceKind), projectId: safeOtelConfigText(execution.projectId ?? launchContext?.projectId ?? params.projectId), taskRef: safeOtelText(execution.taskRef ?? launchContext?.taskRef ?? params.taskRef), fileRef: safeOtelConfigText(execution.fileRef ?? launchContext?.fileRef), relativePath: safeOtelText(execution.relativePath ?? launchContext?.relativePath), taskId: safeOtelConfigText(execution.taskId ?? launchContext?.taskId), hwpodId: safeOtelText(execution.hwpodId ?? launchContext?.hwpodId), nodeId: safeOtelText(execution.nodeId ?? launchContext?.nodeId), mdtodoRootRef: safeOtelText(execution.mdtodoRootRef ?? launchContext?.mdtodoRootRef), workspaceRootHash: safeOtelText(execution.workspaceRootHash ?? launchContext?.workspaceRootHash), hwpodWorkspaceArgs: safeOtelText(execution.hwpodWorkspaceArgs ?? launchContext?.hwpodWorkspaceArgs, 500), contextFingerprint: safeOtelText(execution.contextFingerprint ?? launchContext?.contextFingerprint), valuesRedacted: true }; } function agentChatOtelAttributes(params = {}) { const execution = mdtodoExecutionContextFromParams(params); return { "agent.chat.session_id": safeSessionId(params.sessionId) || null, "agent.chat.provider_profile": safeOtelConfigText(params.providerProfile ?? params.codeAgentProviderProfile), "agent.chat.provider_profile_source": safeOtelConfigText(params.providerProfileSource), "agent.chat.task_ref": safeOtelText(params.taskRef ?? execution?.taskRef), "agent.chat.source_id": safeOtelConfigText(execution?.sourceId ?? params.sourceId), "agent.chat.hwpod_id": safeOtelText(execution?.hwpodId), "agent.chat.workspace_root_hash": safeOtelText(execution?.workspaceRootHash), "agent.chat.context_fingerprint": safeOtelText(execution?.contextFingerprint) }; } function safeOtelText(value, max = 360) { const text = firstNonEmptyValue(value); return text === null ? null : text.slice(0, max); } async function submitCodeAgentChatTurn({ params, options, traceId }) { const traceStore = options.traceStore ?? defaultCodeAgentTraceStore; const results = options.codeAgentChatResults ?? createCodeAgentChatResultStore(); const timestamp = new Date().toISOString(); const runtimeEnv = options.env ?? process.env; const adapterEnabled = codeAgentAgentRunAdapterEnabled(runtimeEnv); const acceptedPayload = { accepted: true, status: "running", traceId, ...codeAgentOtelTraceFields(traceId, process.env), ...codeAgentTurnLifecycleFields(traceId, params), conversationId: safeConversationId(params.conversationId) || null, sessionId: safeSessionId(params.sessionId) || null, threadId: safeOpaqueId(params.threadId) || null, projectId: params.projectId ?? null, ...codeAgentAdmissionTimingFields(timestamp) }; void emitCodeAgentOtelSpan("provider_decision", traceId, runtimeEnv, { attributes: { ...agentChatOtelAttributes(params), adapterEnabled, adapter: safeOtelConfigText(runtimeEnv.HWLAB_CODE_AGENT_ADAPTER ?? runtimeEnv.HWLAB_CODE_AGENT_PROVIDER), provider: safeOtelConfigText(runtimeEnv.HWLAB_CODE_AGENT_PROVIDER), defaultProviderProfile: safeOtelConfigText(runtimeEnv.HWLAB_CODE_AGENT_DEFAULT_PROVIDER_PROFILE), agentRunManagerHost: safeOtelUrlHost(runtimeEnv.AGENTRUN_MGR_URL ?? runtimeEnv.HWLAB_CODE_AGENT_AGENTRUN_MGR_URL), agentRunRunnerNamespace: safeOtelConfigText(runtimeEnv.HWLAB_CODE_AGENT_AGENTRUN_RUNNER_NAMESPACE ?? runtimeEnv.AGENTRUN_RUNTIME_NAMESPACE), agentRunSourceCommitPresent: Boolean(firstNonEmptyValue(runtimeEnv.HWLAB_CODE_AGENT_AGENTRUN_SOURCE_COMMIT)) } }); if (adapterEnabled) { const realtimeCapabilities = workbenchRealtimeCapabilities(runtimeEnv); const transactionalAdmission = realtimeCapabilities.transactionalProjector; const pendingPromotion = results.get(traceId); if (pendingPromotion?.admissionState === "promotion-pending" && pendingPromotion?.agentRun?.durableDispatch === true) { const retryParams = { ...params, userBillingReservation: pendingPromotion.userBillingReservation ?? null }; try { await promoteCodeAgentTurnAdmission({ payload: pendingPromotion, params: retryParams, options, traceId }); } catch (error) { throw codeAgentPromotionUnavailableError(error, { payload: pendingPromotion, params: retryParams, options, traceId }); } const promoted = annotateOwner({ ...pendingPromotion, accepted: true, status: "running", admissionState: "promoted" }, retryParams); results.set(traceId, promoted); return promoted; } const lifecycle = codeAgentTurnLifecycleFields(traceId, params); if (transactionalAdmission) { await persistCodeAgentAdmissionInputState({ params, options, traceId, status: "admitting", messageId: lifecycle.userMessageId }); } traceStore.append(traceId, { type: "request", status: "admitting", label: "turn:admitting", message: "Code Agent turn is validating billing and durable AgentRun dispatch before UI admission.", terminal: false, waitingFor: "billing-and-durable-dispatch", valuesPrinted: false }); const initial = initialAgentRunChatResult({ params, options, traceId }); const billingStartedAt = Date.now(); const billingPreflight = await preflightCodeAgentBilling({ params, options, traceId, sendResponse: false }); void emitCodeAgentOtelSpan("billing_preflight", traceId, options.env ?? process.env, { startTimeMs: billingStartedAt, attributes: { sessionId: safeSessionId(params.sessionId) || null, turnId: codeAgentTurnLifecycleFields(traceId, params).turnId, blocked: Boolean(billingPreflight?.blocked) }, status: billingPreflight?.blocked ? "error" : "ok", error: billingPreflight?.blocked ? billingPreflight?.payload?.error?.message ?? billingPreflight?.blocker?.summary ?? "billing preflight blocked" : null }); if (billingPreflight?.blocked) { if (transactionalAdmission) { await persistCodeAgentAdmissionInputState({ params, options, traceId, status: "blocked", messageId: lifecycle.userMessageId, error: billingPreflight.payload?.error ?? billingPreflight.blocker ?? { code: "billing_preflight_blocked" } }); } traceStore.append(traceId, { type: "billing", status: "blocked", label: "billing:preflight:blocked-before-admission", errorCode: billingPreflight.payload?.error?.code ?? billingPreflight.blocker?.code ?? "billing_preflight_blocked", terminal: false, valuesPrinted: false }); throw codeAgentSubmissionUnavailableError(billingPreflight, { params, options, traceId }); } const dispatchParams = { ...params, userBillingReservation: billingPreflight?.reservation ?? null }; const executionOptions = { ...options, ...(await codeAgentChatExecutionOptions(options, dispatchParams)) }; const dispatchStartedAt = Date.now(); let payload; try { payload = await submitAgentRunChatTurn({ params: dispatchParams, options: executionOptions, traceId, traceStore, results }); } catch (error) { const failure = codeAgentDispatchFailure(error); const rejected = codeAgentSubmissionUnavailablePayload(failure, { params: dispatchParams, options: executionOptions, traceId }); await finalizeCodeAgentBillingUsage({ payload: rejected, params: dispatchParams, options: executionOptions }); if (transactionalAdmission) { await persistCodeAgentAdmissionInputState({ params: dispatchParams, options: executionOptions, traceId, status: "failed", messageId: lifecycle.userMessageId, error: failure.payload?.error ?? error }); } void emitCodeAgentOtelSpan("agentrun_dispatch", traceId, executionOptions.env ?? process.env, { startTimeMs: dispatchStartedAt, status: "error", error, attributes: { sessionId: safeSessionId(dispatchParams.sessionId) || null, turnId: codeAgentTurnLifecycleFields(traceId, dispatchParams).turnId, ...codeAgentDispatchErrorOtelAttributes(error) } }); throw codeAgentSubmissionUnavailableError(failure, { params: dispatchParams, options: executionOptions, traceId }); } void emitCodeAgentOtelSpan("agentrun_dispatch", traceId, executionOptions.env ?? process.env, { startTimeMs: dispatchStartedAt, attributes: { sessionId: safeSessionId(dispatchParams.sessionId) || null, turnId: codeAgentTurnLifecycleFields(traceId, dispatchParams).turnId, runId: payload?.agentRun?.runId ?? null, commandId: payload?.agentRun?.commandId ?? null, providerProfile: dispatchParams.providerProfile ?? null } }); const pending = annotateOwner({ ...withCodeAgentBillingReservation(payload, dispatchParams), accepted: false, status: "admitting", admissionState: "promotion-pending" }, dispatchParams); results.set(traceId, pending); try { if (transactionalAdmission) { await persistCodeAgentAdmissionInputState({ params: dispatchParams, options: executionOptions, traceId, status: "admitting", messageId: lifecycle.userMessageId, commandId: payload.agentRun?.commandId ?? null, agentRun: payload.agentRun ?? null }); } await promoteCodeAgentTurnAdmission({ payload: pending, params: dispatchParams, options: executionOptions, traceId }); } catch (error) { throw codeAgentPromotionUnavailableError(error, { payload: pending, params: dispatchParams, options: executionOptions, traceId }); } const promoted = annotateOwner({ ...pending, accepted: true, status: "running", admissionState: "promoted" }, dispatchParams); results.set(traceId, promoted); return promoted; } await recordCodeAgentTurnAdmission({ payload: acceptedPayload, params, options, traceId }); results.set(traceId, annotateOwner(acceptedPayload, params)); 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; committed progress is delivered through the Workbench projection event stream.", waitingFor: "codex-stdio" }); const run = async () => { try { const billingStartedAt = Date.now(); const billingPreflight = await preflightCodeAgentBilling({ params, options, traceId, sendResponse: false }); void emitCodeAgentOtelSpan("billing_preflight", traceId, options.env ?? process.env, { startTimeMs: billingStartedAt, attributes: { sessionId: safeSessionId(params.sessionId) || null, turnId: codeAgentTurnLifecycleFields(traceId, params).turnId, blocked: Boolean(billingPreflight?.blocked) }, status: billingPreflight?.blocked ? "error" : "ok", error: billingPreflight?.blocked ? billingPreflight?.payload?.error?.message ?? billingPreflight?.blocker?.summary ?? "billing preflight blocked" : null }); if (billingPreflight?.blocked) { await settleAdmittedCodeAgentFailure({ base: codeAgentAdmittedFailureBasePayload({ params, options, traceId }), params, options, traceId, results, failure: billingPreflight, traceLabel: "billing:preflight:failed" }); return; } const dispatchParams = { ...params, userBillingReservation: billingPreflight?.reservation ?? null }; const payload = await runCodeAgentChat(dispatchParams, options); if (isCodeAgentResultCanceled(results.get(traceId))) return; await finalizeCodeAgentBillingUsage({ payload, params: dispatchParams, options }); await recordCodeAgentSessionOwner({ payload, params: dispatchParams, options, status: codeAgentOwnerStatusForResult(payload) }); results.set(traceId, annotateOwner(payload, dispatchParams)); traceStore.append(traceId, { type: "result", status: payload.status === "completed" ? "completed" : "failed", label: `result:${payload.status}`, message: payload.status === "completed" ? "Code Agent result is committed to the Workbench projection event stream." : 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 后台任务失败;错误已提交到 Workbench 实时投影。" }, updatedAt: new Date().toISOString() }; await finalizeCodeAgentBillingUsage({ payload, params, options }); 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(); }); return annotateOwner(acceptedPayload, params); } async function recordCodeAgentTurnAdmission({ payload = {}, params = {}, options = {}, traceId } = {}) { const traceStore = options.traceStore ?? defaultCodeAgentTraceStore; let ownerRecord = null; try { ownerRecord = await recordCodeAgentSessionOwner({ payload, params, options, status: "running" }); if (!ownerRecord) { const error = new Error("Code Agent turn admission did not durably persist Workbench session ownership."); error.code = "workbench_admission_owner_not_persisted"; error.valuesRedacted = true; throw error; } await writeWorkbenchSessionAdmissionFact({ runtimeStore: options.runtimeStore, session: ownerRecord, ownerUserId: options.actor?.id ?? params.ownerUserId, ownerRole: options.actor?.role ?? params.ownerRole, projectId: params.projectId ?? ownerRecord.projectId ?? null, conversationId: params.conversationId ?? ownerRecord.conversationId ?? null, threadId: params.threadId ?? ownerRecord.threadId ?? null, status: "running" }); await recordCodeAgentSessionInputFact({ params, options, traceId, delivery: "queue", status: "admitted", messageId: codeAgentTurnLifecycleFields(traceId, params).userMessageId, commandId: payload.agentRun?.commandId ?? null }); } catch (error) { throw codeAgentAdmissionUnavailableError(error, { params, options, traceId }); } traceStore.append(traceId, { type: "request", status: "admitted", label: "turn:admitted", message: "Code Agent turn was durably admitted before billing or dispatch preflight.", sessionId: safeSessionId(params.sessionId) || null, threadId: safeOpaqueId(params.threadId) || null, ...codeAgentOtelTraceFields(traceId, options.env ?? process.env), waitingFor: "billing-or-dispatch", valuesPrinted: false }); void emitCodeAgentOtelSpan("durable_admission", traceId, options.env ?? process.env, { attributes: { sessionId: safeSessionId(params.sessionId) || null, threadId: safeOpaqueId(params.threadId) || null, turnId: codeAgentTurnLifecycleFields(traceId, params).turnId } }); return ownerRecord; } async function promoteCodeAgentTurnAdmission({ payload = {}, params = {}, options = {}, traceId } = {}) { const traceStore = options.traceStore ?? defaultCodeAgentTraceStore; const ownerRecord = await recordCodeAgentSessionOwner({ payload, params, options, status: "admitting" }); if (!ownerRecord) { const error = new Error("Code Agent turn promotion did not durably persist the admitting owner record."); error.code = "workbench_admission_owner_not_persisted"; throw error; } const lifecycle = codeAgentTurnLifecycleFields(traceId, params); const realtimeCapabilities = workbenchRealtimeCapabilities(options.env ?? process.env); if (!realtimeCapabilities.transactionalProjector) { const runningOwner = await recordCodeAgentSessionOwner({ payload: { ...payload, status: "running" }, params, options, status: "running" }); if (!runningOwner) { const error = new Error("Code Agent live turn did not durably promote the session owner to running."); error.code = "workbench_admission_owner_not_promoted"; error.valuesRedacted = true; throw error; } traceStore.append(traceId, { type: "request", status: "admitted", label: "turn:admitted-live-kafka", message: "Code Agent turn is live after AgentRun durable dispatch; lifecycle visibility is owned only by hwlab.event.v1.", sessionId: safeSessionId(params.sessionId) || null, runId: payload.agentRun?.runId ?? null, commandId: payload.agentRun?.commandId ?? null, dispatchIntentId: payload.agentRun?.dispatchIntentId ?? null, waitingFor: "hwlab-live-kafka-sse", terminal: false, valuesPrinted: false }); return { written: false, promoted: true, outboxCommitted: false, realtimeCapabilities, liveOnly: realtimeCapabilities.liveKafkaSse, valuesPrinted: false }; } const inputFact = buildCodeAgentSessionInputFact({ params, options, traceId, delivery: "queue", status: "promoted", messageId: lifecycle.userMessageId, commandId: payload.agentRun?.commandId ?? null, agentRun: payload.agentRun ?? null }); const promotion = await persistWorkbenchTurnAdmissionPromotion({ runtimeStore: options.runtimeStore, session: ownerRecord, inputFact, payload, params, ownerUserId: options.actor?.id ?? params.ownerUserId, ownerRole: options.actor?.role ?? params.ownerRole, projectId: params.projectId ?? ownerRecord.projectId ?? null, conversationId: params.conversationId ?? ownerRecord.conversationId ?? null, threadId: payload.agentRun?.threadId ?? params.threadId ?? ownerRecord.threadId ?? null }); const runningOwner = await recordCodeAgentSessionOwner({ payload: { ...payload, status: "running" }, params, options, status: "running" }); if (!runningOwner) { const error = new Error("Code Agent turn promotion did not durably promote the session owner to running."); error.code = "workbench_admission_owner_not_promoted"; error.valuesRedacted = true; throw error; } traceStore.append(traceId, { type: "request", status: "admitted", label: "turn:admitted", message: "Code Agent turn was promoted only after billing and durable AgentRun dispatch admission committed.", sessionId: safeSessionId(params.sessionId) || null, threadId: safeOpaqueId(payload.agentRun?.threadId ?? params.threadId) || null, runId: payload.agentRun?.runId ?? null, commandId: payload.agentRun?.commandId ?? null, dispatchIntentId: payload.agentRun?.dispatchIntentId ?? null, runnerJobId: payload.agentRun?.runnerJobId ?? null, waitingFor: "agentrun-kafka-projection", terminal: false, valuesPrinted: false }); void emitCodeAgentOtelSpan("durable_admission", traceId, options.env ?? process.env, { attributes: { sessionId: safeSessionId(params.sessionId) || null, threadId: safeOpaqueId(payload.agentRun?.threadId ?? params.threadId) || null, turnId: lifecycle.turnId, runId: payload.agentRun?.runId ?? null, commandId: payload.agentRun?.commandId ?? null, dispatchIntentId: payload.agentRun?.dispatchIntentId ?? null, outboxCommitted: promotion?.outboxCommitted === true } }); return promotion; } async function persistCodeAgentAdmissionInputState({ params = {}, options = {}, traceId, status, messageId = null, commandId = null, agentRun = null, error = null } = {}) { try { const written = await recordCodeAgentSessionInputFact({ params, options, traceId, delivery: "queue", status, messageId, commandId, agentRun, error }); if (written?.written !== true) { const unavailable = new Error("Code Agent admission input state was not durably persisted."); unavailable.code = "workbench_admission_input_not_persisted"; unavailable.valuesRedacted = true; throw unavailable; } return written; } catch (cause) { throw codeAgentAdmissionUnavailableError(cause, { params, options, traceId }); } } function codeAgentAdmissionUnavailableError(error, { params = {}, options = {}, traceId } = {}) { const payload = codeAgentAdmissionUnavailablePayload({ error, params, options, traceId }); return Object.assign(new Error(payload.error.message), { code: "admission_unavailable", statusCode: 503, payload, alreadyClassified: true }); } function isCodeAgentAdmissionUnavailableError(error) { return error?.code === "admission_unavailable" && error?.payload; } function isCodeAgentSubmissionUnavailableError(error) { return Boolean(error?.payload && Number.isInteger(error?.statusCode) && ( isCodeAgentAdmissionUnavailableError(error) || error.code === "submission_unavailable" || error.code === "admission_promotion_unavailable" )); } function codeAgentSubmissionUnavailableError(failure = {}, context = {}) { const payload = codeAgentSubmissionUnavailablePayload(failure, context); return Object.assign(new Error(payload.error.message), { code: "submission_unavailable", statusCode: failure.statusCode ?? 503, payload, alreadyClassified: true }); } function codeAgentSubmissionUnavailablePayload(failure = {}, { params = {}, options = {}, traceId } = {}) { const source = failure.payload ?? failure; const error = source.error ?? {}; const blocker = source.blocker ?? null; const message = error.message ?? blocker?.summary ?? "Code Agent submission failed before durable admission."; return { ...source, ok: false, accepted: false, status: "failed", traceId: safeTraceId(traceId) || null, ...codeAgentOtelTraceFields(traceId, options.env ?? process.env), ...codeAgentTurnLifecycleFields(traceId, params), conversationId: safeConversationId(params.conversationId) || null, sessionId: safeSessionId(params.sessionId) || null, threadId: safeOpaqueId(params.threadId) || null, error: { ...error, code: error.code ?? blocker?.code ?? "submission_unavailable", layer: error.layer ?? blocker?.layer ?? "admission", retryable: error.retryable !== false, message, route: error.route ?? "/v1/agent/chat" }, blocker: blocker ? { ...blocker, summary: blocker.summary ?? message } : null, valuesRedacted: true, secretMaterialStored: false }; } function codeAgentPromotionUnavailableError(error, { payload = {}, params = {}, options = {}, traceId } = {}) { const message = error?.message ?? "Local Workbench admission promotion failed after AgentRun durable dispatch."; const responsePayload = codeAgentSubmissionUnavailablePayload({ error: { code: "admission_promotion_unavailable", layer: "admission", retryable: true, message, userMessage: "AgentRun 已持久接纳本次请求,但本地可见性提交尚未完成;请使用同一 traceId 重试补提交。", route: "/v1/agent/chat" }, blocker: { code: "admission_promotion_unavailable", layer: "admission", retryable: true, summary: message } }, { params, options, traceId }); responsePayload.admission = { state: "promotion-pending", runId: payload.agentRun?.runId ?? null, commandId: payload.agentRun?.commandId ?? null, dispatchIntentId: payload.agentRun?.dispatchIntentId ?? null, runnerJobId: payload.agentRun?.runnerJobId ?? null, durableDispatch: payload.agentRun?.durableDispatch === true, valuesRedacted: true }; return Object.assign(new Error(message), { code: "admission_promotion_unavailable", statusCode: 503, payload: responsePayload, alreadyClassified: true }); } function codeAgentAdmissionUnavailablePayload({ error, params = {}, options = {}, traceId } = {}) { const message = error?.message ?? "Code Agent turn admission is unavailable; the user message was not persisted."; return { ok: false, accepted: false, status: "failed", traceId: safeTraceId(traceId) || null, ...codeAgentOtelTraceFields(traceId, options.env ?? process.env), ...codeAgentTurnLifecycleFields(traceId, params), conversationId: safeConversationId(params.conversationId) || null, sessionId: safeSessionId(params.sessionId) || null, threadId: safeOpaqueId(params.threadId) || null, error: { code: "admission_unavailable", layer: "admission", retryable: true, message, userMessage: "本次输入尚未持久化,请保留草稿后重试。", route: "/v1/agent/chat" }, blocker: { code: "admission_unavailable", layer: "admission", retryable: true, summary: message }, valuesRedacted: true, secretMaterialStored: false }; } function codeAgentDispatchFailure(error) { const message = error?.message ?? "AgentRun dispatch failed"; return { statusCode: 503, payload: { error: { code: error?.code ?? "agentrun_dispatch_failed", layer: "agentrun", retryable: true, message, userMessage: "AgentRun 调度失败;错误已提交到 Workbench 实时投影。", route: "/v1/agent/chat" }, blocker: { code: error?.code ?? "agentrun_dispatch_failed", layer: "agentrun", retryable: true, summary: message } } }; } function codeAgentDispatchErrorOtelAttributes(error) { const agentRunError = error?.agentRunError && typeof error.agentRunError === "object" ? error.agentRunError : null; const details = agentRunError?.error?.details && typeof agentRunError.error.details === "object" ? agentRunError.error.details : null; return { failureKind: safeOtelText(error?.code ?? agentRunError?.failureKind, 120), upstreamStatusCode: Number.isInteger(error?.statusCode) ? error.statusCode : null, upstreamTraceId: safeTraceId(agentRunError?.traceId) || null, upstreamMessage: safeOtelText(agentRunError?.message ?? error?.message, 300), upstreamStderrTail: safeOtelText(details?.stderr, 600) }; } function safeOtelText(value, limit = 300) { const text = String(value ?? "").replace(/[\u0000-\u001f\u007f]+/gu, " ").trim(); return text ? text.slice(0, limit) : null; } function codeAgentAdmittedFailureBasePayload({ params = {}, options = {}, traceId } = {}) { const now = new Date().toISOString(); return { accepted: true, status: "running", shortConnection: true, traceId, ...codeAgentTurnLifecycleFields(traceId, params), messageId: codeAgentTurnLifecycleFields(traceId, params).assistantMessageId, projectId: params.projectId ?? null, conversationId: safeConversationId(params.conversationId) || null, sessionId: safeSessionId(params.sessionId) || null, threadId: safeOpaqueId(params.threadId) || null, ...codeAgentAdmissionTimingFields(now), provider: codeAgentAgentRunAdapterEnabled(options.env ?? process.env) ? "agentrun" : "codex-stdio", model: options.env?.HWLAB_CODE_AGENT_MODEL ?? "unknown", backend: codeAgentAgentRunAdapterEnabled(options.env ?? process.env) ? "agentrun-v01" : "hwlab-cloud-api/code-agent-chat", valuesPrinted: false }; } function codeAgentAdmissionTimingFields(value = null) { const admittedAt = timestampIsoOrNow(value); const timing = { startedAt: admittedAt, lastEventAt: admittedAt, finishedAt: null, durationMs: null, observedAt: admittedAt, lastEventAgeMs: 0, valuesRedacted: true }; return { createdAt: admittedAt, updatedAt: admittedAt, timing, startedAt: admittedAt, lastEventAt: admittedAt, finishedAt: null, durationMs: null }; } async function settleAdmittedCodeAgentFailure({ base = {}, params = {}, options = {}, traceId, results, failure = {}, traceLabel = "code-agent:failed" } = {}) { const traceStore = options.traceStore ?? defaultCodeAgentTraceStore; const error = failure.payload?.error ?? failure.error ?? {}; const blocker = failure.payload?.blocker ?? failure.blocker ?? null; const message = error.message ?? blocker?.summary ?? "Code Agent request failed after admission."; traceStore.append(traceId, { type: "result", status: "failed", label: traceLabel, errorCode: error.code ?? "code_agent_failed_after_admission", message, terminal: true, valuesPrinted: false }); const now = new Date().toISOString(); const payload = annotateOwner({ ...base, accepted: true, status: "failed", traceId, ...codeAgentTurnLifecycleFields(traceId, base), messageId: base.messageId ?? codeAgentTurnLifecycleFields(traceId, base).assistantMessageId, conversationId: safeConversationId(base.conversationId ?? params.conversationId) || null, sessionId: safeSessionId(base.sessionId ?? params.sessionId) || null, threadId: safeOpaqueId(base.threadId ?? params.threadId) || null, error: { code: error.code ?? "code_agent_failed_after_admission", layer: error.layer ?? blocker?.layer ?? "api", retryable: error.retryable !== false, message, userMessage: error.userMessage ?? message, route: error.route ?? "/v1/agent/chat" }, blocker: blocker ? { code: blocker.code ?? error.code ?? "code_agent_failed_after_admission", layer: blocker.layer ?? error.layer ?? "api", retryable: blocker.retryable !== false, summary: blocker.summary ?? message } : null, finalResponse: { text: error.userMessage ?? message, textChars: String(error.userMessage ?? message).length, role: "assistant", status: "failed", traceId, updatedAt: now, source: "code-agent-admitted-failure", valuesPrinted: false }, runnerTrace: traceStore.snapshot(traceId), updatedAt: now, valuesPrinted: false, secretMaterialStored: false }, params); await finalizeCodeAgentBillingUsage({ payload, params, options }); payload.runnerTrace = traceStore.snapshot(traceId); recordCodeAgentConversationFact(payload, options); results?.set?.(traceId, payload); await recordCodeAgentSessionOwner({ payload, params, options, status: codeAgentOwnerStatusForResult(payload) }); return payload; }