function traceEventMeta(event) { return [ event.toolName ? `tool=${event.toolName}` : null, event.waitingFor ? `waiting=${event.waitingFor}` : null, typeof event.timeoutMs === "number" ? `timeoutMs=${event.timeoutMs}` : null, typeof event.hardTimeoutMs === "number" ? `hardTimeoutMs=${event.hardTimeoutMs}` : null, typeof event.idleMs === "number" ? `idleMs=${event.idleMs}` : null, event.lastActivityAt ? `lastActivityAt=${event.lastActivityAt}` : null, typeof event.elapsedMs === "number" ? `${event.elapsedMs}ms` : null, event.errorCode ? `error=${event.errorCode}` : null, event.outputSummary, event.message ].filter(Boolean).join(" / "); } function toolCallsSummary(toolCalls) { const summary = compactToolCallsSummary(toolCalls); return summary === "none" ? null : summary; } function skillsSummary(skills) { const summary = compactSkillsSummary(skills); return summary === "none" ? null : summary; } function runnerTraceSummary(runnerTrace) { const summary = compactRunnerTraceSummary(runnerTrace); return summary === "none" ? null : summary; } function conversationFactsSummary(conversationFacts) { if (!conversationFacts || typeof conversationFacts !== "object") return "none"; return [ recordField("turns", conversationFacts.turnCount), recordField("sessionId", conversationFacts.sessionId), recordField("status", conversationFacts.sessionStatus), recordField("workspace", conversationFacts.workspace), recordField("mode", conversationFacts.sessionMode), conversationFacts.valuesRedacted === true ? "valuesRedacted=true" : null ].filter(Boolean).join(" / ") || "none"; } function conversationFactTracesSummary(conversationFacts) { if (!conversationFacts || typeof conversationFacts !== "object") return "none"; const traces = Array.isArray(conversationFacts.traceIds) ? conversationFacts.traceIds.filter(Boolean) : []; return traces.length ? traces.slice(-4).join(",") : conversationFacts.latestTraceId ?? "none"; } function conversationFactSkillsSummary(conversationFacts) { const skills = conversationFacts?.latestSkills; if (!skills || typeof skills !== "object") return "none"; const names = Array.isArray(skills.names) ? skills.names.filter(Boolean).slice(0, 6).join(",") : ""; return [ skills.status ?? "unknown", recordField("count", skills.totalCount ?? skills.count), names ? `items=${names}` : null ].filter(Boolean).join(":") || "none"; } function conversationFactToolsSummary(conversationFacts) { const toolCalls = Array.isArray(conversationFacts?.recentToolCalls) ? conversationFacts.recentToolCalls : []; if (toolCalls.length === 0) return "none"; return toolCalls .map((toolCall) => [toolCall.name ?? "tool", toolCall.status ? `(${toolCall.status})` : null].filter(Boolean).join("")) .slice(-5) .join(","); } function sessionSummary(session) { if (!session || typeof session !== "object") return null; const parts = [ session.sessionId, session.status, typeof session.turn === "number" ? `turn${session.turn}` : null, session.threadId ? `thread=${session.threadId}` : null, session.lastTraceId ? `lastTrace=${session.lastTraceId}` : null ].filter(Boolean); return parts.join(":"); } function statusCard(item) { const article = document.createElement("article"); article.className = "status-card"; article.append(textSpan(item.title, "metric-label")); article.append(textSpan(String(item.value), `status-value tone-${toneClass(item.tone)}`)); article.append(textSpan(item.meta, "status-meta")); return article; } function infoCard(item) { const article = document.createElement("article"); article.className = "info-card"; article.append(badge(statusLabel(item.tone), item.tone)); const body = document.createElement("div"); body.append(textSpan(item.title, "info-title")); body.append(textSpan(item.detail, "info-detail")); article.append(body); return article; } function badge(text, tone = text) { const span = document.createElement("span"); span.className = `badge tone-${toneClass(tone)}`; span.textContent = text ?? "无"; return span; } function copyButton(value, label = "value") { const button = document.createElement("button"); button.className = "copy-chip"; button.type = "button"; button.textContent = "复制"; button.title = `复制 ${label}`; button.addEventListener("click", async () => { await copyTextToClipboard(value); button.textContent = "已复制"; window.setTimeout(() => { button.textContent = "复制"; }, 1200); }); return button; } async function copyTextToClipboard(value) { const text = String(value ?? ""); if (navigator.clipboard?.writeText) { await navigator.clipboard.writeText(text); return; } const input = document.createElement("textarea"); input.value = text; input.setAttribute("readonly", ""); input.style.position = "fixed"; input.style.left = "-9999px"; document.body.append(input); input.select(); document.execCommand("copy"); input.remove(); } function safeLink(href, text, className) { const anchor = document.createElement("a"); anchor.href = href; anchor.textContent = text ?? href; anchor.rel = "noreferrer"; if (className) anchor.className = className; return anchor; } function textSpan(text, className) { const span = document.createElement("span"); span.textContent = text ?? "无"; if (className) span.className = className; return span; } function cell(text, className) { const td = document.createElement("td"); td.textContent = text ?? "无"; if (className) td.className = className; return td; } function formatTimestamp(value) { const date = new Date(value); if (Number.isNaN(date.getTime())) return "未知"; return shortTime(date.toISOString()); } function tableCell(text, className, colspan = 1) { const td = cell(text, className); if (colspan > 1) td.colSpan = colspan; return td; } function runtimeSummaryFrom(live) { return live.health.data?.runtime ?? live.healthLive.data?.runtime ?? live.adapter.data?.runtime ?? live.restIndex.data?.runtime ?? null; } function messageFromPayload(payload) { if (!payload || typeof payload !== "object") return "非 JSON 或空响应"; if (payload.error && typeof payload.error === "object") { return payload.error.message || payload.error.code || payload.reason || payload.message || JSON.stringify(payload.error).slice(0, 160); } if (typeof payload.error === "string") { return payload.message ? `${payload.error}:${payload.message}` : payload.error; } return payload.code || payload.message || payload.reason || JSON.stringify(payload).slice(0, 160); } function normalizeBlockedAgentResult(payload, httpStatus, traceId, fallbackMessage) { const { reply, ...safePayload } = payload; const payloadError = payload.error && typeof payload.error === "object" ? payload.error : {}; const payloadErrorCode = payloadError.code ?? (typeof payload.error === "string" ? payload.error : null); const providerStatus = payloadError.providerStatus ?? httpStatus; const error = { ...payloadError, code: payloadErrorCode ?? "provider_unavailable", message: payloadError.message ?? fallbackMessage ?? `Code Agent provider HTTP ${httpStatus}`, providerStatus }; return { ...safePayload, status: "failed", traceId: payload.traceId || traceId, error, availability: payload.availability ?? codeAgentAvailabilityFromResult({ error }) }; } function isBlockedAgentResponse(payload, httpStatus) { return Boolean( payload && typeof payload === "object" && ( isHttpNon2xxStatus(httpStatus) || isHttpNon2xxStatus(payload.error?.providerStatus) || payload.status === "blocked" || payload.error?.code === "provider_unavailable" || payload.error?.code === "security_blocked" || payload.error?.code === "tool_unavailable" || payload.error?.code === "skills_unavailable" || payload.availability?.status === "blocked" ) ); } function isHttpNon2xxStatus(value) { return typeof value === "number" && Number.isInteger(value) && (value < 200 || value > 299); } function codeAgentAvailabilityFrom(live) { return sanitizeCodeAgentAvailability(codeAgentAvailabilityFromLive(live)); } function codeAgentAvailabilityFromResult(result) { const missingEnv = Array.isArray(result?.error?.missingEnv) ? result.error.missingEnv : []; const providerStatus = result?.error?.providerStatus; const credentialBlocked = missingEnv.includes("OPENAI_API_KEY"); return { status: "blocked", blocker: credentialBlocked ? "凭证缺口" : "provider_unavailable", reason: result?.error?.code ?? "provider_unavailable", summary: providerStatus ? `真实后端已接入,但当前 provider 上游返回 HTTP ${providerStatus};本次保持 BLOCKED/provider_unavailable,不会冒充真实 Code Agent 回复。` : "真实后端已接入,但当前 DEV provider Secret hwlab-code-agent-provider/openai-api-key 未注入,因此不能返回真实回复。", missingEnv, secretRefs: credentialBlocked ? [ { env: "OPENAI_API_KEY", secretName: "hwlab-code-agent-provider", secretKey: "openai-api-key", redacted: true } ] : [] }; } function sanitizeCodeAgentAvailability(availability) { if (!availability || typeof availability !== "object") return availability; if (availability.status !== "blocked") return availability; return { ...availability, blocker: "服务受阻", summary: codeAgentBlockedSummary(availability) }; } function codeAgentStatusMessage(availability) { if (availability?.longLivedSessionGate?.status === "pass" && availability?.codexStdioFeasibility?.canStartLongLivedCodexStdio === true) { return { role: "system", title: "Code Agent 状态:Codex stdio 长会话可用", text: "当前通道可创建和复用 repo-owned Codex stdio session,并暴露 cancel、reap、idle timeout 与 trace capture;硬件控制仍只走 cloud-api/HWLAB API/skill CLI。", status: "source" }; } if (availability?.runner?.ready === true) { return { role: "system", title: "Code Agent 状态:Codex stdio gate 未通过", text: `同源服务可响应,但完整 Codex stdio 长会话仍未通过 gate;不会用本地技能发现、shell/file 快捷命令或文本 fallback 冒充回复。${codeAgentBlockerDetail(availability)}`, status: "blocked" }; } if (availability?.status === "blocked") { return { role: "system", title: "Code Agent 状态:服务受阻", text: codeAgentBlockedSummary(availability), status: "blocked" }; } if (String(availability?.sourceKind ?? availability?.evidenceLevel ?? "").toUpperCase() === "SOURCE") { return { role: "system", title: "Code Agent 状态:本地验证记录", text: "当前只用于本地浏览器验证:可以检查发送、处理中、回复渲染和证据展示,但不会标记为实况完成。", status: "source" }; } if (availability?.status === "available") { return { role: "system", title: "Code Agent 状态:真实通道已接入", text: "受控对话通道已接入;发送后只有真实完成的回复才会标记为已完成。", status: "source" }; } return { role: "system", title: "Code Agent 状态:等待 Codex stdio 探测", text: "正在确认服务状态;首屏不会宣称 Code Agent 可用,也不会把失败或静态内容当成真实回复。", status: "source" }; } function codeAgentPromptText(availability) { if (availability?.status === "blocked") { return "可以继续输入并保留草稿;发送会走受控对话通道。服务恢复前会显示结构化失败,不会冒充真实 Codex 回复。"; } return "请在底部输入中文消息并点击发送;完成态只来自真实完成的后端回复。"; } function codeAgentControlSummary(availability) { if (latestCompletedAgentMessage()) { return state.conversationId ? `当前会话 ${state.conversationId};最近一次真实完成回复来自受控 Code Agent 接口,并带有可核验记录。` : "最近一次真实完成回复来自受控 Code Agent 接口,并带有可核验记录。"; } if (isSourceFixtureCompletedChatMessage(latestChatResult())) { return "最近一次为本地验证回复;它只证明浏览器对话体验,不作为实况完成证据。"; } if (availability?.status === "blocked") { return codeAgentBlockedSummary(availability); } if (availability?.partialReady === true || availability?.runner?.ready === true) { return `输入区会调用受控接口;完整 Codex stdio 长会话尚未可用时只返回结构化失败,不能用本地 shortcut 或 text fallback 冒充回复。${codeAgentBlockerDetail(availability)}`; } return "输入区会调用受控 Code Agent 接口;只有真实完成回复才显示为完成,不能因为只有会话编号就当成实况完成。"; } function codeAgentBlockedSummary(availability) { const structured = availability?.error?.userMessage ?? availability?.blocker?.userMessage; if (structured) return structured; const providerStatus = availability?.error?.providerStatus ?? availability?.providerStatus; if (providerStatus) { return `真实后端已接入,但当前上游响应 HTTP ${providerStatus};工作台保持只读和可重试,不会冒充真实 Code Agent 回复。`; } const blocker = codeAgentBlockerDetail(availability); return `真实后端已接入,但 Code Agent 服务暂不可用;完整 Codex stdio Code Agent 仍受阻,工作台保持只读和可重试,不会冒充真实回复。${blocker}`; } function codeAgentBlockerDetail(availability) { const blockers = codeAgentBlockerCodes(availability); if (blockers.length === 0 && availability?.reason) blockers.push(availability.reason); if (blockers.length === 0) return ""; const labels = blockers.slice(0, 3).map(codeAgentBlockerChineseLabel); return ` 阻塞:${labels.join(";")}。证据 code=${blockers.slice(0, 3).join(",")}。`; } function codeAgentBlockerCodes(availability) { const values = [ ...(Array.isArray(availability?.blockers) ? availability.blockers.map((blocker) => blocker?.code) : []), ...(Array.isArray(availability?.blockerCodes) ? availability.blockerCodes : []), ...(Array.isArray(availability?.longLivedSessionGate?.blockers) ? availability.longLivedSessionGate.blockers.map((blocker) => blocker?.code) : []), ...(Array.isArray(availability?.codexStdioFeasibility?.blockers) ? availability.codexStdioFeasibility.blockers.map((blocker) => blocker?.code) : []), ...(Array.isArray(availability?.runtimeContract?.stdioProtocol?.missingTools) && availability.runtimeContract.stdioProtocol.missingTools.length > 0 ? ["stdio_protocol_not_wired"] : []), availability?.runtimeContract?.binary?.status === "missing" ? "codex_cli_binary_missing" : null, availability?.runtimeContract?.lifecycleSupervisor?.status === "blocked" ? "runner_lifecycle_missing" : null ].filter(Boolean).map((code) => String(code)); return [...new Set(values)]; } function codeAgentBlockerChineseLabel(code) { return { codex_cli_binary_missing: "未找到受控 Codex CLI binary", codex_cli_not_executable: "Codex CLI 无法执行 --version", codex_cli_native_dependency_missing: "Codex CLI native 依赖缺失", runner_lifecycle_missing: "缺少 repo-owned lifecycle supervisor", stdio_protocol_not_wired: "Codex stdio 协议尚未接入", codex_stdio_supervisor_disabled: "Codex stdio supervisor 未启用", codex_stdio_blocked_readonly_session_available: "旧 partial runner 不能作为 Codex stdio", controlled_readonly_not_long_lived_stdio: "非 Codex stdio 长会话", openai_responses_fallback_not_session: "文本 fallback 不是长会话", one_shot_runner_not_long_lived: "一次性执行不是可复用 session", provider_token_boundary: "token 边界未配置", codex_home_missing: "CODEX_HOME 不存在或不可读", codex_home_write_blocked: "CODEX_HOME 不可写", workspace_mount_missing: "工作区挂载不可读", workspace_write_boundary_blocked: "workspace-write 沙箱不可写", codex_stdio_egress_boundary: "DEV egress 边界不合规" }[code] ?? code; } function untrustedCompletionMessage(result) { const provider = result?.provider ?? "unknown"; return `Code Agent 返回 completed,但缺少真实 provider/model/trace/conversation 证据,或 provider=${provider} 属于 echo/mock/stub;本次不会标记为真实完成。SOURCE fixture 只可显示为 SOURCE 回复,不能冒充 DEV-LIVE。`; } function codeAgentAvailabilitySummary() { if (state.codeAgentAvailability?.longLivedSessionGate?.status === "pass" && state.codeAgentAvailability?.codexStdioFeasibility?.canStartLongLivedCodexStdio === true) { return "同源 API 可响应;Codex stdio 长会话 gate 已通过,支持真实 session 复用和 trace capture。"; } if (state.codeAgentAvailability?.runner?.ready === true) { return `同源 API 可响应;完整 Codex stdio 长会话仍受阻,本地 shortcut 与文本 fallback 不会冒充真实 Code Agent 回复。${codeAgentBlockerDetail(state.codeAgentAvailability)}`; } if (state.codeAgentAvailability?.status === "blocked") { return "同源 API 可响应;Code Agent 服务暂不可用,工作台保持只读和可重试。"; } if (String(state.codeAgentAvailability?.sourceKind ?? state.codeAgentAvailability?.evidenceLevel ?? "").toUpperCase() === "SOURCE") { return "同源 API 可响应;当前 Code Agent 回复来源是本地验证记录,仅用于浏览器验证,不是实况完成。"; } if (state.codeAgentAvailability?.status === "available") { return "同源 API 可响应;Code Agent 真实通道已接入,只有真实完成回复才显示为完成。"; } return "同源接口可响应。技术复核详情已放在二级页面。"; } function latestCompletedAgentMessage() { return [...state.chatMessages].reverse().find((message) => message.role === "agent" && isRealCompletedChatMessage(message) ) ?? null; } function isRealCompletedChatResult(result) { const assistantReply = typeof result?.reply?.content === "string" ? result.reply.content.trim() : ""; return result?.status === "completed" && assistantReply.length > 0 && !isSourceFixtureCompletion(result) && hasRealCodeAgentEvidence(result); } function isSourceFixtureChatResult(result) { const assistantReply = typeof result?.reply?.content === "string" ? result.reply.content.trim() : ""; return result?.status === "completed" && assistantReply.length > 0 && isSourceFixtureCompletion(result); } function isTextFallbackChatResult(result) { const assistantReply = typeof result?.reply?.content === "string" ? result.reply.content.trim() : ""; return result?.status === "completed" && assistantReply.length > 0 && result?.provider === "openai-responses" && (result?.capabilityLevel === "text-chat-only" || result?.runner?.kind === "openai-responses-fallback") && result?.longLivedSessionGate?.status !== "pass"; } function isRealCompletedChatMessage(message) { return message?.status === "completed" && !message.error && hasRealCodeAgentEvidence(message); } function isSourceFixtureCompletedChatMessage(message) { return message?.status === "source" && !message.error && message.sourceKind === "SOURCE"; } function hasRealCodeAgentEvidence(value) { if (isCodexRunnerCapableEvidence(value)) { return true; } if (CODEX_RUNNER_CAPABLE_PROVIDERS.includes(String(value?.provider ?? "").trim().toLowerCase())) { return false; } if (isTextFallbackChatResult(value)) { return false; } return ( isTrustedCodeAgentProvider(value?.provider) && Boolean(value?.model) && Boolean(value?.backend) && Boolean(value?.traceId) && Boolean(value?.conversationId || value?.sessionId) && hasProviderTrace(value) && Boolean(sessionIdFrom(value)) && (!requiresCodexThreadEvidence(value) || Boolean(threadIdFrom(value))) && !UNTRUSTED_CODE_AGENT_PROVIDER_PATTERN.test(`${value?.provider ?? ""} ${value?.backend ?? ""}`) ); } function isCodexRunnerCapableEvidence(value) { return ( CODEX_RUNNER_CAPABLE_PROVIDERS.includes(String(value?.provider ?? "").trim().toLowerCase()) && value?.runner?.kind === CODEX_APP_SERVER_RUNNER_KIND && value?.capabilityLevel === "long-lived-codex-stdio-session" && value?.session?.status === "idle" && typeof value?.session?.idleTimeoutMs === "number" && Boolean(value?.session?.lastTraceId) && value?.sessionMode === CODEX_APP_SERVER_SESSION_MODE && value?.implementationType === CODEX_APP_SERVER_IMPLEMENTATION_TYPE && Boolean(value?.sessionReuse) && hasProviderTrace(value) && Boolean(sessionIdFrom(value)) && Boolean(threadIdFrom(value)) && value?.session?.longLivedSession === true && value?.session?.codexStdio === true && value?.runner?.codexStdio === true && value?.runner?.writeCapable === true && value?.runner?.durableSession === true && Boolean(value?.workspace || value?.runner?.workspace) && ["workspace-write", "danger-full-access"].includes(value?.sandbox || value?.runner?.sandbox) && Array.isArray(value?.toolCalls) && value.toolCalls.some((tool) => tool?.status === "completed") && value?.longLivedSessionGate?.status === "pass" && value?.providerTrace?.protocol === CODEX_APP_SERVER_PROTOCOL && /\bcodex\s+app-server\s+--listen\s+stdio:\/\//u.test(String(value?.providerTrace?.command ?? "")) && ["completed", "succeeded"].includes(String(value?.providerTrace?.terminalStatus ?? "").toLowerCase()) && Boolean(value?.runnerTrace) && Boolean(value?.skills) ); } function isTrustedCodeAgentProvider(provider) { const normalized = String(provider ?? "").trim().toLowerCase(); return TRUSTED_CODE_AGENT_PROVIDERS.includes(normalized); } function isSourceFixtureCompletion(value) { const sourceKind = String(value?.sourceKind ?? value?.evidenceLevel ?? value?.providerTrace?.sourceKind ?? "").trim().toUpperCase(); const traceSource = String(value?.providerTrace?.source ?? value?.providerTrace?.mode ?? "").trim().toUpperCase(); return sourceKind === "SOURCE" || traceSource.startsWith("SOURCE"); } function latestChatResult() { return [...state.chatMessages].reverse().find((message) => message.role === "agent") ?? null; } function deriveAgentChatStatus() { if (state.chatPending) return "running"; const latest = latestChatResult(); if (latest?.status === "completed") return "completed"; if (latest?.status === "source") return "source"; if (latest?.status === "canceled") return "canceled"; if (latest?.status === "timeout") return "timeout"; if (latest?.status === "error") return "error"; if (latest?.status === "failed") return latest.error?.code === "provider_unavailable" ? "blocked" : "failed"; if (state.codeAgentAvailability?.status === "blocked") return "blocked"; return "idle"; } function currentConversationTone() { if (latestCompletedAgentMessage()) return "dev-live"; if (isSourceFixtureCompletedChatMessage(latestChatResult())) return "source"; if (state.codeAgentAvailability?.status === "blocked" || ["failed", "timeout", "canceled", "error"].includes(latestChatResult()?.status)) return "blocked"; return "source"; } function statusLabel(value) { const key = String(value ?? "unknown").trim(); const normalized = key.toLowerCase(); return STATUS_LABELS[normalized] ?? key; } function gateStatusLabel(value, statusKey) { const explicit = String(value ?? "").trim(); const labels = { pass: "通过", blocked: "阻塞", failed: "失败", pending: "待验证", info: "信息" }; return labels[statusKey] ?? (["通过", "阻塞", "失败", "待验证", "信息"].includes(explicit) ? explicit : "阻塞"); } function normalizeGateStatusKey(value) { const text = String(value ?? "").trim().toLowerCase(); if (["通过", "pass", "passed", "ok", "ready"].includes(text)) return "pass"; if (["失败", "failed", "failure", "error"].includes(text)) return "failed"; if (["待验证", "pending", "loading", "unknown"].includes(text)) return "pending"; if (["信息", "info", "information"].includes(text)) return "info"; return "blocked"; } function normalizeEvidenceList(value) { const items = Array.isArray(value) ? value : [value]; const normalized = items.map(oneLine).filter(Boolean); return normalized.length > 0 ? normalized : ["live 后端未返回 evidence 字段"]; } function oneLine(value) { return String(value ?? "").replace(/\s+/gu, " ").trim().slice(0, 240); } function roleLabel(role) { return ( { agent: "Agent", blocker: "阻塞", system: "系统", user: "用户" }[String(role ?? "").toLowerCase()] ?? statusLabel(role) ); } function recordKindLabel(kind) { return ( { audit: "审计", evidence: "证据", operation: "操作", record: "记录", report: "报告", trace: "轨迹" }[String(kind ?? "").toLowerCase()] ?? statusLabel(kind) ); } function internalGatePathnames() { return new Set(["/gate", "/diagnostics/gate"]); } function helpPathnames() { return new Set(["/help"]); } function skillsPathnames() { return new Set(["/skills"]); } function valueOrUnavailable(liveValue, sourceValue) { if (liveValue !== undefined && liveValue !== null) return liveValue; if (sourceValue !== undefined && sourceValue !== null) return `${sourceValue} 来源 SOURCE`; return "不可用"; } function shortTime(value) { return new Date(value).toISOString().replace("T", " ").replace(/\.\d{3}Z$/, "Z"); } function toneClass(tone) { return String(tone ?? "unknown").replace(/[^a-z0-9_-]/gi, "-").toLowerCase(); } function replaceChildren(parent, ...children) { parent.replaceChildren(...children.filter((child) => child && typeof child !== "string")); }