From 1c44ce671bd7a63bba467ab4f821f9bc798bc860 Mon Sep 17 00:00:00 2001 From: Codex Date: Thu, 28 May 2026 08:06:24 +0800 Subject: [PATCH] fix: stabilize cloud web restored status --- package.json | 2 +- scripts/artifact-publish.mjs | 2 ++ scripts/dev-cloud-workbench-smoke.test.mjs | 2 +- web/hwlab-cloud-web/app.mjs | 3 +- web/hwlab-cloud-web/favicon.ico | 6 ++++ web/hwlab-cloud-web/favicon.svg | 6 ++++ web/hwlab-cloud-web/index.html | 1 + web/hwlab-cloud-web/live-status.mjs | 6 ++++ web/hwlab-cloud-web/live-status.test.mjs | 33 +++++++++++++++++++ web/hwlab-cloud-web/package.json | 2 +- web/hwlab-cloud-web/scripts/check.mjs | 11 +++++++ web/hwlab-cloud-web/scripts/dist-contract.mjs | 2 ++ 12 files changed, 71 insertions(+), 5 deletions(-) create mode 100644 web/hwlab-cloud-web/favicon.ico create mode 100644 web/hwlab-cloud-web/favicon.svg create mode 100644 web/hwlab-cloud-web/live-status.test.mjs diff --git a/package.json b/package.json index 60ed0187..62d82e35 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/scripts/artifact-publish.mjs b/scripts/artifact-publish.mjs index 681fb08c..6833c593 100644 --- a/scripts/artifact-publish.mjs +++ b/scripts/artifact-publish.mjs @@ -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"; } diff --git a/scripts/dev-cloud-workbench-smoke.test.mjs b/scripts/dev-cloud-workbench-smoke.test.mjs index af6abe6c..11dc0526 100644 --- a/scripts/dev-cloud-workbench-smoke.test.mjs +++ b/scripts/dev-cloud-workbench-smoke.test.mjs @@ -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" diff --git a/web/hwlab-cloud-web/app.mjs b/web/hwlab-cloud-web/app.mjs index f35c7847..dc6f1a9c 100644 --- a/web/hwlab-cloud-web/app.mjs +++ b/web/hwlab-cloud-web/app.mjs @@ -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) { diff --git a/web/hwlab-cloud-web/favicon.ico b/web/hwlab-cloud-web/favicon.ico new file mode 100644 index 00000000..3d1bbcf3 --- /dev/null +++ b/web/hwlab-cloud-web/favicon.ico @@ -0,0 +1,6 @@ + + + + + + diff --git a/web/hwlab-cloud-web/favicon.svg b/web/hwlab-cloud-web/favicon.svg new file mode 100644 index 00000000..3d1bbcf3 --- /dev/null +++ b/web/hwlab-cloud-web/favicon.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/web/hwlab-cloud-web/index.html b/web/hwlab-cloud-web/index.html index 2829e5f7..8b646f7e 100644 --- a/web/hwlab-cloud-web/index.html +++ b/web/hwlab-cloud-web/index.html @@ -4,6 +4,7 @@ HWLAB 云工作台 + diff --git a/web/hwlab-cloud-web/live-status.mjs b/web/hwlab-cloud-web/live-status.mjs index 3b63abcb..9b07b857 100644 --- a/web/hwlab-cloud-web/live-status.mjs +++ b/web/hwlab-cloud-web/live-status.mjs @@ -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({ diff --git a/web/hwlab-cloud-web/live-status.test.mjs b/web/hwlab-cloud-web/live-status.test.mjs new file mode 100644 index 00000000..d31d4e9b --- /dev/null +++ b/web/hwlab-cloud-web/live-status.test.mjs @@ -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); +}); diff --git a/web/hwlab-cloud-web/package.json b/web/hwlab-cloud-web/package.json index 48b2c26d..1b77f191 100644 --- a/web/hwlab-cloud-web/package.json +++ b/web/hwlab-cloud-web/package.json @@ -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", diff --git a/web/hwlab-cloud-web/scripts/check.mjs b/web/hwlab-cloud-web/scripts/check.mjs index 3c78e922..f4a22987 100644 --- a/web/hwlab-cloud-web/scripts/check.mjs +++ b/web/hwlab-cloud-web/scripts/check.mjs @@ -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, /