const TEXT_FALLBACK_RUNNER = "openai-responses-fallback"; const CODEX_ONE_SHOT_RUNNER = "codex-cli-one-shot-ephemeral"; const READONLY_SESSION_MODE = "controlled-readonly-session-registry"; const CODEX_APP_SERVER_RUNNER = "codex-app-server-stdio-runner"; const CODEX_APP_SERVER_SESSION_MODE = "codex-app-server-stdio-long-lived"; const CODEX_APP_SERVER_IMPLEMENTATION = "repo-owned-codex-app-server-stdio-session"; const CODEX_APP_SERVER_PROTOCOL = "codex-app-server-jsonrpc-stdio"; const MISSING_FIELD_VALUE = "字段缺失:证据不足"; const REQUIRED_ATTRIBUTION_KEYS = Object.freeze([ "provider", "backend", "runnerKind", "protocol", "implementationType", "providerTrace.command", "providerTrace.terminalStatus", "sessionMode", "capabilityLevel", "toolCalls", "traceId", "operationId", "audit", "evidence" ]); export function codeAgentFactsFromMessage(message) { if (!hasCodeAgentFields(message)) return null; const classification = classifyCodeAgentFacts(message); const rows = [ { label: "runner/provider/backend", value: compactFields([ requiredField("runner", message.runner?.kind ?? runnerKindFromMessage(message)), requiredField("provider", message.provider), requiredField("backend", message.backend) ]) }, { label: "session", value: compactFields([ field("id", message.session?.sessionId ?? message.sessionId ?? message.runner?.sessionId), field("status", message.session?.status), requiredField("sessionMode", message.sessionMode ?? message.runner?.sessionMode ?? message.runner?.session) ]) }, { label: "workspace", value: valueOrMissing(message.workspace ?? message.runner?.workspace) }, { label: "sandbox", value: valueOrMissing(message.sandbox ?? message.runner?.sandbox) }, { label: "capabilityLevel", value: valueOrMissing(message.capabilityLevel ?? message.runner?.capabilityLevel) }, { label: "toolCalls", value: toolCallsSummary(message.toolCalls) }, { label: "skills", value: skillsSummary(message.skills) }, { label: "runnerTrace/traceId", value: compactFields([ requiredField("traceId", message.traceId), field("runnerTrace", runnerTraceSummary(message.runnerTrace)) ]) } ]; return { ...classification, rows }; } export function classifyCodeAgentFacts(message) { const runnerKind = runnerKindFromMessage(message); const provider = String(message?.provider ?? "").trim(); const sessionMode = String(message?.sessionMode ?? message?.runner?.sessionMode ?? message?.runner?.session ?? "").trim(); const implementationType = String(message?.implementationType ?? message?.runner?.implementationType ?? "").trim(); const capabilityLevel = String(message?.capabilityLevel ?? message?.runner?.capabilityLevel ?? "").trim(); const longLivedGate = message?.longLivedSessionGate; const runtimePath = codeAgentRuntimePathFromMessage(message); if ( provider === "codex-stdio" && longLivedGate?.status === "pass" && longLivedGate?.pass === true && runtimePath?.kind === "codex-app-server-stdio" && runtimePath?.criticalMissing === false && runtimePath?.terminalStatusOk === true ) { return { kind: "codex-stdio-long-lived", statusLabel: "真实 runner:Codex app-server stdio 长会话", tone: "dev-live", summary: "repo-owned Codex app-server stdio runner 已通过长会话 gate;providerTrace 明确显示 Responses wire API、stdio command 和 terminalStatus。", fullCodeAgent: true, codexSessionGatePass: true }; } if (runnerKind === CODEX_ONE_SHOT_RUNNER || sessionMode === "ephemeral-one-shot" || implementationType === "codex-cli-one-shot-ephemeral") { return { kind: "stateless-one-shot", statusLabel: "一次性 runner:非长会话", tone: "source", summary: "回复来自一次性 runner;不可当作可复用或长会话 Code Agent。", fullCodeAgent: false, codexSessionGatePass: false }; } if (runnerKind === TEXT_FALLBACK_RUNNER || provider === "openai-responses" || capabilityLevel === "text-chat-only") { return { kind: "text-chat-only", statusLabel: "OpenAI fallback:只是文本回答 / text-chat-only", tone: "source", summary: "只是文本回答;当前不能执行 workspace、tools、skills 或 Codex session 控制。", fullCodeAgent: false, codexSessionGatePass: false }; } if (provider === "codex-readonly-runner" || runnerKind === "hwlab-readonly-runner" || sessionMode === READONLY_SESSION_MODE) { return { kind: READONLY_SESSION_MODE, statusLabel: "只读长会话:controlled-readonly-session-registry", tone: "source", summary: "只读 session registry 可复用 conversation/session;只能使用 read-only-session-tools,不能写工作区,也不能冒充完整 Codex stdio Code Agent。", fullCodeAgent: false, codexSessionGatePass: false }; } if (provider === "codex-stdio" || sessionMode === CODEX_APP_SERVER_SESSION_MODE) { return { kind: runtimePath?.providerTraceMissing ? "codex-stdio-provider-trace-missing" : "codex-stdio-blocked", statusLabel: runtimePath?.providerTraceMissing ? "Codex stdio 降级:providerTrace 缺失" : "Codex stdio 受阻:当前不能执行", tone: "blocked", summary: runtimePath?.providerTraceMissing ? "Codex stdio 字段存在,但 providerTrace 缺失;界面不会猜测它是完整 app-server runner。" : "Codex stdio 字段存在,但长会话 gate 或 providerTrace 终态未通过;当前不能标记为完整 runner 控制。", fullCodeAgent: false, codexSessionGatePass: false }; } return { kind: "unknown-code-agent-path", statusLabel: message?.status === "failed" ? "BLOCKED:当前不能执行" : "证据不足:字段缺失", tone: message?.status === "failed" ? "blocked" : "source", summary: "payload 缺少足够 provider/backend/runner/session 证据;只展示结构化字段缺失,不假定具备执行能力。", fullCodeAgent: false, codexSessionGatePass: false }; } export function codeAgentAttributionFromMessage(message) { if (!message || typeof message !== "object") return null; if (message.role && message.role !== "agent") return null; if (!hasCodeAgentFields(message)) return null; const classification = classifyCodeAgentFacts(message); const ids = operationAuditEvidenceIds(message); const runtimePath = codeAgentRuntimePathFromMessage(message); const runnerKind = message.runner?.kind ?? runnerKindFromMessage(message); const sessionMode = message.sessionMode ?? message.runner?.sessionMode ?? message.runner?.session; const capabilityLevel = message.capabilityLevel ?? message.runner?.capabilityLevel; const fields = [ attributionField("provider", message.provider, { message }), attributionField("backend", message.backend, { message }), attributionField("runnerKind", runnerKind, { message }), attributionField("protocol", runtimePath?.protocol, { message }), attributionField("implementationType", runtimePath?.implementationType, { message }), attributionField("providerTrace.command", runtimePath?.command, { message }), attributionField("providerTrace.terminalStatus", runtimePath?.terminalStatus, { message }), attributionField("sessionMode", sessionMode, { message }), attributionField("capabilityLevel", capabilityLevel, { message }), attributionField("toolCalls", toolCallsAttributionSummary(message.toolCalls), { message, missingWhenNone: false }), attributionField("traceId", message.traceId ?? message.runnerTrace?.traceId ?? message.providerTrace?.traceId, { message }), attributionField("operationId", ids.operationId, { message }), attributionField("audit", ids.auditId, { message }), attributionField("evidence", ids.evidenceId, { message }) ]; const missingFields = fields.filter((field) => field.missing).map((field) => field.key); return { kind: classification.kind, tone: classification.tone, label: messageAttributionLabel(message, classification), summary: messageAttributionSummary(message, classification, missingFields, runtimePath), fields, missingFields }; } export function codeAgentRuntimePathFromMessage(message) { if (!message || typeof message !== "object") return null; if (message.role && message.role !== "agent") return null; if (!hasCodeAgentFields(message)) return null; const providerTrace = objectOrNull(message.providerTrace); const runnerTrace = objectOrNull(message.runnerTrace); const runnerKind = firstText(message.runner?.kind, runnerTrace?.runnerKind, runnerKindFromMessage(message)); const provider = nullableText(message.provider); const protocol = firstText(providerTrace?.protocol, runnerTrace?.protocol); const implementationType = firstText(message.implementationType, message.runner?.implementationType, runnerTrace?.implementationType); const command = firstText(providerTrace?.command); const terminalStatus = firstText(providerTrace?.terminalStatus); const wireApi = firstText(providerTrace?.wireApi, isCodexAppServerPath({ provider, runnerKind, protocol, implementationType, command }) ? "responses" : null); const providerTraceMissing = !providerTrace; const required = { provider, runnerKind, protocol, implementationType, "providerTrace.command": command, "providerTrace.terminalStatus": terminalStatus }; const missingFields = Object.entries(required) .filter(([, value]) => !nullableText(value)) .map(([key]) => key); const isAppServer = isCodexAppServerPath({ provider, runnerKind, protocol, implementationType, command }); const isFallback = provider === "openai-responses" || runnerKind === TEXT_FALLBACK_RUNNER || message.capabilityLevel === "text-chat-only"; const isReadonly = provider === "codex-readonly-runner" || runnerKind === "hwlab-readonly-runner" || message.sessionMode === READONLY_SESSION_MODE; const terminalStatusOk = terminalStatus === "completed" || terminalStatus === "succeeded"; const criticalMissing = missingFields.length > 0; const blocked = ["failed", "blocked", "timeout", "error", "canceled"].includes(String(message.status ?? "").toLowerCase()); let kind = "unknown"; let label = "运行路径证据不足"; let summary = "payload 缺少 providerTrace 或 runner 关键字段;界面不会推断完整 Code Agent。"; let tone = blocked ? "blocked" : "warn"; if (isAppServer) { kind = "codex-app-server-stdio"; if (!providerTrace) { label = "DEGRADED:providerTrace 缺失"; summary = "检测到 Codex app-server 形态字段,但 providerTrace 缺失;不能确认 command、protocol 或 terminalStatus。"; } else if (criticalMissing) { label = "DEGRADED:运行路径字段不完整"; summary = `repo-owned Codex app-server stdio 已暴露部分字段,但仍缺少 ${missingFields.join(", ")}。`; } else if (!terminalStatusOk) { label = "BLOCKED:Codex app-server 终态未完成"; summary = `repo-owned Codex app-server stdio 返回 terminalStatus=${terminalStatus};本次不标为完整完成。`; tone = "blocked"; } else { label = "repo-owned Codex app-server stdio"; summary = `Codex app-server 通过 stdio JSON-RPC 运行,使用 Responses wire API(wire_api=${wireApi ?? "未观测"}),不是 MCP、fallback 或 Cloud Web 直连 OpenAI。`; tone = "ok"; } } else if (isFallback) { kind = "openai-text-fallback"; label = "DEGRADED:OpenAI text fallback"; summary = "这是 OpenAI text fallback/text-chat-only;没有 workspace runner、Codex app-server session 或工具控制能力。"; tone = blocked ? "blocked" : "warn"; } else if (isReadonly) { kind = "readonly-runner"; label = "DEGRADED:read-only/source runner"; summary = "这是只读或 source runner;不能写 workspace,也不能冒充完整 Code Agent 完成。"; tone = blocked ? "blocked" : "warn"; } else if (providerTraceMissing) { kind = "provider-trace-missing"; label = blocked ? "BLOCKED:providerTrace 缺失" : "DEGRADED:providerTrace 缺失"; summary = "providerTrace 缺失;不会把 fallback/read-only/source 或未知路径显示成完整 Code Agent。"; } return { kind, label, summary, tone, provider, runnerKind, protocol, implementationType, command, terminalStatus, wireApi, providerTraceMissing, criticalMissing, missingFields, terminalStatusOk, rows: [ runtimePathRow("provider", provider), runtimePathRow("runnerKind", runnerKind), runtimePathRow("protocol", protocol), runtimePathRow("implementationType", implementationType), runtimePathRow("providerTrace.command", command), runtimePathRow("providerTrace.terminalStatus", terminalStatus), runtimePathRow("wireApi", wireApi) ] }; } export function toolCallsSummary(toolCalls) { if (!Array.isArray(toolCalls) || toolCalls.length === 0) return "none"; const completed = toolCalls.filter((tool) => tool?.status === "completed").length; const names = toolCalls .map((tool) => compactFields([ firstText(tool?.name, "tool"), tool?.status ? `(${tool.status})` : null, tool?.route ? `@${tool.route}` : null ], "")) .filter(Boolean) .slice(0, 4) .join(", "); return `${completed}/${toolCalls.length}${names ? ` ${names}` : ""}`; } export function skillsSummary(skills) { if (!skills || typeof skills !== "object") return "none"; const count = skills.totalCount ?? skills.count ?? (Array.isArray(skills.items) ? skills.items.length : 0); const names = Array.isArray(skills.items) ? skills.items.map((item) => item?.name).filter(Boolean).slice(0, 3).join(",") : ""; const blockers = Array.isArray(skills.blockers) ? skills.blockers.map((blocker) => blocker?.code).filter(Boolean).slice(0, 2).join(",") : ""; return compactFields([ skills.status ?? "unknown", `count=${count}`, names ? `items=${names}` : null, blockers ? `blockers=${blockers}` : null ], ":"); } export function runnerTraceSummary(runnerTrace) { if (!runnerTrace || typeof runnerTrace !== "object") return "none"; return compactFields([ field("traceId", runnerTrace.traceId), field("runnerKind", runnerTrace.runnerKind), field("sessionMode", runnerTrace.sessionMode), field("implementationType", runnerTrace.implementationType), field("protocol", runnerTrace.protocol), typeof runnerTrace.turn === "number" ? field("turn", runnerTrace.turn) : null, field("route", runnerTrace.route), field("status", runnerTrace.status) ]); } function hasCodeAgentFields(message) { return Boolean( message && typeof message === "object" && ( message.provider || message.backend || message.runner || message.session || message.sessionMode || message.capabilityLevel || Array.isArray(message.toolCalls) || message.runnerTrace || message.providerTrace || (message.role === "agent" && (message.traceId || message.error || message.status === "failed" || message.status === "running")) ) ); } function messageAttributionLabel(message, classification) { if (message?.status === "failed" || message?.sourceKind === "BLOCKED") return "BLOCKED:当前不能执行"; if (classification.kind === "text-chat-only") return "OpenAI fallback:只是文本回答"; if (classification.kind === READONLY_SESSION_MODE) return "read-only-session-tools:只读长会话"; if (classification.kind === "codex-stdio-long-lived") return "真实 runner:Codex app-server stdio 长会话"; if (classification.kind === "stateless-one-shot") return "一次性 runner:非长会话"; if (classification.kind === "codex-stdio-provider-trace-missing") return "DEGRADED:providerTrace 缺失"; if (classification.kind === "codex-stdio-blocked") return "Codex stdio 受阻:当前不能执行"; return classification.statusLabel; } function messageAttributionSummary(message, classification, missingFields, runtimePath = null) { const prefix = messageAttributionLabel(message, classification); const missing = missingFields.length > 0 ? `证据不足:${missingFields.join(",")} 字段缺失。` : "关键归因字段已观测。"; const runtime = runtimePath?.summary ? `运行路径:${runtimePath.summary}` : ""; if (classification.kind === "text-chat-only") { return `${prefix};只是文本回答,当前不能执行工具或 runner 控制。${missing}${runtime}`; } if (message?.status === "failed" || message?.sourceKind === "BLOCKED") { return `${prefix};本次回复没有执行能力。${missing}${runtime}`; } return `${prefix};${missing}${runtime}`; } function attributionField(key, rawValue, { message, missingWhenNone = true } = {}) { const text = nullableText(rawValue); const noneLike = !text || ["none", "unknown", "null", "undefined"].includes(text.toLowerCase()); const missing = missingWhenNone && noneLike; const value = missing ? MISSING_FIELD_VALUE : localizedAttributionValue(key, text ?? "none", message); return { key, label: key, value, missing, chip: `${key}=${value}` }; } function localizedAttributionValue(key, value, message) { const normalized = String(value ?? "").trim(); if (key === "provider" && normalized === "openai-responses") return `${normalized} / OpenAI fallback:只是文本回答`; if (key === "runnerKind" && normalized === CODEX_APP_SERVER_RUNNER) return `${normalized} / repo-owned Codex app-server stdio`; if (key === "runnerKind" && normalized === TEXT_FALLBACK_RUNNER) return `${normalized} / 不是 runner 控制`; if (key === "runnerKind" && normalized === "hwlab-readonly-runner") return `${normalized} / 只读 runner`; if (key === "protocol" && normalized === CODEX_APP_SERVER_PROTOCOL) return `${normalized} / Responses wire API`; if (key === "implementationType" && normalized === CODEX_APP_SERVER_IMPLEMENTATION) return `${normalized} / repo-owned app-server stdio session`; if (key === "sessionMode" && normalized === READONLY_SESSION_MODE) return `${normalized} / 只读 session registry`; if (key === "sessionMode" && normalized === CODEX_APP_SERVER_SESSION_MODE) return `${normalized} / app-server stdio 长会话`; if (key === "capabilityLevel" && normalized === "text-chat-only") return "text-chat-only / 只是文本回答"; if (key === "capabilityLevel" && normalized === "read-only-session-tools") return `${normalized} / 只读工具`; if (key === "toolCalls" && normalized === "none") { return message?.capabilityLevel === "text-chat-only" ? "0/0 / 无工具调用:只是文本回答" : "0/0 / 无工具调用"; } return normalized; } function operationAuditEvidenceIds(message) { const providerTrace = objectOrNull(message?.providerTrace); const runnerTrace = objectOrNull(message?.runnerTrace); const toolCalls = Array.isArray(message?.toolCalls) ? message.toolCalls : []; const parsedToolPayloads = toolCalls.map((toolCall) => parseToolStdout(toolCall?.stdout)).filter(Boolean); return { operationId: firstText( message?.operationId, message?.operation?.operationId, providerTrace?.operationId, runnerTrace?.operationId, ...toolCalls.map((toolCall) => toolCall?.operationId ?? toolCall?.hwlabApi?.operationId), ...parsedToolPayloads.map((payload) => payload?.operationId ?? payload?.operation?.operationId) ), auditId: firstText( message?.auditId, message?.audit?.auditId, providerTrace?.auditId, runnerTrace?.auditId, ...toolCalls.map((toolCall) => toolCall?.auditId ?? toolCall?.audit?.auditId ?? toolCall?.hwlabApi?.auditId), ...parsedToolPayloads.map((payload) => payload?.auditId ?? payload?.audit?.auditId) ), evidenceId: firstText( message?.evidenceId, message?.evidence?.evidenceId, providerTrace?.evidenceId, runnerTrace?.evidenceId, ...toolCalls.map((toolCall) => toolCall?.evidenceId ?? toolCall?.evidence?.evidenceId ?? toolCall?.hwlabApi?.evidenceId), ...parsedToolPayloads.map((payload) => payload?.evidenceId ?? payload?.evidence?.evidenceId) ) }; } function toolCallsAttributionSummary(toolCalls) { const summary = toolCallsSummary(toolCalls); return summary === "none" ? "none" : summary; } function objectOrNull(value) { return value && typeof value === "object" && !Array.isArray(value) ? value : null; } function runnerKindFromMessage(message) { return firstText( message?.runner?.kind, message?.runnerTrace?.runnerKind, message?.provider === "openai-responses" ? TEXT_FALLBACK_RUNNER : null ); } function runtimePathRow(label, value) { const text = nullableText(value); return { label, value: text ?? MISSING_FIELD_VALUE, missing: !text }; } function isCodexAppServerPath({ provider, runnerKind, protocol, implementationType, command }) { return provider === "codex-stdio" && ( runnerKind === CODEX_APP_SERVER_RUNNER || protocol === CODEX_APP_SERVER_PROTOCOL || implementationType === CODEX_APP_SERVER_IMPLEMENTATION || /\bcodex\s+app-server\s+--listen\s+stdio:\/\//u.test(String(command ?? "")) ); } function parseToolStdout(stdout) { if (typeof stdout !== "string" || !stdout.trim()) return null; const text = stdout.trim(); const direct = parseJson(text); if (direct) return direct; const start = text.indexOf("{"); const end = text.lastIndexOf("}"); if (start === -1 || end <= start) return null; return parseJson(text.slice(start, end + 1)); } function parseJson(text) { try { const parsed = JSON.parse(text); return parsed && typeof parsed === "object" ? parsed : null; } catch { return null; } } function compactFields(fields, separator = " / ") { return fields.filter((item) => item !== null && item !== undefined && String(item).trim()).map((item) => String(item).trim()).join(separator); } function field(label, value) { const normalized = nullableText(value); return normalized ? `${label}=${normalized}` : null; } function requiredField(label, value) { const normalized = nullableText(value); return `${label}=${normalized ?? MISSING_FIELD_VALUE}`; } function valueOrMissing(value) { return nullableText(value) ?? MISSING_FIELD_VALUE; } function firstText(...values) { return values.map(nullableText).find(Boolean) ?? null; } function nullableText(value) { if (value === undefined || value === null || value === "") return null; return String(value).replace(/\s+/gu, " ").trim(); }