fix: stabilize cloud web restored status
This commit is contained in:
+1
-1
@@ -23,7 +23,7 @@
|
|||||||
"cli:dry-run": "node tools/hwlab-cli/bin/hwlab-cli.mjs test e2e --env dev --mvp",
|
"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",
|
"cli:projects": "node tools/hwlab-cli/bin/hwlab-cli.mjs project list",
|
||||||
"l6:smoke": "node scripts/l6-cli-web-smoke.mjs",
|
"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",
|
"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",
|
"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",
|
"g14:gitops:render": "node scripts/g14-gitops-render.mjs",
|
||||||
|
|||||||
@@ -905,6 +905,8 @@ function contentType(filePath) {
|
|||||||
if (filePath.endsWith(".html")) return "text/html; charset=utf-8";
|
if (filePath.endsWith(".html")) return "text/html; charset=utf-8";
|
||||||
if (filePath.endsWith(".css")) return "text/css; 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(".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";
|
return "application/octet-stream";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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", () => {
|
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 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(
|
assert.equal(
|
||||||
rootPackage.scripts["web:layout"],
|
rootPackage.scripts["web:layout"],
|
||||||
"node scripts/dev-cloud-workbench-layout-smoke.mjs --static --report /tmp/hwlab-dev-gate/dev-cloud-workbench-layout.json"
|
"node scripts/dev-cloud-workbench-layout-smoke.mjs --static --report /tmp/hwlab-dev-gate/dev-cloud-workbench-layout.json"
|
||||||
|
|||||||
@@ -1804,8 +1804,7 @@ function shouldReconcileRestoredCodeAgentResult(message) {
|
|||||||
if (state.resultReconciliationInFlight.has(message.traceId)) return false;
|
if (state.resultReconciliationInFlight.has(message.traceId)) return false;
|
||||||
const status = String(message.status ?? "").toLowerCase();
|
const status = String(message.status ?? "").toLowerCase();
|
||||||
if (["completed", "canceled"].includes(status)) return false;
|
if (["completed", "canceled"].includes(status)) return false;
|
||||||
if (["failed", "timeout", "error"].includes(status)) return isRecoverableCodeAgentTerminalMessage(message) || traceHasTerminalResultEvent(message.runnerTrace);
|
return traceHasTerminalResultEvent(message.runnerTrace);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function isRecoverableCodeAgentTerminalMessage(message) {
|
function isRecoverableCodeAgentTerminalMessage(message) {
|
||||||
|
|||||||
@@ -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 |
@@ -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 |
@@ -4,6 +4,7 @@
|
|||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<title>HWLAB 云工作台</title>
|
<title>HWLAB 云工作台</title>
|
||||||
|
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
|
||||||
<link rel="stylesheet" href="/styles.css" />
|
<link rel="stylesheet" href="/styles.css" />
|
||||||
<script type="module" src="/app.mjs"></script>
|
<script type="module" src="/app.mjs"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
@@ -222,6 +222,12 @@ function devicePodEvidenceSummary(data = {}) {
|
|||||||
].filter(Boolean).join(" / ");
|
].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) {
|
function classifyApiProbe(probe, context) {
|
||||||
if (!probe) {
|
if (!probe) {
|
||||||
return unverifiedProbe({
|
return unverifiedProbe({
|
||||||
|
|||||||
@@ -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);
|
||||||
|
});
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"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",
|
"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": "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",
|
"layout:build": "node ../../scripts/dev-cloud-workbench-layout-smoke.mjs --build --report /tmp/hwlab-dev-gate/dev-cloud-workbench-layout-build.json",
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ const requiredFiles = Object.freeze([
|
|||||||
"message-markdown.mjs",
|
"message-markdown.mjs",
|
||||||
"live-status.mjs",
|
"live-status.mjs",
|
||||||
"runtime.mjs",
|
"runtime.mjs",
|
||||||
|
"favicon.svg",
|
||||||
|
"favicon.ico",
|
||||||
"help.md",
|
"help.md",
|
||||||
"third_party/marked/marked.esm.js",
|
"third_party/marked/marked.esm.js",
|
||||||
"third_party/marked/LICENSE"
|
"third_party/marked/LICENSE"
|
||||||
@@ -34,6 +36,8 @@ const styles = readWeb("styles.css");
|
|||||||
const app = readWeb("app.mjs");
|
const app = readWeb("app.mjs");
|
||||||
const liveStatus = readWeb("live-status.mjs");
|
const liveStatus = readWeb("live-status.mjs");
|
||||||
const helpMarkdown = readWeb("help.md");
|
const helpMarkdown = readWeb("help.md");
|
||||||
|
const faviconSvg = readWeb("favicon.svg");
|
||||||
|
const faviconIco = readWeb("favicon.ico");
|
||||||
const distContractScript = readWeb("scripts/dist-contract.mjs");
|
const distContractScript = readWeb("scripts/dist-contract.mjs");
|
||||||
const cloudApiServer = readRepo("internal/cloud/server.mjs");
|
const cloudApiServer = readRepo("internal/cloud/server.mjs");
|
||||||
const devicePodData = readRepo("internal/device-pod/fake-data.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.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 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.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}`;
|
const activeFrontend = `${html}\n${app}\n${liveStatus}\n${helpMarkdown}`;
|
||||||
for (const forbidden of [
|
for (const forbidden of [
|
||||||
"Gateway-SIMU",
|
"Gateway-SIMU",
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ export const cloudWebDistRuntimeFiles = Object.freeze([
|
|||||||
"message-markdown.mjs",
|
"message-markdown.mjs",
|
||||||
"live-status.mjs",
|
"live-status.mjs",
|
||||||
"runtime.mjs",
|
"runtime.mjs",
|
||||||
|
"favicon.svg",
|
||||||
|
"favicon.ico",
|
||||||
"help.md",
|
"help.md",
|
||||||
"third_party/marked/marked.esm.js",
|
"third_party/marked/marked.esm.js",
|
||||||
"third_party/marked/LICENSE"
|
"third_party/marked/LICENSE"
|
||||||
|
|||||||
Reference in New Issue
Block a user