Files
pikasTech-HWLAB/web/hwlab-cloud-web/app.mjs
T
2026-05-23 07:21:26 +00:00

2351 lines
83 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { gateSummary } from "./gate-summary.mjs";
import { runtime } from "./runtime.mjs";
import { marked } from "./third_party/marked/marked.esm.js";
const DEFAULT_API_TIMEOUT_MS = 4500;
const DEFAULT_CODE_AGENT_TIMEOUT_MS = 150000;
const API_TIMEOUT_MS = resolveTimeoutMs("apiTimeoutMs", DEFAULT_API_TIMEOUT_MS, { min: 500, max: 30000 });
const CODE_AGENT_TIMEOUT_MS = resolveTimeoutMs("codeAgentTimeoutMs", DEFAULT_CODE_AGENT_TIMEOUT_MS, { min: 50, max: 300000 });
const rpcReadMethods = Object.freeze([
"system.health",
"cloud.adapter.describe",
"audit.event.query",
"evidence.record.query"
]);
const STATUS_LABELS = Object.freeze({
active: "活动",
available: "可用",
blocked_after_cloud_api: "cloud-api 后阻塞",
blocked: "待处理",
completed: "完成",
connected: "已连接",
degraded: "降级",
disabled: "禁用",
entry: "入口",
failed: "失败",
healthy: "健康",
live: "实况",
ok: "正常",
open: "未关闭",
pass: "通过",
pending: "等待",
running: "处理中",
probing: "探测中",
ready: "就绪",
recorded: "已记录",
sent: "已发送",
requires: "依赖",
source: "来源",
"dry-run": "演练",
"dev-live": "实况已验证"
});
const M3_TRUSTED_ROUTE = Object.freeze({
fromResourceId: "res_boxsimu_1",
fromPort: "DO1",
patchPanelServiceId: "hwlab-patch-panel",
toResourceId: "res_boxsimu_2",
toPort: "DI1"
});
const LIVE_M3_ID_FIELDS = Object.freeze(["operationId", "traceId", "auditId", "evidenceId"]);
const LIVE_M3_PASS_STATUSES = Object.freeze(["pass", "passed", "verified", "succeeded", "trusted", "completed"]);
const NON_LIVE_ID_VALUES = Object.freeze(["", "n/a", "none", "null", "undefined", "not_observed", "not-observed"]);
const TRUSTED_CODE_AGENT_PROVIDERS = Object.freeze(["openai-responses", "codex-cli"]);
const UNTRUSTED_CODE_AGENT_PROVIDER_PATTERN = /\b(?:echo|mock|fixture|stub|sample)\b/iu;
const viewIds = new Set([...document.querySelectorAll("[data-view]")].map((view) => view.dataset.view));
let rpcSequence = 0;
const el = {
shell: query("[data-app-shell]"),
explorerToggle: byId("explorer-toggle"),
explorerStatus: byId("explorer-status"),
treeCount: byId("tree-count"),
resourceTree: byId("resource-tree"),
capabilityCount: byId("capability-count"),
explorerCapabilities: byId("explorer-capabilities"),
routePath: byId("route-path"),
liveStatus: byId("live-status"),
liveDetail: byId("live-detail"),
agentChatStatus: byId("agent-chat-status"),
conversationList: byId("conversation-list"),
taskList: byId("task-list"),
traceList: byId("trace-list"),
unblockList: byId("unblock-list"),
gateMetadata: byId("gate-metadata"),
milestoneGrid: byId("milestone-grid"),
healthBody: byId("health-body"),
gateBlockers: byId("gate-blockers"),
validationList: byId("validation-list"),
commandForm: byId("command-form"),
commandInput: byId("command-input"),
commandSend: byId("command-send"),
commandClear: byId("command-clear"),
hardwareSourceStatus: byId("hardware-source-status"),
hardwareList: byId("hardware-list"),
controlList: byId("control-list"),
wiringBody: byId("wiring-body"),
recordsList: byId("records-list"),
diagnosticsList: byId("diagnostics-list"),
methodList: byId("method-list"),
m3ControlForm: byId("m3-control-form"),
m3ControlStatus: byId("m3-control-status"),
m3GatewaySelect: byId("m3-gateway-select"),
m3BoxSelect: byId("m3-box-select"),
m3PortSelect: byId("m3-port-select"),
m3ValueSelect: byId("m3-value-select"),
m3WriteDo: byId("m3-write-do"),
m3ReadDi: byId("m3-read-di"),
helpContent: byId("help-content"),
helpStatus: byId("help-status")
};
const state = {
conversationId: null,
codeAgentAvailability: null,
chatMessages: [],
chatPending: false,
liveSurface: null,
m3Control: {
contract: null,
operation: null,
pending: false
}
};
initRoutes();
initExplorerToggle();
syncMobileExplorer();
initSideTabs();
initQuickActions();
initCommandBar();
initM3Control();
renderStaticWorkbench();
renderProbePending();
loadHelpSurface();
loadLiveSurface().then(renderLiveSurface);
function byId(id) {
const element = document.getElementById(id);
if (!element) {
throw new Error(`missing required element #${id}`);
}
return element;
}
function query(selector) {
const element = document.querySelector(selector);
if (!element) {
throw new Error(`missing required element ${selector}`);
}
return element;
}
function resolveTimeoutMs(name, fallback, { min, max }) {
const config = globalThis.HWLAB_CLOUD_WEB_CONFIG?.timeouts ?? globalThis.HWLAB_CLOUD_WEB_TIMEOUTS ?? {};
const configured = Number(config?.[name]);
if (!Number.isFinite(configured)) return fallback;
return Math.min(Math.max(Math.trunc(configured), min), max);
}
function initRoutes() {
window.addEventListener("hashchange", () => showView(routeFromLocation()));
for (const button of document.querySelectorAll("[data-route]")) {
button.addEventListener("click", () => {
window.location.hash = button.dataset.route;
});
}
showView(routeFromLocation());
}
function routeFromLocation() {
const hashRoute = window.location.hash.replace(/^#\/?/, "");
if (viewIds.has(hashRoute)) return hashRoute;
if (helpPathnames().has(window.location.pathname.replace(/\/+$/, "") || "/")) return "help";
if (internalGatePathnames().has(window.location.pathname.replace(/\/+$/, "") || "/")) return "gate";
return "workspace";
}
function showView(route) {
const activeRoute = viewIds.has(route) ? route : "workspace";
for (const view of document.querySelectorAll("[data-view]")) {
view.hidden = view.dataset.view !== activeRoute;
}
for (const button of document.querySelectorAll("[data-route]")) {
const active = button.dataset.route === activeRoute;
button.classList.toggle("active", active);
button.setAttribute("aria-current", active ? "page" : "false");
}
}
function initExplorerToggle() {
el.explorerToggle.addEventListener("click", () => {
const collapsed = !el.shell.classList.contains("explorer-collapsed");
setExplorerCollapsed(collapsed);
});
}
function setExplorerCollapsed(collapsed) {
el.shell.classList.toggle("explorer-collapsed", collapsed);
el.explorerToggle.setAttribute("aria-pressed", collapsed ? "true" : "false");
el.explorerToggle.setAttribute("aria-expanded", collapsed ? "false" : "true");
}
function syncMobileExplorer() {
const media = window.matchMedia("(max-width: 860px)");
const apply = () => {
if (media.matches) {
setExplorerCollapsed(true);
} else {
setExplorerCollapsed(false);
}
};
apply();
media.addEventListener("change", apply);
}
async function loadHelpSurface() {
el.helpStatus.textContent = "加载中";
el.helpStatus.className = "state-tag tone-dry-run";
el.helpContent.dataset.helpState = "loading";
try {
const response = await fetch("/help.md", { cache: "no-store" });
if (!response.ok) {
throw new Error(`help.md HTTP ${response.status}`);
}
const markdown = await response.text();
el.helpContent.innerHTML = marked.parse(markdown, { gfm: true, breaks: false });
el.helpContent.dataset.helpState = "ready";
el.helpStatus.textContent = "已就绪";
el.helpStatus.className = "state-tag tone-source";
} catch (error) {
el.helpContent.dataset.helpState = "error";
el.helpContent.replaceChildren();
const notice = document.createElement("div");
notice.className = "help-error";
notice.textContent = `帮助内容加载失败:${error.message}。默认工作台仍可继续使用。`;
el.helpContent.append(notice);
el.helpStatus.textContent = "加载失败";
el.helpStatus.className = "state-tag tone-blocked";
}
}
function initSideTabs() {
for (const tab of document.querySelectorAll("[data-side-tab]")) {
tab.addEventListener("click", () => selectSideTab(tab.dataset.sideTab));
}
for (const jump of document.querySelectorAll("[data-side-tab-jump]")) {
jump.addEventListener("click", () => {
selectSideTab(jump.dataset.sideTabJump);
collapseExplorerAfterMobileAction();
});
}
}
function initQuickActions() {
for (const button of document.querySelectorAll("[data-focus-command]")) {
button.addEventListener("click", () => {
el.commandInput.focus();
collapseExplorerAfterMobileAction();
});
}
}
function collapseExplorerAfterMobileAction() {
if (window.matchMedia("(max-width: 860px)").matches) {
setExplorerCollapsed(true);
}
}
function selectSideTab(tabId) {
for (const tab of document.querySelectorAll("[data-side-tab]")) {
const active = tab.dataset.sideTab === tabId;
tab.classList.toggle("active", active);
tab.setAttribute("aria-selected", active ? "true" : "false");
}
for (const panel of document.querySelectorAll("[data-side-panel]")) {
const active = panel.dataset.sidePanel === tabId;
panel.classList.toggle("active", active);
panel.hidden = !active;
}
}
function initCommandBar() {
el.commandForm.addEventListener("submit", async (event) => {
event.preventDefault();
const value = el.commandInput.value.trim();
if (!value || state.chatPending) return;
const traceId = nextProtocolId("trc");
const userMessage = {
id: nextProtocolId("msg"),
role: "user",
title: `用户 ${shortTime(new Date().toISOString())}`,
text: value,
status: "sent",
traceId,
createdAt: new Date().toISOString()
};
const pendingMessage = {
id: nextProtocolId("msg"),
role: "agent",
title: "Code Agent 处理中",
text: `正在调用真实 Code Agent / Codex 后端,复杂问题可能需要 1-2 分钟;超时上限 ${Math.round(CODE_AGENT_TIMEOUT_MS / 1000)} 秒,失败时会保留输入供重试。`,
status: "running",
traceId,
createdAt: new Date().toISOString()
};
state.chatMessages.push(userMessage, pendingMessage);
state.chatPending = true;
el.commandInput.value = "";
renderAgentChatStatus("running");
renderConversation();
renderDrafts();
renderRecords(state.liveSurface);
try {
const result = await sendAgentMessage(value, state.conversationId, traceId);
state.conversationId = result.conversationId || result.sessionId || state.conversationId;
const index = state.chatMessages.findIndex((message) => message.id === pendingMessage.id);
const completion = classifyCodeAgentCompletion(result);
const status = completion.status;
state.chatMessages[index] = {
...pendingMessage,
title: completion.title,
text: completion.replied
? result.reply?.content || "Code Agent 没有返回文本。"
: result.status === "completed"
? untrustedCompletionMessage(result)
: failureMessage(result),
status,
traceId: result.traceId,
conversationId: result.conversationId || result.sessionId || state.conversationId,
messageId: result.messageId,
provider: result.provider,
model: result.model,
backend: result.backend,
sourceKind: completion.sourceKind,
providerTrace: result.providerTrace,
updatedAt: result.updatedAt,
error: result.error ?? (result.status === "completed" && !completion.replied
? {
code: "untrusted_completion",
message: "completed 回复缺少真实 provider/model/trace/conversation 证据,或 provider 属于 echo/mock/stub。"
}
: undefined),
availability: result.availability
};
if (result.availability) {
state.codeAgentAvailability = result.availability;
}
if (!completion.replied) {
el.commandInput.value = value;
}
renderAgentChatStatus(status, result);
} catch (error) {
const index = state.chatMessages.findIndex((message) => message.id === pendingMessage.id);
state.chatMessages[index] = {
...pendingMessage,
title: "发送失败",
text: `发送失败:${error.message}`,
status: "failed",
traceId: error.traceId || traceId,
updatedAt: new Date().toISOString(),
error: {
code: error.code || "request_failed",
message: error.message,
timeoutMs: error.timeoutMs
}
};
el.commandInput.value = value;
renderAgentChatStatus("failed");
} finally {
state.chatPending = false;
renderConversation();
renderDrafts();
renderRecords(state.liveSurface);
}
});
el.commandClear.addEventListener("click", () => {
state.chatMessages = [];
state.conversationId = null;
state.chatPending = false;
el.commandInput.value = "";
renderAgentChatStatus("idle");
renderConversation();
renderDrafts();
renderRecords(state.liveSurface);
});
}
function initM3Control() {
el.m3ControlForm.addEventListener("submit", async (event) => {
event.preventDefault();
await runM3IoAction("do.write");
});
el.m3ReadDi.addEventListener("click", async () => {
await runM3IoAction("di.read");
});
}
function renderStaticWorkbench() {
el.explorerStatus.textContent = "只读工作区";
el.explorerStatus.className = "status-dot tone-source";
el.routePath.textContent = "当前浏览器会话只保存任务草稿,硬件动作需通过受控后端流程。";
renderResourceTree();
renderCapabilityList();
renderConversation();
renderTaskList();
renderTrace();
renderUnblockList();
renderHardwareStatus(null);
renderDrafts();
renderM3ControlStatus();
renderWiringList(null);
renderRecords(null);
renderDiagnostics(null);
renderGateDiagnostics();
}
function renderProbePending() {
el.liveStatus.textContent = "只读模式";
el.liveStatus.className = "tone-source";
el.liveDetail.textContent = "可整理任务、查看资源、核对接线和可信记录;不会触发硬件变更。";
renderMethodList(rpcReadMethods, "pending");
}
async function sendAgentMessage(message, conversationId, traceId = nextProtocolId("trc")) {
const response = await fetchJson("/v1/agent/chat", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Trace-Id": traceId
},
timeoutMs: CODE_AGENT_TIMEOUT_MS,
timeoutName: "Code Agent",
body: JSON.stringify({
message,
conversationId,
traceId,
projectId: gateSummary.topology.projectId
})
});
if (!response.ok) {
if (isBlockedAgentResponse(response.data, response.status)) {
return normalizeBlockedAgentResult(response.data, response.status, traceId, response.error);
}
const error = new Error(response.timeout
? `Code Agent 请求超过 ${response.timeoutMs}ms 未完成;已保留输入,可稍后重试。`
: response.error || "Code Agent 请求失败");
error.traceId = traceId;
error.code = response.timeout ? "client_timeout" : "request_failed";
error.timeoutMs = response.timeoutMs;
throw error;
}
return {
...response.data,
traceId: response.data?.traceId || traceId
};
}
async function loadLiveSurface() {
const projectId = gateSummary.topology.projectId;
const [healthLive, restIndex, m3Control, health, adapter, audit, evidence] = await Promise.all([
fetchJson("/health/live"),
fetchJson("/v1"),
fetchJson("/v1/m3/io"),
callRpc("system.health"),
callRpc("cloud.adapter.describe"),
callRpc("audit.event.query", { projectId, limit: 6 }),
callRpc("evidence.record.query", { projectId, limit: 6 })
]);
return {
observedAt: new Date().toISOString(),
healthLive,
restIndex,
m3Control,
health,
adapter,
audit,
evidence
};
}
async function fetchJson(path, options = {}) {
const {
timeoutMs = API_TIMEOUT_MS,
timeoutName = path,
...fetchOptions
} = options;
const controller = new AbortController();
const timer = window.setTimeout(() => controller.abort(), timeoutMs);
try {
const response = await fetch(path, {
...fetchOptions,
headers: {
Accept: "application/json",
...(fetchOptions.headers ?? {})
},
signal: controller.signal
});
const text = await response.text();
let data = null;
try {
data = text ? JSON.parse(text) : null;
} catch (error) {
if (response.ok) throw error;
return {
ok: false,
path,
status: response.status,
data: null,
error: `${path} HTTP ${response.status}:响应不是 JSON`
};
}
if (!response.ok) {
return {
ok: false,
path,
status: response.status,
data,
error: `${path} HTTP ${response.status}${messageFromPayload(data)}`
};
}
return { ok: true, path, status: response.status, data };
} catch (error) {
const timedOut = error.name === "AbortError";
return {
ok: false,
path,
timeout: timedOut,
timeoutMs,
error: timedOut ? `${timeoutName} 请求超时 ${timeoutMs}ms` : `请求失败:${error.message}`
};
} finally {
window.clearTimeout(timer);
}
}
async function callRpc(method, params = {}) {
const id = nextProtocolId("req");
const traceId = nextProtocolId("trc");
const response = await fetchJson("/json-rpc", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
jsonrpc: "2.0",
id,
method,
params,
meta: {
traceId,
serviceId: "hwlab-cloud-web",
environment: "dev"
}
})
});
if (!response.ok) {
return { ...response, method };
}
if (response.data?.error) {
return {
ok: false,
path: response.path,
status: response.status,
method,
error: `RPC 错误 ${method}${response.data.error.message}`,
envelope: response.data
};
}
return {
ok: true,
path: response.path,
status: response.status,
method,
data: response.data?.result,
envelope: response.data
};
}
async function runM3IoAction(action) {
if (state.m3Control.pending) return;
const traceId = nextProtocolId("trc");
if (!m3ControlCanOperate()) {
state.m3Control.operation = {
status: "blocked",
action,
traceId,
blocker: {
code: state.m3Control.contract?.readiness?.blocker?.code ?? "m3_control_readiness_blocked",
zh: m3ControlBlockedReason()
},
observedAt: new Date().toISOString(),
sourceKind: "BLOCKED"
};
renderM3ControlStatus();
renderRecords(state.liveSurface);
renderDrafts();
return;
}
state.m3Control.pending = true;
state.m3Control.operation = {
status: "running",
action,
traceId,
observedAt: new Date().toISOString()
};
renderM3ControlStatus();
renderDrafts();
renderRecords(state.liveSurface);
const body = action === "do.write"
? {
action,
gatewayId: el.m3GatewaySelect.value,
resourceId: el.m3BoxSelect.value,
boxId: "boxsimu_1",
port: el.m3PortSelect.value,
value: el.m3ValueSelect.value === "true",
projectId: gateSummary.topology.projectId,
traceId
}
: {
action,
gatewayId: "gwsimu_2",
resourceId: M3_TRUSTED_ROUTE.toResourceId,
boxId: "boxsimu_2",
port: M3_TRUSTED_ROUTE.toPort,
projectId: gateSummary.topology.projectId,
traceId
};
const response = await fetchJson("/v1/m3/io", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Trace-Id": traceId,
"X-Actor-Id": "usr_hwlab_cloud_web"
},
body: JSON.stringify(body)
});
if (!response.ok) {
state.m3Control.operation = {
status: "blocked",
action,
traceId,
blocker: {
zh: response.error || "M3 IO 控制请求失败"
},
observedAt: new Date().toISOString(),
sourceKind: "BLOCKED"
};
} else {
state.m3Control.operation = {
...response.data,
sourceKind: response.data?.evidenceState?.status === "green" ? "DEV-LIVE" : "BLOCKED"
};
}
state.m3Control.pending = false;
renderM3ControlStatus();
renderHardwareStatus(runtimeSummaryFrom(state.liveSurface));
renderWiringList(runtimeSummaryFrom(state.liveSurface));
renderRecords(state.liveSurface);
renderDrafts();
}
function nextProtocolId(prefix) {
rpcSequence += 1;
return `${prefix}_cloud-web-workbench-${Date.now()}-${rpcSequence}`;
}
function renderLiveSurface(live) {
state.liveSurface = live;
state.m3Control.contract = live.m3Control?.ok ? live.m3Control.data : null;
const coreProbes = [live.healthLive, live.restIndex, live.health, live.adapter];
const surfaceStatus = workbenchApiSurfaceStatus(live, coreProbes);
const runtimeSummary = runtimeSummaryFrom(live);
const methods = methodsFrom(live);
el.liveStatus.textContent = surfaceStatus.label;
el.liveStatus.className = `tone-${toneClass(surfaceStatus.tone)}`;
state.codeAgentAvailability = codeAgentAvailabilityFrom(live);
el.liveDetail.textContent = surfaceStatus.detail ?? codeAgentAvailabilitySummary();
renderAgentChatStatus(deriveAgentChatStatus());
renderHardwareStatus(runtimeSummary);
renderWiringList(runtimeSummary);
renderRecords(live);
renderDiagnostics(live);
renderMethodList(methods.length > 0 ? methods.filter((method) => rpcReadMethods.includes(method)) : rpcReadMethods, surfaceStatus.methodTone);
renderM3ControlStatus();
renderConversation();
renderDrafts();
}
function workbenchApiSurfaceStatus(live, coreProbes = [live.healthLive, live.restIndex, live.health, live.adapter]) {
const surface = workbenchSurfaceReadiness(live, coreProbes);
if (!surface.reachable) {
return {
label: "离线查看",
tone: "source",
methodTone: "degraded",
detail: "同源 API 暂不可用,当前仍可使用本地草稿和已加载资源。"
};
}
if (surface.durableQueryBlocked) {
return {
label: "可信记录受阻 / 只读模式",
tone: "blocked",
methodTone: "degraded",
detail: "同源 API 可响应,但可信记录持久化尚未就绪;当前只保留只读查看和本地任务整理。"
};
}
if (surface.degraded) {
return {
label: "API 降级 / 只读模式",
tone: "degraded",
methodTone: "degraded",
detail: "同源 API 可响应但处于降级状态;当前保持只读查看和本地任务整理。"
};
}
return {
label: "只读连接可用",
tone: "dev-live",
methodTone: "live",
detail: null
};
}
function workbenchSurfaceReadiness(live, coreProbes = [live.healthLive, live.restIndex, live.health, live.adapter]) {
const payloads = coreProbes
.filter((probe) => probe?.ok)
.map((probe) => probe.data)
.filter((payload) => payload && typeof payload === "object");
const statuses = payloads.flatMap(readinessStatusValues).map(normalizedStatusValue);
const readyValues = payloads.flatMap(readinessBooleanValues);
return {
reachable: payloads.length > 0,
degraded:
statuses.some((status) => ["blocked", "degraded", "failed"].includes(status)) ||
readyValues.includes(false),
durableQueryBlocked: payloads.some(hasDurableRuntimeQueryBlock)
};
}
function readinessStatusValues(payload) {
return [
payload.status,
payload.readiness?.status,
payload.readiness?.durability?.status,
payload.runtime?.status
].filter((value) => value !== undefined && value !== null);
}
function readinessBooleanValues(payload) {
return [
payload.ready,
payload.readiness?.ready,
payload.readiness?.durability?.ready,
payload.runtime?.ready
].filter((value) => typeof value === "boolean");
}
function hasDurableRuntimeQueryBlock(payload) {
const codes = [
...(Array.isArray(payload.blockerCodes) ? payload.blockerCodes : []),
...(Array.isArray(payload.readiness?.blockerCodes) ? payload.readiness.blockerCodes : []),
...(Array.isArray(payload.blockers) ? payload.blockers.map((item) => item?.code) : []),
...(Array.isArray(payload.readiness?.blockers) ? payload.readiness.blockers.map((item) => item?.code) : []),
payload.runtime?.blocker,
payload.readiness?.durability?.blocker
].map(normalizedStatusValue);
const queryResult = normalizedStatusValue(payload.runtime?.connection?.queryResult ?? payload.readiness?.durability?.queryResult);
const blockedLayer = normalizedStatusValue(payload.runtime?.durabilityContract?.blockedLayer ?? payload.readiness?.durability?.blockedLayer);
return (
codes.includes("runtime_durable_adapter_query_blocked") ||
queryResult === "query_blocked" ||
blockedLayer === "durability_query"
);
}
function normalizedStatusValue(value) {
return String(value ?? "").trim().toLowerCase();
}
function renderResourceTree() {
const items = [
{
label: gateSummary.topology.projectId,
meta: "当前项目",
tone: "source",
children: [
...gateSummary.topology.gateways.map((gateway) => ({
label: gateway.gatewaySessionId,
meta: `${gateway.serviceId} / ${statusLabel(gateway.status)}`,
tone: gateway.status
})),
...gateSummary.topology.boxResources.map((resource) => ({
label: resource.resourceId,
meta: `${resourceTypeLabel(resource.resourceType)} / ${statusLabel(resource.state)}`,
tone: resource.state
})),
{
label: gateSummary.topology.patchPanel.patchPanelStatusId,
meta: `${gateSummary.topology.patchPanel.serviceId} / ${statusLabel(gateSummary.topology.patchPanel.state)}`,
tone: gateSummary.topology.patchPanel.state
}
]
},
{
label: gateSummary.agent.agentSessionId,
meta: `${gateSummary.agent.agentServiceId} / Agent 会话`,
tone: gateSummary.agent.agentStatus,
children: [
{
label: gateSummary.agent.workerSessionId,
meta: `${gateSummary.agent.workerServiceId} / ${statusLabel(gateSummary.agent.workerStatus)}`,
tone: gateSummary.agent.workerStatus
}
]
}
];
el.treeCount.textContent = String(items.reduce((count, item) => count + 1 + (item.children?.length ?? 0), 0));
replaceChildren(el.resourceTree, ...items.map(treeGroup));
}
function treeGroup(item) {
const details = document.createElement("details");
details.open = true;
const summary = document.createElement("summary");
summary.append(statusLight(item.tone), textSpan(item.label, "tree-label"), textSpan(item.meta, "tree-meta"));
details.append(summary);
for (const child of item.children ?? []) {
const row = document.createElement("button");
row.type = "button";
row.className = "tree-row";
row.append(statusLight(child.tone), textSpan(child.label, "tree-label"), textSpan(child.meta, "tree-meta"));
details.append(row);
}
return details;
}
function renderCapabilityList() {
const capabilities = [
{
title: "任务草稿",
detail: "在底部输入区记录要交给 Agent 的任务,不会直接发送硬件请求。",
tone: "source"
},
{
title: "资源查看",
detail: "查看 Gateway-SIMU、BOX-SIMU 与 hwlab-patch-panel 的只读资源状态。",
tone: "source"
},
{
title: "接线核对",
detail: "在右侧接线表核对源端口、接线盘、目标端口与证据记录。",
tone: "source"
}
];
el.capabilityCount.textContent = String(capabilities.length);
replaceChildren(el.explorerCapabilities, ...capabilities.map(infoCard));
replaceChildren(el.gateBlockers, ...gateSummary.blockers.map(blockerCard));
}
function renderConversation() {
const introMessages = [
{
role: "system",
title: "界面模式",
text: "这里是用户工作台:可以整理任务、查看资源、核对接线和可信记录。对话会发送到受控 Code Agent 后端;页面不会直接发送硬件变更。",
status: "source"
},
codeAgentStatusMessage(state.codeAgentAvailability)
];
replaceChildren(el.conversationList, ...[...introMessages, ...state.chatMessages].map(messageCard));
}
function renderTaskList() {
const rows = [
{
title: "1. 描述目标",
detail: "例如要检查哪个 BOX-SIMU、哪个端口、期望看到什么信号。",
tone: "source"
},
{
title: "2. 核对接线",
detail: "打开右侧“接线”表,确认源设备、接线盘、目标设备和轨迹/证据是否匹配。",
tone: "source"
},
{
title: "3. 留存记录",
detail: "在“可信记录”面板查看已有证据;新增硬件动作不从前端直接发起。",
tone: "source"
}
];
replaceChildren(el.taskList, ...rows.map(infoCard));
}
function renderTrace() {
replaceChildren(
el.traceList,
...gateSummary.steps.map((step) => {
const article = document.createElement("article");
article.className = "trace-item";
article.append(textSpan(String(step.order).padStart(2, "0"), "trace-order"));
const body = document.createElement("div");
body.append(textSpan(stepTitle(step.title), "trace-title"));
body.append(textSpan(`${stepKindLabel(step.kind)} / ${step.outputs.length} 个输出`, "trace-meta"));
article.append(body, badge(step.requires.length === 0 ? "入口" : "依赖前序", step.requires.length === 0 ? "source" : "dry-run"));
return article;
})
);
}
function renderUnblockList() {
const rows = [
{
title: "DB live readiness",
detail: "先让 cloud-api /health/live 返回 db.ready=true、db.connected=true,再进入 Agent runtime 调度。",
tone: "blocked"
},
{
title: "patch-panel DO1 -> DI1",
detail: "再由 hwlab-patch-panel 建立 res_boxsimu_1:DO1 -> res_boxsimu_2:DI1 的活动链路,并附带可信记录。",
tone: "blocked"
},
{
title: "证据升级规则",
detail: "源码、本地与演练记录只能作为解释材料,不能升级为实况验收证据。",
tone: "dry-run"
}
];
replaceChildren(el.unblockList, ...rows.map(infoCard));
}
function renderHardwareStatus(summary) {
const counts = summary?.counts ?? null;
const patchPanel = gateSummary.topology.patchPanel;
const sourceLink = trustedM3Link(patchPanel.activeConnections);
const liveM3Evidence = trustedM3LiveEvidenceFrom(summary) ?? m3ControlLiveEvidence();
const m3Live = hasTrustedM3LiveEvidence(liveM3Evidence);
const controlReady = m3ControlCanOperate();
const runtimeCountsSourceKind = "SOURCE";
const runtimeCountsTone = "source";
const patchTone = m3Live ? "live" : patchPanel.state === "active" ? "source" : patchPanel.state;
const linkTone = m3Live ? "live" : sourceLink || patchPanel.activeConnectionCount > 0 ? "degraded" : "blocked";
el.hardwareSourceStatus.textContent = m3Live ? "实况已验证" : "只读核对";
el.hardwareSourceStatus.className = `state-tag tone-${toneClass(m3Live ? "dev-live" : "source")}`;
replaceChildren(
el.hardwareList,
hardwareGroup({
title: "Gateway/Box 在线",
rows: [
{
label: "Gateway-SIMU",
value: counts ? `${counts.gatewaySessions ?? 0} 会话` : "离线查看",
detail: counts
? `${counts.gatewaySessions ?? 0} 个 Gateway 会话来自只读运行计数;仅用于资源核对。`
: `${gateSummary.gatewaySessionCount} 个 Gateway 会话来自内置拓扑。`,
sourceKind: runtimeCountsSourceKind,
tone: runtimeCountsTone
},
{
label: "BOX-SIMU",
value: counts ? `${counts.boxResources ?? 0} 资源` : "离线查看",
detail: counts
? `${counts.boxResources ?? 0} 个 BOX 资源来自只读运行计数;仅用于资源核对。`
: `${gateSummary.boxResourceCount} 个 BOX 资源来自内置拓扑。`,
sourceKind: runtimeCountsSourceKind,
tone: runtimeCountsTone
}
]
}),
hardwareGroup({
title: "BOX-SIMU 端口",
rows: [
{
label: "DO1",
value: m3Live ? "已验证" : "待接线",
detail: m3Live ? m3LiveDetail(liveM3Evidence) : "尚未看到 DO1 电平的完整可信记录。",
sourceKind: m3Live ? "DEV-LIVE" : "BLOCKED",
tone: m3Live ? "live" : "blocked"
},
{
label: "DI1",
value: m3Live ? "已验证" : "待回读",
detail: m3Live ? m3LiveDetail(liveM3Evidence) : "尚未看到 DI1 回读的完整可信记录。",
sourceKind: m3Live ? "DEV-LIVE" : "BLOCKED",
tone: m3Live ? "live" : "blocked"
},
{
label: "AI/AO/FREQ",
value: "后续能力",
detail: "当前未声明模拟量或频率闭环,按后续能力处理。",
sourceKind: "SOURCE",
tone: "disabled"
}
]
}),
hardwareGroup({
title: "Patch Panel 连线",
rows: [
{
label: "hwlab-patch-panel",
value: statusLabel(patchPanel.state),
detail: m3Live
? `接线盘实况记录 ${liveM3Evidence.patchPanelLiveReport.reportId ?? liveM3Evidence.patchPanelLiveReport.patchPanelStatusId} 已绑定目标链路。`
: `${patchPanel.activeConnectionCount} 条内置接线记录;工作台只展示目标链路,未升级为可信闭环证据。`,
sourceKind: m3Live ? "DEV-LIVE" : "SOURCE",
tone: patchTone
},
{
label: "DO1 -> patch-panel -> DI1",
value: linkStatusLabel(linkTone),
detail: m3LinkDetail({ liveM3Evidence, sourceLink, patchPanel, counts }),
sourceKind: m3Live ? "DEV-LIVE" : "BLOCKED",
tone: linkTone
}
]
}),
hardwareGroup({
title: "写入边界",
rows: [
{
label: "硬件控制",
value: controlReady ? "受控路径" : "受控路径受阻",
detail: controlReady
? "控制只通过受控后端执行,并经由 gateway-simu、box-simu 与 patch-panel;页面不直连模拟器,也不伪造硬件状态。"
: `${m3ControlBlockedReason()} 页面不直连模拟器,也不伪造硬件状态。`,
sourceKind: controlReady ? "SOURCE" : "BLOCKED",
tone: controlReady ? "source" : "blocked"
}
]
})
);
}
function hardwareGroup(group) {
const article = document.createElement("article");
article.className = "hardware-section";
article.append(textSpan(group.title, "hardware-section-title"));
const rows = document.createElement("div");
rows.className = "hardware-rows";
rows.append(...group.rows.map(hardwareRow));
article.append(rows);
return article;
}
function hardwareRow(item) {
const row = document.createElement("div");
row.className = `hardware-row tone-border-${toneClass(item.tone)}`;
row.dataset.sourceKind = item.sourceKind;
const head = document.createElement("div");
head.className = "hardware-row-head";
head.append(textSpan(item.label, "hardware-row-label"), badge(item.badgeLabel ?? hardwareEvidenceLabel(item.sourceKind), item.sourceKind));
row.append(head);
row.append(textSpan(item.value, `hardware-row-value tone-${toneClass(item.tone)}`));
row.append(textSpan(item.detail, "hardware-row-detail"));
return row;
}
function trustedM3Link(connections) {
return (connections ?? []).find(
(link) =>
String(link.fromResourceId ?? "") === M3_TRUSTED_ROUTE.fromResourceId &&
normalizePort(link.fromPort) === "DO1" &&
String(link.toResourceId ?? "") === M3_TRUSTED_ROUTE.toResourceId &&
normalizePort(link.toPort) === "DI1" &&
connectionPatchPanelServiceId(link) === M3_TRUSTED_ROUTE.patchPanelServiceId
);
}
function m3LinkDetail({ liveM3Evidence, sourceLink, patchPanel, counts }) {
if (hasTrustedM3LiveEvidence(liveM3Evidence)) {
return `实况链路:${M3_TRUSTED_ROUTE.fromResourceId}:${M3_TRUSTED_ROUTE.fromPort} -> ${M3_TRUSTED_ROUTE.patchPanelServiceId} -> ${M3_TRUSTED_ROUTE.toResourceId}:${M3_TRUSTED_ROUTE.toPort}${m3LiveDetail(liveM3Evidence)}。`;
}
if (sourceLink) {
return `来源拓扑包含目标接线 ${sourceLink.fromResourceId}:${sourceLink.fromPort} -> ${patchPanel.serviceId} -> ${sourceLink.toResourceId}:${sourceLink.toPort},仍在等待完整可信记录。`;
}
if (counts) {
return "只读运行计数已返回,但还不能证明接线盘闭环。";
}
if (patchPanel.activeConnectionCount > 0) {
return `当前只看到非目标接线;目标链路 ${M3_TRUSTED_ROUTE.fromResourceId}:${M3_TRUSTED_ROUTE.fromPort} -> ${M3_TRUSTED_ROUTE.patchPanelServiceId} -> ${M3_TRUSTED_ROUTE.toResourceId}:${M3_TRUSTED_ROUTE.toPort} 仍在等待可信记录。`;
}
return "当前还没有 DO1 -> patch-panel -> DI1 的可信链路记录。";
}
function hardwareEvidenceLabel(sourceKind) {
const normalized = String(sourceKind ?? "").toUpperCase();
return (
{
SOURCE: "来源拓扑",
BLOCKED: "待可信记录",
"DEV-LIVE": "实况已验证",
"DRY-RUN": "演练记录"
}[normalized] ?? statusLabel(sourceKind)
);
}
function linkStatusLabel(status) {
return (
{
live: "live",
degraded: "degraded",
blocked: "blocked"
}[status] ?? statusLabel(status)
);
}
function normalizePort(port) {
return String(port ?? "").trim().toUpperCase();
}
function trustedM3LiveEvidenceFrom(summary) {
if (!isDurableRuntimeReady(summary)) return null;
const candidates = [
summary?.m3TrustedLoop,
summary?.m3HardwareLoop,
summary?.hardware?.m3TrustedLoop,
summary?.patchPanel?.m3TrustedLoop,
summary?.liveM3Evidence
].filter(Boolean);
return candidates.find(hasTrustedM3LiveEvidence) ?? null;
}
function m3ControlLiveEvidence() {
const operation = state.m3Control.operation;
if (!operation || operation.status !== "succeeded") return null;
return {
status: operation.evidenceState?.status === "green" ? "verified" : "blocked",
sourceKind: operation.evidenceState?.status === "green" ? "DEV-LIVE" : "BLOCKED",
live: operation.evidenceState?.status === "green",
operationId: operation.operationId,
traceId: operation.traceId,
auditId: operation.auditId,
evidenceId: operation.evidenceId,
patchPanelLiveReport: {
serviceId: M3_TRUSTED_ROUTE.patchPanelServiceId,
sourceKind: operation.evidenceState?.status === "green" ? "DEV-LIVE" : "BLOCKED",
live: operation.evidenceState?.status === "green",
reportId: operation.evidenceId,
activeConnections: [
{
fromResourceId: M3_TRUSTED_ROUTE.fromResourceId,
fromPort: M3_TRUSTED_ROUTE.fromPort,
toResourceId: M3_TRUSTED_ROUTE.toResourceId,
toPort: M3_TRUSTED_ROUTE.toPort,
patchPanelServiceId: M3_TRUSTED_ROUTE.patchPanelServiceId
}
]
}
};
}
function liveRecordTone(record, live) {
const runtimeSummary = runtimeSummaryFrom(live);
return isDurableRuntimeReady(runtimeSummary) && hasTrustedM3LiveEvidence(record) ? "dev-live" : "blocked";
}
function liveRecordDurabilityBlocker(live) {
const runtimeSummary = runtimeSummaryFrom(live);
if (isDurableRuntimeReady(runtimeSummary)) return null;
if (runtimeSummary?.durableRequested === true || runtimeSummary?.adapter === "postgres") {
return `runtime durable adapter 未 readystatus=${runtimeSummary?.status ?? "unknown"}durable=${runtimeSummary?.durable === true});可信记录保持 BLOCKED。`;
}
if (runtimeSummary?.durable !== true) {
return "runtime.durable=false;只读 audit/evidence 不能标为 durable ready 或 DEV-LIVE。";
}
return `runtime durable adapter 未 readystatus=${runtimeSummary?.status ?? "unknown"});可信记录保持 BLOCKED。`;
}
function isDurableRuntimeReady(summary) {
if (!summary || typeof summary !== "object") return false;
if (summary.durable !== true) return false;
if (summary.ready === false) return false;
if (summary.liveRuntimeEvidence !== true) return false;
return !["blocked", "degraded", "failed"].includes(String(summary.status ?? "").toLowerCase());
}
function hasTrustedM3LiveEvidence(candidate) {
if (!candidate || typeof candidate !== "object") return false;
if (candidate.dryRun === true || candidate.fixture === true || candidate.sourceKind === "SOURCE" || candidate.sourceKind === "DRY-RUN") return false;
if (!hasLiveSourceKind(candidate)) return false;
if (!LIVE_M3_PASS_STATUSES.includes(String(candidate.status ?? candidate.outcome ?? "verified").toLowerCase())) return false;
if (!LIVE_M3_ID_FIELDS.every((field) => isLiveIdentifier(candidate[field] ?? candidate.operation?.[field] ?? candidate.identifiers?.[field]))) return false;
const patchPanelLiveReport = candidate.patchPanelLiveReport ?? candidate.patchPanelReport;
return hasTrustedPatchPanelLiveReport(patchPanelLiveReport);
}
function hasTrustedPatchPanelLiveReport(report) {
if (!report || typeof report !== "object") return false;
if (report.dryRun === true || report.fixture === true || report.sourceKind === "SOURCE" || report.sourceKind === "DRY-RUN") return false;
if (!hasLiveSourceKind(report)) return false;
if (report.serviceId !== M3_TRUSTED_ROUTE.patchPanelServiceId) return false;
if (!isLiveIdentifier(report.reportId ?? report.patchPanelStatusId ?? report.statusId ?? report.evidenceId)) return false;
return (report.activeConnections ?? report.connections ?? []).some((link) => trustedM3Link([link]));
}
function hasLiveSourceKind(value) {
return value.live === true || value.sourceKind === "DEV-LIVE" || value.evidenceLevel === "DEV-LIVE";
}
function isLiveIdentifier(value) {
const text = String(value ?? "").trim();
return text !== "" && !NON_LIVE_ID_VALUES.includes(text.toLowerCase());
}
function connectionPatchPanelServiceId(link) {
return String(link.patchPanelServiceId ?? link.patchPanel?.serviceId ?? link.serviceId ?? M3_TRUSTED_ROUTE.patchPanelServiceId);
}
function m3LiveDetail(evidence) {
return `operation=${evidence.operationId} / trace=${evidence.traceId} / audit=${evidence.auditId} / evidence=${evidence.evidenceId}`;
}
function renderM3ControlStatus() {
const contract = state.m3Control.contract;
const operation = state.m3Control.operation;
const controlReady = m3ControlCanOperate(contract);
const contractObserved = Boolean(contract);
const pending = state.m3Control.pending;
const operationSucceeded = operation?.status === "succeeded";
const operationBlocked = operation?.status === "blocked";
const label = pending
? "执行中"
: operationSucceeded
? operation.evidenceState?.status === "green"
? "控制可达 / 记录完整"
: "控制可达 / 等待记录"
: operationBlocked
? "操作待处理"
: controlReady
? "受控路径可执行"
: contractObserved
? "受控路径受阻"
: "等待探测";
const tone = pending
? "dry-run"
: operationSucceeded
? operation.evidenceState?.status === "green" ? "dev-live" : "degraded"
: operationBlocked || !controlReady
? "blocked"
: "source";
el.m3ControlStatus.textContent = label;
el.m3ControlStatus.className = `state-tag tone-${toneClass(tone)}`;
el.m3ControlStatus.title = operation?.blocker?.zh ?? operation?.evidenceState?.reason ?? m3ControlBlockedReason(contract);
const disabled = pending || !controlReady;
for (const input of [el.m3GatewaySelect, el.m3BoxSelect, el.m3PortSelect, el.m3ValueSelect, el.m3WriteDo, el.m3ReadDi]) {
input.disabled = disabled;
}
el.m3WriteDo.textContent = pending ? "执行中" : "写入 DO1";
}
function m3ControlCanOperate(contract = state.m3Control.contract) {
return contract?.status === "available" &&
contract?.readiness?.status === "ready" &&
contract?.readiness?.controlReady === true &&
contract?.readiness?.sourceKind === "DEV-LIVE" &&
contract?.readiness?.evidenceLevel === "DEV-LIVE";
}
function m3ControlBlockedReason(contract = state.m3Control.contract) {
if (!contract) return "等待 cloud-api 返回 M3 IO 只读 readiness;未确认前控制保持阻塞。";
return contract.readiness?.blocker?.zh ??
contract.blockedReason ??
"M3 IO 只读 readiness 未 green;控制面板保持阻塞,不直连 gateway/box-simu。";
}
function controlRows() {
const operation = state.m3Control.operation;
const controlReady = m3ControlCanOperate();
const operationCards = operation
? [
{
title: `M3 ${operationActionLabel(operation.action)} ${statusLabel(operation.status)}`,
detail: m3OperationDetail(operation),
tone: operation.status === "succeeded"
? operation.evidenceState?.status === "green" ? "dev-live" : "degraded"
: operation.status === "running"
? "dry-run"
: "blocked"
}
]
: [];
return [
{
title: "控制路径",
detail: controlReady
? "按钮只走受控后端,再通过 gateway-simu -> box-simu -> hwlab-patch-panel 执行 M3 DO/DI。"
: `${m3ControlBlockedReason()} 不可用时不会直连 gateway/box-simu,也不会伪造硬件状态。`,
tone: controlReady ? "source" : "blocked"
},
...operationCards,
{
title: "读取目标",
detail: `${M3_TRUSTED_ROUTE.toResourceId}:${M3_TRUSTED_ROUTE.toPort} 只能通过受控路径读取;浏览器不直连 box-simu。`,
tone: "source"
},
{
title: "Code Agent 对话",
detail: codeAgentControlSummary(state.codeAgentAvailability),
tone: currentConversationTone()
},
{
title: "只读探测",
detail: "内部复核探测保持只读,并放在二级页面。",
tone: "source"
}
];
}
function operationActionLabel(action) {
return action === "do.write" ? "DO 写入" : action === "di.read" ? "DI 读取" : "操作";
}
function m3OperationDetail(operation) {
if (operation.status === "running") {
return `trace=${operation.traceId} / 正在通过 cloud-api 提交。`;
}
const ids = [
recordField("operation", operation.operationId),
recordField("trace", operation.traceId),
recordField("audit", operation.auditId),
recordField("evidence", operation.evidenceId)
].filter(Boolean).join(" / ");
const value = operation.result?.value !== undefined && operation.result?.value !== null
? ` / value=${String(operation.result.value)}`
: "";
const blocker = operation.blocker?.zh || operation.blocker?.message
? ` / 阻塞原因=${operation.blocker.zh || operation.blocker.message}`
: "";
const evidence = operation.evidenceState?.reason ? ` / evidence=${operation.evidenceState.reason}` : "";
return `${ids}${value}${blocker}${evidence}`;
}
function renderDrafts() {
const rows = [
...state.chatMessages.slice(-3).reverse().map((message) => ({
title: `${roleLabel(message.role)} ${statusLabel(message.status)}`,
detail: message.text,
tone: message.status || "source"
})),
...(state.chatMessages.length === 0
? [
{
title: "对话状态",
detail: codeAgentPromptText(state.codeAgentAvailability),
tone: state.codeAgentAvailability?.status === "blocked" ? "blocked" : "source"
}
]
: []),
...controlRows()
];
replaceChildren(el.controlList, ...rows.map(infoCard));
}
function renderWiringList(summary = null) {
const patchPanel = gateSummary.topology.patchPanel;
const liveM3Evidence = trustedM3LiveEvidenceFrom(summary);
const m3Live = hasTrustedM3LiveEvidence(liveM3Evidence);
const traceEvidence = m3Live
? [liveM3Evidence.operationId, liveM3Evidence.traceId, liveM3Evidence.auditId, liveM3Evidence.evidenceId].join(" / ")
: "等待可信记录";
const tr = document.createElement("tr");
tr.append(cell(M3_TRUSTED_ROUTE.fromResourceId, "mono wrap"));
tr.append(cell(M3_TRUSTED_ROUTE.fromPort, "mono"));
tr.append(cell(M3_TRUSTED_ROUTE.patchPanelServiceId, "mono wrap"));
tr.append(cell(M3_TRUSTED_ROUTE.toResourceId, "mono wrap"));
tr.append(cell(M3_TRUSTED_ROUTE.toPort, "mono"));
const status = document.createElement("td");
status.append(badge(m3Live ? "实况已验证" : "待可信记录", m3Live ? "dev-live" : "blocked"));
tr.append(status);
tr.append(cell(m3Live ? "实况可信记录" : "来源拓扑", "wrap"));
tr.append(cell(traceEvidence, "mono wrap"));
replaceChildren(el.wiringBody, tr);
}
function renderRecords(live) {
const groups = [
recordGroup({
title: "Code Agent 对话记录",
meta: "trace/messageId",
cards: codeAgentRecordCards()
}),
recordGroup({
title: "接线/operation",
meta: "hwlab-patch-panel + operation",
cards: operationRecordCards()
}),
recordGroup({
title: "audit 记录",
meta: "audit.event.query + SOURCE 回退",
cards: auditRecordCards(live)
}),
recordGroup({
title: "evidence 证据",
meta: "evidence.record.query + SOURCE 回退",
cards: evidenceRecordCards(live)
})
];
replaceChildren(el.recordsList, ...groups);
}
function codeAgentRecordCards() {
const messages = state.chatMessages.filter((message) => message.traceId || message.messageId);
if (messages.length === 0) {
return [
infoCard({
title: "尚无 Code Agent 请求记录",
detail: "发送消息后会在这里显示 conversationId、traceId 和 messageId;未发送时仅显示 SOURCE 说明。",
tone: "source"
})
];
}
return messages.slice(-4).reverse().map((message) =>
infoCard({
title: `${roleLabel(message.role)} ${statusLabel(message.status)}`,
detail: [
sourceKindLabel(message.status === "failed" ? "blocked" : "source"),
recordField("conversation", state.conversationId),
recordField("trace", message.traceId),
recordField("message", message.messageId),
message.error?.message ? `失败原因=${safeFailureReason(message.error.message)}` : null
].filter(Boolean).join(" / "),
tone: message.status === "failed" ? "blocked" : "source"
})
);
}
function operationRecordCards() {
const wiring = gateSummary.topology.patchPanel;
const controlOperation = state.m3Control.operation;
const controlCards = controlOperation
? [
infoCard({
title: controlOperation.operationId ?? controlOperation.traceId ?? "m3-control-operation",
detail: [
sourceKindLabel(controlOperation.evidenceState?.status === "green" ? "dev-live" : "blocked_after_cloud_api"),
"via=/v1/m3/io",
recordField("action", controlOperation.action),
recordField("trace", controlOperation.traceId),
recordField("audit", controlOperation.auditId),
recordField("evidence", controlOperation.evidenceId),
controlOperation.controlPath?.frontendBypass === false ? "frontendBypass=false" : null,
controlOperation.evidenceState?.reason
].filter(Boolean).join(" / "),
tone: controlOperation.status === "succeeded"
? controlOperation.evidenceState?.status === "green" ? "dev-live" : "degraded"
: "blocked"
})
]
: [];
const wiringCards = wiring.activeConnections.map((link) =>
infoCard({
title: `${link.fromResourceId}:${link.fromPort} -> ${link.toResourceId}:${link.toPort}`,
detail: [
sourceKindLabel("source"),
recordField("patch-panel", wiring.serviceId),
`状态=${statusLabel(wiring.state)}`,
recordField("statusId", wiring.patchPanelStatusId)
].filter(Boolean).join(" / "),
tone: "source"
})
);
const operationCards = gateSummary.operations.map((operation) =>
infoCard({
title: operation.operationId,
detail: [
sourceKindLabel(operation.dryRun ? "dry-run" : "source"),
`状态=${statusLabel(operation.status)}`,
recordField("resource", operation.resourceId),
recordField("capability", operation.capabilityId),
recordField("worker", operation.workerSessionId)
].filter(Boolean).join(" / "),
tone: operation.dryRun ? "dry-run" : "source"
})
);
return [...controlCards, ...wiringCards, ...operationCards];
}
function auditRecordCards(live) {
const cards = [];
if (state.m3Control.operation?.auditEvent) {
cards.push(auditCard(
state.m3Control.operation.auditEvent,
state.m3Control.operation.evidenceState?.status === "green" ? "dev-live" : "blocked",
state.m3Control.operation.evidenceState?.status === "green" ? null : state.m3Control.operation.evidenceState?.reason
));
}
if (live) {
if (live.audit.ok) {
const liveEvents = live.audit.data?.events ?? [];
if (liveEvents.length === 0) {
cards.push(infoCard({
title: "audit.event.query",
detail: "只读查询成功但无 M3 trusted loop audit;下方显示 SOURCE/DRY-RUN 回退摘要,不能冒充 DEV-LIVE。",
tone: "source"
}));
} else {
cards.push(...liveEvents.map((event) => auditCard(event, liveRecordTone(event, live), liveRecordDurabilityBlocker(live))));
}
} else {
cards.push(liveFailureCard("audit.event.query", live.audit.error));
}
} else {
cards.push(infoCard({
title: "audit.event.query",
detail: "等待同源只读查询;未完成前显示 SOURCE/DRY-RUN 回退摘要。",
tone: "source"
}));
}
cards.push(...gateSummary.auditEvents.slice(0, 4).map((event) => auditCard(event, event.dryRun ? "dry-run" : "source")));
return cards;
}
function evidenceRecordCards(live) {
const cards = [];
if (state.m3Control.operation?.evidenceRecord) {
cards.push(evidenceCard(
state.m3Control.operation.evidenceRecord,
state.m3Control.operation.evidenceState?.status === "green" ? "dev-live" : "blocked",
state.m3Control.operation.evidenceState?.status === "green" ? null : state.m3Control.operation.evidenceState?.reason
));
}
if (live) {
if (live.evidence.ok) {
const liveRecords = live.evidence.data?.records ?? [];
if (liveRecords.length === 0) {
cards.push(infoCard({
title: "evidence.record.query",
detail: "只读查询成功但无 M3 trusted loop evidence;下方显示 SOURCE/DRY-RUN 回退摘要,不能冒充 DEV-LIVE。",
tone: "source"
}));
} else {
cards.push(...liveRecords.map((record) => evidenceCard(record, liveRecordTone(record, live), liveRecordDurabilityBlocker(live))));
}
} else {
cards.push(liveFailureCard("evidence.record.query", live.evidence.error));
}
} else {
cards.push(infoCard({
title: "evidence.record.query",
detail: "等待同源只读查询;未完成前显示 SOURCE/DRY-RUN 回退摘要。",
tone: "source"
}));
}
cards.push(...gateSummary.evidenceRecords.map((record) => evidenceCard(record, record.dryRun ? "dry-run" : "source")));
return cards;
}
function auditCard(event, tone, durabilityBlocker = null) {
return infoCard({
title: event.auditId,
detail: [
sourceKindLabel(tone),
durabilityBlocker,
tone === "blocked" ? "只读 audit 缺少 M3 patch-panel live report / operation / trace / evidence 绑定" : null,
recordField("action", event.action),
recordField("target", event.targetId),
recordField("operation", event.operationId),
recordField("trace", event.traceId),
recordField("service", event.serviceId)
].filter(Boolean).join(" / "),
tone
});
}
function evidenceCard(record, tone, durabilityBlocker = null) {
return infoCard({
title: record.evidenceId,
detail: [
sourceKindLabel(tone),
durabilityBlocker,
tone === "blocked" ? "只读 evidence 缺少 M3 patch-panel live report / operation / trace / audit 绑定" : null,
recordKindLabel(record.kind ?? "record"),
recordField("operation", record.operationId),
recordField("trace", record.traceId ?? record.metadata?.traceId),
recordField("service", record.serviceId)
].filter(Boolean).join(" / "),
tone
});
}
function liveFailureCard(title, reason) {
return infoCard({
title,
detail: `阻塞 BLOCKED:开发实况查询失败,失败原因:${reason || "未知错误"};当前显示 SOURCE 回退,不能冒充 DEV-LIVE。`,
tone: "blocked"
});
}
function recordGroup({ title, meta, cards }) {
const section = document.createElement("section");
section.className = "record-group";
const header = document.createElement("div");
header.className = "record-group-head";
header.append(textSpan(title, "record-group-title"), textSpan(meta, "record-group-meta"));
const body = document.createElement("div");
body.className = "record-group-list";
body.append(...cards);
section.append(header, body);
return section;
}
function recordField(label, value) {
if (value === undefined || value === null || value === "") return null;
return `${label}=${value}`;
}
const sensitiveFailureReasonNeedles = Object.freeze([
["OPENAI", "API", "KEY"].join("_"),
["hwlab", "code", "agent", "provider"].join("-"),
["Se", "cret"].join(""),
["secret", "Ref"].join("")
]);
function safeFailureReason(value) {
const text = String(value ?? "").trim();
if (!text) return "未知错误";
const lowerText = text.toLowerCase();
if (sensitiveFailureReasonNeedles.some((needle) => lowerText.includes(needle.toLowerCase()))) {
return "服务暂不可用";
}
return text.length > 120 ? `${text.slice(0, 117)}...` : text;
}
function sourceKindLabel(tone) {
return statusLabel(tone);
}
function renderDiagnostics(live) {
const diagnostics = [
{
title: "前端",
detail: runtime.endpoints.frontend,
tone: "source"
},
{
title: "API 边界",
detail: runtime.endpoints.api,
tone: "source"
},
{
title: "运行路由",
detail: runtime.serviceRoute.join(" -> "),
tone: "source"
}
];
if (live) {
diagnostics.push(
{
title: "/health/live",
detail: live.healthLive.ok ? `HTTP ${live.healthLive.status} ${live.healthLive.data?.status ?? "ok"}` : live.healthLive.error,
tone: live.healthLive.ok ? live.healthLive.data?.status ?? "dev-live" : "blocked"
},
{
title: "/v1",
detail: live.restIndex.ok ? `HTTP ${live.restIndex.status} ${live.restIndex.data?.status ?? "ok"}` : live.restIndex.error,
tone: live.restIndex.ok ? live.restIndex.data?.status ?? "dev-live" : "blocked"
},
{
title: "system.health",
detail: live.health.ok ? `RPC ${live.health.data?.status ?? "ok"}` : live.health.error,
tone: live.health.ok ? live.health.data?.status ?? "dev-live" : "blocked"
},
{
title: "audit.event.query",
detail: live.audit.ok ? `${live.audit.data?.count ?? live.audit.data?.events?.length ?? 0} 条记录` : live.audit.error,
tone: live.audit.ok ? "dev-live" : "blocked"
}
);
} else {
diagnostics.push(
{
title: "/health/live",
detail: "正在探测同源 Web/API 健康状态",
tone: "dev-live"
},
{
title: "/v1 + /json-rpc",
detail: "只读诊断等待中",
tone: "source"
}
);
}
replaceChildren(el.diagnosticsList, ...diagnostics.map(infoCard));
}
function renderGateDiagnostics() {
appendMetricGrid(el.gateMetadata, [
["环境", gateSummary.environment, "source"],
["前端", runtime.endpoints.frontend, "source"],
["API", runtime.endpoints.api, "source"],
["Gate", statusLabel(gateSummary.gateStatus), gateSummary.gateStatus],
["演练 DRY-RUN", statusLabel(gateSummary.dryRun.status), "dry-run"],
["报告", shortHash(gateSummary.reportCommitId), "source"]
]);
replaceChildren(
el.milestoneGrid,
...gateSummary.milestones.map((milestone) => {
const item = document.createElement("article");
item.className = "milestone-card";
const header = document.createElement("div");
header.className = "panel-title-row";
header.append(textSpan(milestone.id, "milestone-id"), badge(statusLabel(milestone.status), milestone.status));
item.append(header);
item.append(textSpan(userFacingSummary(milestone.summary), "milestone-summary"));
item.append(textSpan(`${milestone.commandCount} 条命令 / ${milestone.evidenceCount} 条证据`, "milestone-meta"));
return item;
})
);
el.healthBody.replaceChildren();
for (const row of gateSummary.health) {
const tr = document.createElement("tr");
tr.append(cell(componentLabel(row.component)));
tr.append(cell(row.serviceId, "mono"));
const status = document.createElement("td");
status.append(badge(statusLabel(row.status), row.status));
tr.append(status);
tr.append(cell(observedByLabel(row.observedBy)));
tr.append(cell(row.endpoint ?? gateSummary.endpoint, "mono wrap"));
el.healthBody.append(tr);
}
replaceChildren(
el.validationList,
...[
"node --check web/hwlab-cloud-web/app.mjs",
"node --check web/hwlab-cloud-web/scripts/check.mjs",
"node web/hwlab-cloud-web/scripts/check.mjs",
"node scripts/dev-cloud-workbench-smoke.mjs --mobile",
"node scripts/dev-cloud-workbench-smoke.mjs --local-agent-fixture",
"node scripts/l6-cli-web-smoke.mjs",
"git diff --check"
].map((command) => {
const li = document.createElement("li");
li.textContent = command;
return li;
})
);
}
function renderMethodList(methods, tone) {
replaceChildren(el.methodList, ...methods.map((method) => badge(method, tone === "live" ? "dev-live" : tone)));
}
function renderAgentChatStatus(status, result = null) {
const labels = {
idle: "等待输入",
running: "处理中",
completed: "DEV-LIVE 回复",
source: "SOURCE 回复",
failed: "发送失败",
blocked: "服务受阻"
};
el.agentChatStatus.textContent = labels[status] ?? statusLabel(status);
el.agentChatStatus.className = `state-tag tone-${toneClass(status === "completed" ? "dev-live" : status === "failed" || status === "blocked" ? "blocked" : "source")}`;
el.commandInput.disabled = status === "running";
el.commandSend.disabled = status === "running";
el.commandSend.textContent = status === "running" ? "发送中" : "发送";
if (result?.conversationId) {
el.agentChatStatus.title = `${result.provider ?? "provider"} / ${result.model ?? "model"} / ${result.backend ?? "backend"}`;
} else {
el.agentChatStatus.removeAttribute("title");
}
}
function sourceTitle(result) {
return `Code Agent 回复 ${shortTime(result.updatedAt ?? new Date().toISOString())}`;
}
function sourceFixtureTitle(result) {
return `Code Agent SOURCE 回复 ${shortTime(result.updatedAt ?? new Date().toISOString())}`;
}
function classifyCodeAgentCompletion(result) {
if (isRealCompletedChatResult(result)) {
return {
status: "completed",
replied: true,
sourceKind: "DEV-LIVE",
title: sourceTitle(result)
};
}
if (isSourceFixtureChatResult(result)) {
return {
status: "source",
replied: true,
sourceKind: "SOURCE",
title: sourceFixtureTitle(result)
};
}
return {
status: "failed",
replied: false,
sourceKind: "BLOCKED",
title: result?.status === "completed" ? "Code Agent 完成证据不足" : "Code Agent 返回失败"
};
}
function failureMessage(result) {
if (result?.error?.code === "provider_unavailable" || result?.availability?.status === "blocked") {
const availability = result.error?.providerStatus || result.availability?.status !== "blocked"
? codeAgentAvailabilityFromResult(result)
: result.availability ?? codeAgentAvailabilityFromResult(result);
return codeAgentBlockedSummary(availability);
}
const missing = [
...(result.error?.missingCommands ?? []),
...(result.error?.missingEnv ?? [])
].filter(Boolean);
const suffix = missing.length ? ` 缺口:${missing.join("、")}。` : "";
return `Code Agent 调用失败:${result.error?.message ?? "未知错误"}${suffix}请稍后重试或联系维护者补齐后端 provider。`;
}
function blockerCard(blocker) {
return infoCard({
title: blockerTitle(blocker),
detail: userFacingSummary(blocker.summary),
tone: blocker.status === "open" ? "blocked" : blocker.status
});
}
function messageCard(message) {
const article = document.createElement("article");
article.className = `message-card message-${toneClass(message.role)} status-${toneClass(message.status)}`;
article.append(textSpan(roleLabel(message.role), "message-role"));
article.append(textSpan(message.title, "message-title"));
article.append(textSpan(message.text, "message-copy"));
const messageEvidence = messageEvidencePanel(message);
if (messageEvidence) article.append(messageEvidence);
return article;
}
function messageEvidencePanel(message) {
const fields = messageEvidenceFields(message).map(boundedEvidenceField);
if (fields.length === 0) return null;
const details = document.createElement("details");
details.className = "message-evidence";
details.open = message.status === "completed" || message.status === "source";
const summary = document.createElement("summary");
summary.className = "message-meta";
summary.textContent = messageEvidenceSummary(message, fields);
const chips = document.createElement("div");
chips.className = "message-evidence-chips";
chips.append(...fields.map((field) => textSpan(field, "message-evidence-chip")));
details.append(summary, chips);
return details;
}
function messageEvidenceSummary(message, fields) {
const sourceKind = message.sourceKind ?? (message.status === "completed" ? "DEV-LIVE" : message.status === "failed" ? "BLOCKED" : "SOURCE");
return `证据 ${sourceKind} / ${fields.slice(0, 3).join(" / ")}`;
}
function messageEvidenceFields(message) {
return [
recordField("source", message.sourceKind),
recordField("provider", message.provider),
recordField("model", message.model),
recordField("backend", message.backend),
recordField("conversation", message.conversationId),
recordField("trace", message.traceId),
recordField("message", message.messageId),
providerTraceSummary(message.providerTrace)
].filter(Boolean);
}
function boundedEvidenceField(field) {
const text = String(field ?? "").replace(/\s+/gu, " ").trim();
return text.length > 96 ? `${text.slice(0, 93)}...` : text;
}
function providerTraceSummary(providerTrace) {
if (!providerTrace || typeof providerTrace !== "object") return null;
const responseId = providerTrace.responseId ?? providerTrace.id;
if (responseId) return recordField("providerTrace", responseId);
if (providerTrace.command) return "providerTrace=codex-cli";
return null;
}
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 statusLight(tone) {
const span = document.createElement("span");
span.className = `tree-light tone-bg-${toneClass(tone)}`;
return span;
}
function badge(text, tone = text) {
const span = document.createElement("span");
span.className = `badge tone-${toneClass(tone)}`;
span.textContent = text ?? "无";
return span;
}
function appendMetricGrid(parent, entries) {
replaceChildren(
parent,
...entries.map(([label, value, tone]) => {
const item = document.createElement("div");
item.className = "metric";
item.append(textSpan(label, "metric-label"), textSpan(String(value ?? "无"), `metric-value tone-${toneClass(tone ?? "source")}`));
return item;
})
);
}
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 runtimeSummaryFrom(live) {
return live.health.data?.runtime ?? live.healthLive.data?.runtime ?? live.adapter.data?.runtime ?? live.restIndex.data?.runtime ?? null;
}
function methodsFrom(live) {
const restMethods = live.restIndex.data?.methods;
const adapterMethods = live.adapter.data?.methods;
if (Array.isArray(restMethods)) return restMethods;
if (Array.isArray(adapterMethods)) return adapterMethods;
return [];
}
function messageFromPayload(payload) {
if (!payload || typeof payload !== "object") return "非 JSON 或空响应";
return payload.error?.message || payload.error?.code || payload.message || JSON.stringify(payload).slice(0, 160);
}
function normalizeBlockedAgentResult(payload, httpStatus, traceId, fallbackMessage) {
const { reply, ...safePayload } = payload;
const providerStatus = payload.error?.providerStatus ?? httpStatus;
const error = {
...(payload.error && typeof payload.error === "object" ? payload.error : {}),
code: payload.error?.code ?? "provider_unavailable",
message: payload.error?.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.availability?.status === "blocked"
)
);
}
function isHttpNon2xxStatus(value) {
return typeof value === "number" && Number.isInteger(value) && (value < 200 || value > 299);
}
function codeAgentAvailabilityFrom(live) {
const availability = live?.restIndex?.data?.codeAgent ?? live?.healthLive?.data?.codeAgent ?? null;
return sanitizeCodeAgentAvailability(availability);
}
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?.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 状态:等待只读探测",
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);
}
return "输入区会调用受控 Code Agent 接口;只有真实完成回复才显示为完成,不能因为只有会话编号就当成实况完成。";
}
function codeAgentBlockedSummary(availability) {
const providerStatus = availability?.error?.providerStatus ?? availability?.providerStatus;
if (providerStatus) {
return `真实后端已接入,但当前上游响应 HTTP ${providerStatus};工作台保持只读和可重试,不会冒充真实 Code Agent 回复。`;
}
return "真实后端已接入,但当前 Code Agent 服务暂不可用;工作台保持只读和可重试,不会冒充真实回复。";
}
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?.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 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) {
return (
isTrustedCodeAgentProvider(value?.provider) &&
Boolean(value?.model) &&
Boolean(value?.backend) &&
Boolean(value?.traceId) &&
Boolean(value?.conversationId || value?.sessionId) &&
!UNTRUSTED_CODE_AGENT_PROVIDER_PATTERN.test(`${value?.provider ?? ""} ${value?.backend ?? ""}`)
);
}
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 === "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" || latestChatResult()?.status === "failed") return "blocked";
return "source";
}
function statusLabel(value) {
const key = String(value ?? "unknown").trim();
const normalized = key.toLowerCase();
return STATUS_LABELS[normalized] ?? key;
}
function roleLabel(role) {
return (
{
agent: "Agent",
blocker: "阻塞",
system: "系统",
user: "用户"
}[String(role ?? "").toLowerCase()] ?? statusLabel(role)
);
}
function resourceTypeLabel(type) {
return (
{
board: "板卡",
relay: "继电器"
}[String(type ?? "").toLowerCase()] ?? statusLabel(type)
);
}
function stepKindLabel(kind) {
return (
{
agent: "Agent 会话",
audit: "审计",
cleanup: "清理",
evidence: "证据",
hardware_operation: "硬件操作",
health: "健康检查",
status: "状态检查",
wiring: "接线"
}[String(kind ?? "").toLowerCase()] ?? statusLabel(kind)
);
}
function stepTitle(title) {
return (
{
audit: "审计",
cleanup: "清理",
"agent hardware call": "Agent 硬件调用",
"agent session": "Agent 会话",
"DEV health": "DEV 健康",
"direct hardware call": "直接硬件调用",
evidence: "证据",
"gateway/box status": "Gateway/Box 状态",
wiring: "接线"
}[String(title ?? "")] ?? statusLabel(title)
);
}
function recordKindLabel(kind) {
return (
{
audit: "审计",
evidence: "证据",
operation: "操作",
record: "记录",
report: "报告",
trace: "轨迹"
}[String(kind ?? "").toLowerCase()] ?? statusLabel(kind)
);
}
function componentLabel(component) {
return (
{
"Agent manager": "Agent 管理器",
"Agent skills": "Agent 技能",
"Agent worker": "Agent Worker",
"Box simulator": "Box 模拟器",
CLI: "CLI",
"Cloud API": "Cloud API",
"Cloud Web": "Cloud Web",
"D601 router": "D601 路由器",
"DEV ingress": "DEV 入口",
Gateway: "Gateway",
"Gateway simulator": "Gateway 模拟器",
"Patch panel": "Patch Panel",
frp: "frp",
"master edge proxy": "master 边界代理"
}[String(component ?? "")] ?? statusLabel(component)
);
}
function observedByLabel(value) {
return (
{
fixture: "fixture 来源 SOURCE"
}[String(value ?? "").toLowerCase()] ?? statusLabel(value)
);
}
function blockerTitle(blocker) {
const scope = blocker?.scope ?? "unknown";
return `阻塞:${scope}`;
}
function internalGatePathnames() {
return new Set(["/gate", "/diagnostics/gate"]);
}
function helpPathnames() {
return new Set(["/help"]);
}
function userFacingSummary(text) {
return String(text ?? "")
.replaceAll("Public DEV route/frp", "公共 DEV 路由/frp")
.replaceAll("live MVP remains blocked", "实况 MVP 仍被阻塞")
.replaceAll("DB live readiness", "DB 实况就绪")
.replaceAll("M3 hardware loop topology", "M3 硬件闭环拓扑")
.replaceAll("no historical 6667 failure is counted as active evidence", "历史 6667 失败不计为当前有效证据")
.replaceAll("cloud-api /health/live reports DB degraded", "cloud-api /health/live 报告 DB 降级")
.replaceAll("live DB evidence is not established", "尚未建立实况 DB 证据")
.replaceAll("Live M3 smoke reached DEV simulators", "实况 M3 smoke 已触达 DEV 模拟器")
.replaceAll("patch-panel active DO1 -> DI1 connection is missing", "缺少 patch-panel 上的活动 DO1 -> DI1 连接")
.replaceAll("Frozen contract, artifact, boundary, drift, and evidence fixtures are parseable and aligned to the DEV boundary.", "冻结的契约、产物、边界、漂移与证据 fixture 可解析,并已对齐 DEV 边界。")
.replaceAll("The local M1 contract smoke passes.", "本地 M1 契约 smoke 已通过。")
.replaceAll("The deploy smoke remains fixture-only and validates the frozen route phases and artifact metadata.", "部署 smoke 仍仅使用 fixture,用于验证冻结路由阶段与产物元数据。")
.replaceAll("Local M3 remains green, but active DEV-LIVE M3 is blocked by hardware loop topology.", "本地 M3 仍为通过,但活动 DEV-LIVE M3 受硬件闭环拓扑阻塞。")
.replaceAll("Local M4 remains green, but active DEV-LIVE M4 is blocked at DB live readiness before agent runtime scheduling.", "本地 M4 仍为通过,但活动 DEV-LIVE M4 在 Agent 运行时调度前受 DB 实况就绪阻塞。")
.replaceAll("The M5 orchestration is green in dry-run mode, but active DEV-LIVE acceptance is blocked by DB live and hardware loop evidence.", "M5 编排在 DRY-RUN 模式下通过,但活动 DEV-LIVE 验收受 DB 实况与硬件闭环证据阻塞。");
}
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 shortHash(value) {
return String(value ?? "unknown").slice(0, 7);
}
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"));
}