fix: stabilize cloud web restored status

This commit is contained in:
Codex
2026-05-28 08:06:24 +08:00
parent c21f7e6bf3
commit 1c44ce671b
12 changed files with 71 additions and 5 deletions
+1 -1
View File
@@ -23,7 +23,7 @@
"cli:dry-run": "node tools/hwlab-cli/bin/hwlab-cli.mjs test e2e --env dev --mvp",
"cli:projects": "node tools/hwlab-cli/bin/hwlab-cli.mjs project list",
"l6:smoke": "node scripts/l6-cli-web-smoke.mjs",
"web:check": "node web/hwlab-cloud-web/scripts/check.mjs && node --test web/hwlab-cloud-web/message-markdown.test.mjs web/hwlab-cloud-web/scripts/trace-scroll.test.mjs",
"web:check": "node web/hwlab-cloud-web/scripts/check.mjs && node --test web/hwlab-cloud-web/live-status.test.mjs web/hwlab-cloud-web/message-markdown.test.mjs web/hwlab-cloud-web/scripts/trace-scroll.test.mjs",
"web:build": "node web/hwlab-cloud-web/scripts/build.mjs",
"artifact-catalog:preview-blocked": "node scripts/refresh-artifact-catalog.mjs --target-ref HEAD --blocked --no-write",
"g14:gitops:render": "node scripts/g14-gitops-render.mjs",
+2
View File
@@ -905,6 +905,8 @@ function contentType(filePath) {
if (filePath.endsWith(".html")) return "text/html; charset=utf-8";
if (filePath.endsWith(".css")) return "text/css; charset=utf-8";
if (filePath.endsWith(".mjs") || filePath.endsWith(".js")) return "text/javascript; charset=utf-8";
if (filePath.endsWith(".svg")) return "image/svg+xml";
if (filePath.endsWith(".ico")) return "image/svg+xml";
return "application/octet-stream";
}
+1 -1
View File
@@ -732,7 +732,7 @@ test("layout smoke verifies desktop and mobile default workbench geometry withou
test("repo-owned web checks expose source build and DEV live layout smoke gates", () => {
assert.match(rootPackage.scripts["web:check"], /^node web\/hwlab-cloud-web\/scripts\/check\.mjs/u);
assert.match(rootPackage.scripts["web:check"], /node --test web\/hwlab-cloud-web\/message-markdown\.test\.mjs web\/hwlab-cloud-web\/scripts\/trace-scroll\.test\.mjs/u);
assert.match(rootPackage.scripts["web:check"], /node --test web\/hwlab-cloud-web\/live-status\.test\.mjs web\/hwlab-cloud-web\/message-markdown\.test\.mjs web\/hwlab-cloud-web\/scripts\/trace-scroll\.test\.mjs/u);
assert.equal(
rootPackage.scripts["web:layout"],
"node scripts/dev-cloud-workbench-layout-smoke.mjs --static --report /tmp/hwlab-dev-gate/dev-cloud-workbench-layout.json"
+1 -2
View File
@@ -1804,8 +1804,7 @@ function shouldReconcileRestoredCodeAgentResult(message) {
if (state.resultReconciliationInFlight.has(message.traceId)) return false;
const status = String(message.status ?? "").toLowerCase();
if (["completed", "canceled"].includes(status)) return false;
if (["failed", "timeout", "error"].includes(status)) return isRecoverableCodeAgentTerminalMessage(message) || traceHasTerminalResultEvent(message.runnerTrace);
return true;
return traceHasTerminalResultEvent(message.runnerTrace);
}
function isRecoverableCodeAgentTerminalMessage(message) {
+6
View File
@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-label="HWLAB">
<rect width="64" height="64" rx="12" fill="#111827"/>
<path d="M14 17h8v12h8V17h8v30h-8V35h-8v12h-8V17Z" fill="#f8fafc"/>
<path d="M42 17h8l-7 30h-8l-4-15 4-15 4 18 3-18Z" fill="#38bdf8"/>
<path d="M14 53h36" stroke="#22c55e" stroke-width="4" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 374 B

+6
View File
@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-label="HWLAB">
<rect width="64" height="64" rx="12" fill="#111827"/>
<path d="M14 17h8v12h8V17h8v30h-8V35h-8v12h-8V17Z" fill="#f8fafc"/>
<path d="M42 17h8l-7 30h-8l-4-15 4-15 4 18 3-18Z" fill="#38bdf8"/>
<path d="M14 53h36" stroke="#22c55e" stroke-width="4" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 374 B

+1
View File
@@ -4,6 +4,7 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>HWLAB 云工作台</title>
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
<link rel="stylesheet" href="/styles.css" />
<script type="module" src="/app.mjs"></script>
</head>
+6
View File
@@ -222,6 +222,12 @@ function devicePodEvidenceSummary(data = {}) {
].filter(Boolean).join(" / ");
}
function shortEvidenceToken(value) {
const text = String(value ?? "").trim();
if (!text) return "unknown";
return text.length > 16 ? text.slice(0, 16) : text;
}
function classifyApiProbe(probe, context) {
if (!probe) {
return unverifiedProbe({
+33
View File
@@ -0,0 +1,33 @@
import assert from "node:assert/strict";
import test from "node:test";
import { classifyWorkbenchLiveStatus } from "./live-status.mjs";
test("Device Pod profile hash evidence is summarized without undefined helper crashes", () => {
const status = classifyWorkbenchLiveStatus({
healthLive: { ok: true, data: { serviceId: "hwlab-cloud-api", status: "ok" } },
restIndex: { ok: true, data: { serviceId: "hwlab-cloud-api", status: "ok" } },
health: { ok: true, data: { serviceId: "hwlab-cloud-api", status: "ok" } },
adapter: { ok: true, data: { serviceId: "hwlab-cloud-api", status: "ok" } },
devicePodStatus: {
ok: true,
data: {
status: "ready",
serviceId: "hwlab-device-pod",
summary: {
devicePodId: "device-pod-71-freq",
targetId: "71-FREQ",
profileHash: "abcdef0123456789fedcba9876543210",
latestEvent: { refs: { traceId: "trc_live_status", operationId: "op_live_status" } }
}
}
}
});
const devicePodProbe = status.probes.find((probe) => probe.serviceId === "hwlab-device-pod");
assert.ok(devicePodProbe, JSON.stringify(status.probes));
assert.equal(devicePodProbe.kind, "pass");
assert.equal(devicePodProbe.reasonCode, "device_pod_ready");
assert.match(devicePodProbe.evidenceSummary, /profile=abcdef0123456789/u);
assert.doesNotMatch(devicePodProbe.evidenceSummary, /fedcba9876543210/u);
});
+1 -1
View File
@@ -4,7 +4,7 @@
"private": true,
"type": "module",
"scripts": {
"check": "node --test auth.test.mjs code-agent-facts.test.mjs code-agent-status.test.mjs scripts/sidebar-resize.test.mjs scripts/trace-scroll.test.mjs && node scripts/check.mjs",
"check": "node --test auth.test.mjs code-agent-facts.test.mjs code-agent-status.test.mjs live-status.test.mjs scripts/sidebar-resize.test.mjs scripts/trace-scroll.test.mjs && node scripts/check.mjs",
"build": "node scripts/build.mjs",
"layout": "node ../../scripts/dev-cloud-workbench-layout-smoke.mjs --static --report /tmp/hwlab-dev-gate/dev-cloud-workbench-layout.json",
"layout:build": "node ../../scripts/dev-cloud-workbench-layout-smoke.mjs --build --report /tmp/hwlab-dev-gate/dev-cloud-workbench-layout-build.json",
+11
View File
@@ -17,6 +17,8 @@ const requiredFiles = Object.freeze([
"message-markdown.mjs",
"live-status.mjs",
"runtime.mjs",
"favicon.svg",
"favicon.ico",
"help.md",
"third_party/marked/marked.esm.js",
"third_party/marked/LICENSE"
@@ -34,6 +36,8 @@ const styles = readWeb("styles.css");
const app = readWeb("app.mjs");
const liveStatus = readWeb("live-status.mjs");
const helpMarkdown = readWeb("help.md");
const faviconSvg = readWeb("favicon.svg");
const faviconIco = readWeb("favicon.ico");
const distContractScript = readWeb("scripts/dist-contract.mjs");
const cloudApiServer = readRepo("internal/cloud/server.mjs");
const devicePodData = readRepo("internal/device-pod/fake-data.mjs");
@@ -107,8 +111,15 @@ assert.match(app, /function\s+renderDrafts\s*\(/u, "restored Code Agent state mu
assert.doesNotMatch(functionBody(app, "loadLiveSurface"), /\/v1\/m3|audit\.event\.query|evidence\.record\.query/u, "live surface must use Device Pod routes, not legacy hardware/evidence panels");
assert.match(liveStatus, /function classifyDevicePodProbe/u, "live status must classify Device Pod probe");
assert.match(liveStatus, /function shortEvidenceToken\s*\(/u, "live status must define profile hash evidence shortener locally");
assert.doesNotMatch(functionBody(app, "shouldReconcileRestoredCodeAgentResult"), /return\s+true\s*;/u, "restored Code Agent state must not blindly fetch stale result traces");
assert.doesNotMatch(functionBody(app, "shouldReconcileRestoredCodeAgentResult"), /isRecoverableCodeAgentTerminalMessage/u, "restored Code Agent state must not fetch stale result traces just because a timeout is recoverable");
assert.doesNotMatch(liveStatus, /classifyM3|serviceForM3Layer|\/v1\/m3/u, "live status must not keep legacy M3 probes");
assert.match(html, /rel="icon"[^>]*href="\/favicon\.svg"/u, "Cloud Web must declare a repo-owned favicon");
assert.match(faviconSvg, /<svg\b[\s\S]*HW/u, "favicon.svg must be a real HWLAB SVG icon");
assert.equal(faviconIco, faviconSvg, "favicon.ico compatibility asset must mirror the repo-owned SVG icon until binary ICO generation exists");
const activeFrontend = `${html}\n${app}\n${liveStatus}\n${helpMarkdown}`;
for (const forbidden of [
"Gateway-SIMU",
@@ -15,6 +15,8 @@ export const cloudWebDistRuntimeFiles = Object.freeze([
"message-markdown.mjs",
"live-status.mjs",
"runtime.mjs",
"favicon.svg",
"favicon.ico",
"help.md",
"third_party/marked/marked.esm.js",
"third_party/marked/LICENSE"