import assert from "node:assert/strict"; import fs from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { loadMvpGateSummary } from "../../../internal/mvp-gate/summary.mjs"; import { runDevCloudWorkbenchLocalAgentFixtureSmoke, runDevCloudWorkbenchLayoutSmoke, runDevCloudWorkbenchMobileSmoke, runDevCloudWorkbenchAuthFixtureSmoke, runDevCloudWorkbenchQuickPromptsFixtureSmoke, runDevCloudWorkbenchSlowBlockerFixtureSmoke, runDevCloudWorkbenchStaticSmoke } from "../../../scripts/src/dev-cloud-workbench-smoke-lib.mjs"; import { gateSummary } from "../gate-summary.mjs"; import { runtime } from "../runtime.mjs"; import { codeAgentM3EvidenceContractSummary, extractCodeAgentM3Evidence, m3EvidenceRows } from "../code-agent-m3-evidence.mjs"; import { runM3ControlPanelGuard } from "./m3-control-panel-guard.mjs"; import { runCloudWebM3ReadonlyContract } from "./m3-readonly-contract.mjs"; import { runWorkbenchHardwarePanelContract } from "./workbench-hardware-panel-contract.mjs"; const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); const repoRoot = path.resolve(rootDir, "../.."); const requiredFiles = [ "index.html", "styles.css", "auth.mjs", "app.mjs", "code-agent-facts.mjs", "code-agent-status.mjs", "code-agent-m3-evidence.mjs", "live-status.mjs", "gate-summary.mjs", "runtime.mjs", "wiring-status.mjs", "workbench-hardware-panel.mjs", "help.md", "third_party/marked/marked.esm.js", "third_party/marked/LICENSE" ]; for (const file of requiredFiles) { const filePath = path.resolve(rootDir, file); if (!fs.existsSync(filePath)) { throw new Error(`missing web asset: ${file}`); } } const html = fs.readFileSync(path.resolve(rootDir, "index.html"), "utf8"); const styles = fs.readFileSync(path.resolve(rootDir, "styles.css"), "utf8"); const auth = fs.readFileSync(path.resolve(rootDir, "auth.mjs"), "utf8"); const app = fs.readFileSync(path.resolve(rootDir, "app.mjs"), "utf8"); const codeAgentFacts = fs.readFileSync(path.resolve(rootDir, "code-agent-facts.mjs"), "utf8"); const codeAgentStatus = fs.readFileSync(path.resolve(rootDir, "code-agent-status.mjs"), "utf8"); const codeAgentM3Evidence = fs.readFileSync(path.resolve(rootDir, "code-agent-m3-evidence.mjs"), "utf8"); const liveStatus = fs.readFileSync(path.resolve(rootDir, "live-status.mjs"), "utf8"); const wiringStatus = fs.readFileSync(path.resolve(rootDir, "wiring-status.mjs"), "utf8"); const helpMarkdown = fs.readFileSync(path.resolve(rootDir, "help.md"), "utf8"); const buildScript = fs.readFileSync(path.resolve(rootDir, "scripts/build.mjs"), "utf8"); const distContractScript = fs.readFileSync(path.resolve(rootDir, "scripts/dist-contract.mjs"), "utf8"); const markedLicense = fs.readFileSync(path.resolve(rootDir, "third_party/marked/LICENSE"), "utf8"); const artifactPublisher = fs.readFileSync(path.resolve(repoRoot, "scripts/dev-artifact-publish.mjs"), "utf8"); const cloudWebRoutes = fs.readFileSync(path.resolve(repoRoot, "internal/dev-entrypoint/cloud-web-routes.mjs"), "utf8"); const cloudWebProxy = fs.readFileSync(path.resolve(repoRoot, "internal/dev-entrypoint/cloud-web-proxy.mjs"), "utf8"); const codexStdioSession = fs.readFileSync(path.resolve(repoRoot, "internal/cloud/codex-stdio-session.mjs"), "utf8"); const gatewayShellTool = fs.readFileSync(path.resolve(repoRoot, "tools/hwlab-gateway-shell.mjs"), "utf8"); const frontendSource = `${html}\n${auth}\n${app}\n${artifactPublisher}`; const requiredTrustedRecordTerms = Object.freeze([ "Code Agent 对话记录", "接线/operation", "audit 记录", "evidence 证据", "trace/messageId", "audit.event.query + SOURCE 回退", "evidence.record.query + SOURCE 回退", "conversationId、traceId 和 messageId", "provider/model/trace/conversation", "失败原因=", "runtime.durable=false;只读 audit/evidence 不能标为 durable ready 或 DEV-LIVE。", "只读 audit 缺少 M3 patch-panel live report / operation / trace / evidence 绑定", "只读 evidence 缺少 M3 patch-panel live report / operation / trace / audit 绑定", "当前显示 SOURCE 回退,不能冒充 DEV-LIVE" ]); const m3EvidenceFixture = { status: "completed", provider: "hwlab-skill-cli", model: "controlled-m3-io", backend: "hwlab-cloud-api/hwlab-agent-runtime-skill-cli", traceId: "trc_check_m3_evidence", conversationId: "conv_check_m3_evidence", sessionId: "sess_check_m3_evidence", messageId: "msg_check_m3_evidence", runner: { kind: "hwlab-m3-io-skill-cli" }, providerTrace: { runnerKind: "hwlab-m3-io-skill-cli", skill: "hwlab-agent-runtime.m3-io", route: "/v1/m3/io", status: "succeeded", accepted: true, operationId: "op_check_m3_evidence", traceId: "trc_check_m3_evidence" }, toolCalls: [ { type: "skill-cli", name: "hwlab-agent-runtime.m3-io", route: "/v1/m3/io", status: "completed", accepted: true, operationId: "op_check_m3_evidence", traceId: "trc_check_m3_evidence", audit: { auditId: "aud_check_m3_evidence" }, evidence: { evidenceId: "evd_check_m3_evidence" }, command: { action: "do.write", resourceId: "res_boxsimu_1", port: "DO1", value: true }, result: { targetReadback: { resourceId: "res_boxsimu_2", port: "DI1", value: true } }, safety: { cloudApiRouteOnly: true, allowedRoute: "/v1/m3/io", directGatewayCalls: false, directBoxCalls: false, directPatchPanelCalls: false } } ] }; const workbenchSmoke = runDevCloudWorkbenchStaticSmoke(); const helpSmokeCheck = workbenchSmoke.checks.find((check) => check.id === "help-md-contract"); const outerScrollSmokeCheck = workbenchSmoke.checks.find((check) => check.id === "outer-scroll-contract"); const mobileLayoutSmokeCheck = workbenchSmoke.checks.find((check) => check.id === "mobile-workbench-layout-contract"); const resourceExplorerRemovalSmokeCheck = workbenchSmoke.checks.find((check) => check.id === "feedback-352-resource-explorer-removed"); const conversationUxSmokeCheck = workbenchSmoke.checks.find((check) => check.id === "code-agent-conversation-ux-states"); const codeAgentTimeoutSmokeCheck = workbenchSmoke.checks.find((check) => check.id === "code-agent-long-timeout-contract"); const codeAgentQuickPromptsSmokeCheck = workbenchSmoke.checks.find((check) => check.id === "code-agent-quick-prompts-contract"); const mobileWorkbenchSmoke = await runDevCloudWorkbenchMobileSmoke(); const mobileOuterScrollSmokeCheck = mobileWorkbenchSmoke.checks.find((check) => check.id === "mobile-outer-scroll-lock"); const mobileHelpSmokeCheck = mobileWorkbenchSmoke.checks.find((check) => check.id === "mobile-help-markdown-route"); const localAgentFixtureSmoke = await runDevCloudWorkbenchLocalAgentFixtureSmoke(); const localAgentNo4500Check = localAgentFixtureSmoke.checks.find((check) => check.id === "local-agent-fixture-no-4500ms-permanent-failure"); const localAgentMobilePendingCheck = localAgentFixtureSmoke.checks.find((check) => check.id === "local-agent-fixture-mobile-pending-layout"); const slowBlockerFixtureSmoke = await runDevCloudWorkbenchSlowBlockerFixtureSmoke(); const slowBlockerCheck = slowBlockerFixtureSmoke.checks.find((check) => check.id === "local-agent-fixture-slow-structured-blocker"); const quickPromptsFixtureSmoke = await runDevCloudWorkbenchQuickPromptsFixtureSmoke(); const quickPromptsFillCheck = quickPromptsFixtureSmoke.checks.find((check) => check.id === "quick-prompts-fill-input"); const quickPromptsNoAutosendCheck = quickPromptsFixtureSmoke.checks.find((check) => check.id === "quick-prompts-write-no-autosend"); const quickPromptsCopyCheck = quickPromptsFixtureSmoke.checks.find((check) => check.id === "quick-prompts-copy-boundary"); const quickPromptsLayoutCheck = quickPromptsFixtureSmoke.checks.find((check) => check.id === "quick-prompts-mobile-layout"); const authFixtureSmoke = await runDevCloudWorkbenchAuthFixtureSmoke(); const authSuccessDesktopCheck = authFixtureSmoke.checks.find((check) => check.id === "auth-login-success-desktop"); const authFailureDesktopCheck = authFixtureSmoke.checks.find((check) => check.id === "auth-login-failure-desktop"); const authRefreshDesktopCheck = authFixtureSmoke.checks.find((check) => check.id === "auth-refresh-session-desktop"); const authLogoutDesktopCheck = authFixtureSmoke.checks.find((check) => check.id === "auth-logout-expiry-desktop"); const authSuccessMobileCheck = authFixtureSmoke.checks.find((check) => check.id === "auth-login-success-mobile"); const authFailureMobileCheck = authFixtureSmoke.checks.find((check) => check.id === "auth-login-failure-mobile"); const authRefreshMobileCheck = authFixtureSmoke.checks.find((check) => check.id === "auth-refresh-session-mobile"); const authLogoutMobileCheck = authFixtureSmoke.checks.find((check) => check.id === "auth-logout-expiry-mobile"); const layoutSmokeReportPath = path.resolve(repoRoot, "tmp/dev-cloud-workbench-layout-web-check.json"); const layoutSmoke = await runDevCloudWorkbenchLayoutSmoke({ reportPath: layoutSmokeReportPath }); fs.mkdirSync(path.dirname(layoutSmokeReportPath), { recursive: true }); layoutSmoke.artifacts = { ...(layoutSmoke.artifacts ?? {}), reportPath: layoutSmokeReportPath }; fs.writeFileSync(layoutSmokeReportPath, `${JSON.stringify(layoutSmoke, null, 2)}\n`, "utf8"); assert.notEqual( mobileWorkbenchSmoke.status, "skip", [ mobileWorkbenchSmoke.summary, "Install repository dependencies and run `node scripts/dev-cloud-workbench-smoke.mjs --mobile` to generate the required mobile outer-scroll evidence." ].filter(Boolean).join(" ") ); assert.notEqual( layoutSmoke.status, "skip", [ layoutSmoke.summary, "Install repository dependencies and run `node scripts/dev-cloud-workbench-layout-smoke.mjs --static` to generate the required layout evidence." ].filter(Boolean).join(" ") ); assert.equal(workbenchSmoke.status, "pass", JSON.stringify(workbenchSmoke.blockers, null, 2)); assert.equal(workbenchSmoke.mode, "source"); assert.equal(workbenchSmoke.evidenceLevel, "SOURCE"); assert.equal(workbenchSmoke.devLive, false); assert.equal(helpSmokeCheck?.status, "pass", "PR #114 help Markdown route must be present and ready"); assert.equal(outerScrollSmokeCheck?.status, "pass", "desktop workbench shell must lock outer page scrolling"); assert.equal(mobileLayoutSmokeCheck?.status, "pass", "mobile 390x844 workbench layout contract must be present"); assert.equal(resourceExplorerRemovalSmokeCheck?.status, "pass", "#352 resource explorer removal source contract must be present"); assert.equal(conversationUxSmokeCheck?.status, "pass", "Code Agent conversation UX states must remain distinct"); assert.equal(codeAgentTimeoutSmokeCheck?.status, "pass", "Code Agent chat must not reuse the 4500ms light API timeout"); assert.equal(codeAgentQuickPromptsSmokeCheck?.status, "pass", "Code Agent quick prompts must remain fill-only with explicit send semantics for writes"); assert.equal(workbenchSmoke.help.status, "pass", "smoke report must not leave #114 help contract pending"); assert.notEqual(mobileWorkbenchSmoke.status, "blocked", JSON.stringify(mobileWorkbenchSmoke.blockers, null, 2)); assert.equal(mobileWorkbenchSmoke.evidenceLevel, "SOURCE"); assert.equal(mobileWorkbenchSmoke.devLive, false); assert.ok( mobileOuterScrollSmokeCheck, "mobile smoke must include the mobile-outer-scroll-lock check; run `node scripts/dev-cloud-workbench-smoke.mjs --mobile` with Playwright installed." ); assert.equal(mobileOuterScrollSmokeCheck.status, "pass", "mobile smoke must prove outer page scroll remains locked"); assert.ok( mobileHelpSmokeCheck, "mobile smoke must include the mobile-help-markdown-route check; run `node scripts/dev-cloud-workbench-smoke.mjs --mobile` with Playwright installed." ); assert.equal(mobileHelpSmokeCheck.status, "pass", "mobile help route must remain non-default Markdown content"); assert.equal(localAgentFixtureSmoke.status, "pass", JSON.stringify(localAgentFixtureSmoke.blockers, null, 2)); assert.equal(localAgentFixtureSmoke.evidenceLevel, "SOURCE"); assert.equal(localAgentFixtureSmoke.devLive, false); assert.equal(localAgentNo4500Check?.status, "pass", "local Code Agent fixture must prove the 4500ms browser timeout path is not permanent"); assert.equal(localAgentMobilePendingCheck?.status, "pass", "local Code Agent fixture must keep mobile pending state visible with trace/session context"); assert.equal(slowBlockerFixtureSmoke.status, "pass", JSON.stringify(slowBlockerFixtureSmoke.blockers, null, 2)); assert.equal(slowBlockerFixtureSmoke.evidenceLevel, "SOURCE"); assert.equal(slowBlockerFixtureSmoke.devLive, false); assert.equal(slowBlockerCheck?.status, "pass", "slow structured blocker fixture must show Chinese reason, trace, retry input, and no 4500ms permanent failure"); assert.equal(quickPromptsFixtureSmoke.status, "pass", JSON.stringify(quickPromptsFixtureSmoke.blockers, null, 2)); assert.equal(quickPromptsFixtureSmoke.evidenceLevel, "SOURCE"); assert.equal(quickPromptsFixtureSmoke.devLive, false); assert.equal(quickPromptsFillCheck?.status, "pass", "quick prompt buttons must fill and focus the Code Agent input"); assert.equal(quickPromptsNoAutosendCheck?.status, "pass", "HWLAB API write quick prompts must not auto-send"); assert.equal(quickPromptsCopyCheck?.status, "pass", "quick prompt labels must say HWLAB API / Skill CLI without direct hardware-control copy"); assert.equal(quickPromptsLayoutCheck?.status, "pass", "quick prompt strip must stay contained on desktop and 390x844 mobile"); assert.equal(authFixtureSmoke.status, "pass", JSON.stringify(authFixtureSmoke.blockers, null, 2)); assert.equal(authFixtureSmoke.evidenceLevel, "SOURCE"); assert.equal(authFixtureSmoke.devLive, false); assert.equal(authSuccessDesktopCheck?.status, "pass", "desktop login must accept admin/hwlab2026 and enter the Cloud Workbench"); assert.equal(authFailureDesktopCheck?.status, "pass", "desktop login failure must show a bounded Chinese error and keep workbench hidden"); assert.equal(authRefreshDesktopCheck?.status, "pass", "desktop refresh must preserve authenticated session state"); assert.equal(authLogoutDesktopCheck?.status, "pass", "desktop logout and expired session must return to login"); assert.equal(authSuccessMobileCheck?.status, "pass", "390x844 mobile login must enter workbench without outer scroll or hidden controls"); assert.equal(authFailureMobileCheck?.status, "pass", "390x844 mobile login failure must keep form visible and error contained"); assert.equal(authRefreshMobileCheck?.status, "pass", "390x844 mobile refresh must preserve authenticated session state"); assert.equal(authLogoutMobileCheck?.status, "pass", "390x844 mobile logout and expired session must return to login"); assert.equal( layoutSmoke.status, "pass", JSON.stringify(compactLayoutSmokeFailures(layoutSmoke), null, 2) ); assert.equal(layoutSmoke.sourceMode, "source-static"); assert.equal(layoutSmoke.evidenceLevel, "SOURCE"); assert.equal(layoutSmoke.devLive, false); assert.equal(layoutSmoke.safety.layoutOnly, true); assert.equal(layoutSmoke.safety.codeAgentPostSent, false); assert.equal(layoutSmoke.safety.hardwareWriteApis, false); assert.equal(layoutSmoke.failures.length, 0); assert.equal(layoutSmoke.viewports.map((viewport) => `${viewport.width}x${viewport.height}`).join(","), "1366x768,1024x768,390x844"); assert.equal( layoutSmoke.checks.find((check) => check.id === "layout-issue-287-future-hardware-status-tabs")?.status, "pass", "#287 hardware status tabs must remain covered by layout smoke" ); assert.equal( layoutSmoke.checks.find((check) => check.id === "layout-hardware-tabs-center-hit")?.status, "pass", "hardware status tabs must be center-hit reachable on desktop and 390x844 mobile" ); assert.equal( layoutSmoke.checks.find((check) => check.id === "layout-wiring-readable-two-column")?.status, "pass", "#276 wiring table must stay readable, non-overlapping, and horizontally contained" ); assert.equal( layoutSmoke.checks.find((check) => check.id === "layout-feedback-352-resource-explorer-removed")?.status, "pass", "#352 resource explorer removal must remain covered by desktop and 390x844 layout smoke" ); assert.equal( layoutSmoke.checks.find((check) => check.id === "layout-feedback-437-live-build-overlay")?.status, "pass", "#437 live build details overlay must not move the topbar, workspace, or conversation geometry" ); assert.equal( layoutSmoke.checks.find((check) => check.id === "layout-left-sidebar-collapse")?.status, "pass", "#278 left activity rail collapse/expand must remain covered by desktop and 390x844 layout smoke" ); assert.equal( layoutSmoke.checks.find((check) => check.id === "layout-issue-288-future-single-table-gate")?.status, "skip", "#288 single-table gate is future coverage; current /gate route remains covered by layout smoke" ); for (const layoutCheckId of [ "layout-desktop-default", "layout-narrow-desktop-default", "layout-mobile-default", "layout-feedback-352-resource-explorer-removed", "layout-feedback-437-live-build-overlay", "layout-left-sidebar-collapse", "layout-right-sidebar-resize-desktop", "layout-right-sidebar-resize-narrow-desktop", "layout-right-sidebar-resize-mobile", "layout-gate-desktop", "layout-gate-narrow-desktop", "layout-gate-mobile" ]) { assert.equal(layoutSmoke.checks.find((check) => check.id === layoutCheckId)?.status, "pass", `${layoutCheckId} must pass in web:check layout gate`); } if (mobileWorkbenchSmoke.status === "pass") { assert.equal(mobileWorkbenchSmoke.viewport.width, 390); assert.equal(mobileWorkbenchSmoke.viewport.height, 844); assert.equal(mobileWorkbenchSmoke.checks.every((check) => check.status === "pass"), true); } assert.match(html, /HWLAB 云工作台/); for (const chineseWorkbenchText of [ "硬件资源", "Agent 对话", "工作清单", "可信记录", "内部复核", "使用说明" ]) { assert.match(`${html}\n${app}`, new RegExp(chineseWorkbenchText)); } for (const legacyEnglishTitle of [ /Agent Conversation \/ Trace Workspace/, /Resource Tree/, /Current Blockers/, /Control Drafts/, /Trusted Records/, /Same-Origin Read Probe/, /Allowed Read RPC/, /Open Blockers/, /Light Validation/, /DEV Health Contract/, /Secondary Entry/, /Runtime Surface/ ]) { assert.doesNotMatch(`${html}\n${app}`, legacyEnglishTitle); } assert.match(html, /M3 实况状态/); assert.match(html, /硬件状态工作区/); assert.match(html, /id="hardware-source-status"/); assert.match(app, /function renderHardwareStatus/); assert.match(app, /wiringPresentationFromSummary/); assert.match(wiringStatus, /function wiringPresentationFromSummary/); assert.match(wiringStatus, /function trustedM3StatusEvidenceFrom/); assert.match(functionBody(wiringStatus, "trustedM3StatusEvidenceFrom"), /contractVersion !== "m3-status-v1"/); assert.match(functionBody(wiringStatus, "trustedM3StatusEvidenceFrom"), /sourceKind !== "DEV-LIVE"/); assert.match(functionBody(wiringStatus, "trustedM3StatusEvidenceFrom"), /trust\.durableStatus !== "green"/); assert.match(functionBody(wiringStatus, "trustedM3StatusEvidenceFrom"), /trust\.blocker != null/); assert.match(functionBody(wiringStatus, "trustedM3StatusEvidenceFrom"), /LIVE_M3_ID_FIELDS\.every/); assert.match(functionBody(wiringStatus, "wiringPresentationFromSummary"), /接线实况 \/ 持久化受阻/); assert.match(functionBody(wiringStatus, "wiringPresentationFromSummary"), /durableStatus !== "green"/); assert.match(functionBody(wiringStatus, "wiringPresentationFromSummary"), /blockedTrust != null/); assert.match(functionBody(wiringStatus, "wiringPresentationFromSummary"), /\/v1\/m3\/status 聚合 \/ 实况可信记录/); assert.match(app, /function initHardwareTabs/); assert.match(app, /function hardwareKvGroup/); assert.match(app, /function sourceFallbackM3Status/); assert.match(app, /function trustedM3Link/); assert.match(app, /function hasTrustedM3LiveEvidence/); assert.match(app, /function hasTrustedPatchPanelLiveReport/); assert.match(app, /function linkStatusLabel/); assert.match(app, /function normalizePort/); assert.match(app, /LIVE_M3_ID_FIELDS/); assert.match(app, /patchPanelLiveReport/); assert.match(functionBody(app, "sourceFallbackM3Status"), /unverified_cloud_api_m3_status/); assert.match(functionBody(app, "sourceFallbackM3Status"), /SOURCE 拓扑不能冒充实况硬件状态/); assert.doesNotMatch(functionBody(app, "renderHardwareStatus"), /hardwareRow\(\s*\{/u); assert.match(functionBody(app, "renderHardwareStatus"), /Key\/Value groups/u); assert.doesNotMatch(app, /sourceKind:\s*counts\s*\?\s*"DEV-LIVE"/); assert.doesNotMatch(app, /counts\s*&&\s*(?:liveLink|sourceLink|m3Link)[\s\S]{0,80}\?\s*"DEV-LIVE"/); assert.match(app, /function renderRecords/); assert.match(app, /function codeAgentRecordCards/); assert.match(app, /function operationRecordCards/); assert.match(app, /function auditRecordCards/); assert.match(app, /function evidenceRecordCards/); assert.match(app, /function auditCard/); assert.match(app, /function evidenceCard/); assert.match(app, /function liveRecordTone/); assert.match(app, /function liveRecordDurabilityBlocker/); assert.match(app, /function isDurableRuntimeReady/); assert.match(app, /runtimeSummary\?\.durable !== true/); assert.match(app, /summary\.liveRuntimeEvidence !== true/); assert.match(app, /auditCard\(event, liveRecordTone\(event, live\), liveRecordDurabilityBlocker\(live\)\)/); assert.match(app, /evidenceCard\(record, liveRecordTone\(record, live\), liveRecordDurabilityBlocker\(live\)\)/); assert.match(app, /function recordGroup/); assert.match(app, /function recordField/); assert.match(app, /function safeFailureReason/); assert.match(app, /function sourceKindLabel/); assert.match(app, /function liveFailureCard/); assert.match(app, /state\.liveSurface/); assert.match(app, /renderRecords\(state\.liveSurface\)/); assert.match(app, /traceId: error\.traceId \|\| traceId/); assert.match(app, /error\.traceId = (?:traceId|response\.data\?\.traceId \|\| traceId)/); assert.match(app, /DEFAULT_API_TIMEOUT_MS\s*=\s*4500/); assert.match(app, /DEFAULT_CODE_AGENT_TIMEOUT_MS\s*=\s*600000/); assert.match(app, /DEFAULT_CODE_AGENT_SUBMIT_TIMEOUT_MS\s*=\s*60000/); assert.match(app, /CODE_AGENT_TIMEOUT_STORAGE_KEY/); assert.match(functionBody(app, "clampTimeoutMs"), /value === null \|\| value === undefined/); assert.match(functionBody(app, "clampTimeoutMs"), /typeof value === "string" && value\.trim\(\) === ""/); assert.match(functionBody(app, "sendAgentMessage"), /timeoutMs:\s*CODE_AGENT_SUBMIT_TIMEOUT_MS/); assert.match(functionBody(app, "sendAgentMessage"), /shortConnection:\s*true/); assert.match(functionBody(app, "waitForAgentMessageResult"), /idleMs\s*>=\s*CODE_AGENT_TIMEOUT_MS/); assert.match(html, /id="code-agent-timeout"/); assert.match(styles, /\.agent-timeout-control\s*{/); assert.match(app, /activityRef:\s*\(\)\s*=>\s*state\.currentRequest/); assert.match(functionBody(app, "fetchJson"), /timeoutMs\s*=\s*API_TIMEOUT_MS/); assert.match(functionBody(app, "fetchJson"), /无新事件/); for (const hardwareTerm of [ "Gateway 在线", "BOX 在线", "链路状态", "接线盘连接", "runtime durable", "DISABLED 后续能力", "SOURCE", "DEV-LIVE", "unverified_cloud_api_m3_status", "/v1/m3/status", "仍在等待完整可信记录", "页面不直连模拟器,也不伪造硬件状态。" ]) { assert.match(app, new RegExp(escapeRegExp(hardwareTerm)), `missing hardware term: ${hardwareTerm}`); } for (const hardwareHtmlTerm of ["Gateway-SIMU", "BOX-SIMU-1", "BOX-SIMU-2", "Patch Panel"]) { assert.match(html, new RegExp(escapeRegExp(hardwareHtmlTerm)), `missing hardware HTML term: ${hardwareHtmlTerm}`); } assert.match(app, /function hardwareEvidenceLabel/); assert.match(styles, /\.record-group\s*{/); assert.match(styles, /\.record-group-title\s*{/); assert.match(styles, /\.hardware-section\s*{/); assert.match(styles, /\.hardware-status-tabs\s*{/); assert.match(styles, /\.kv-row\s*{/); assert.match(styles, /\.hardware-row\s*{/); assert.match(app, /new Set\(\["\/gate", "\/diagnostics\/gate"\]\)/); assert.match(html, /href="\/styles\.css"/); assert.match(html, /src="\/app\.mjs"/); for (const workbenchElement of [ "activity-rail", "left-sidebar-toggle", "conversation-list", "agent-chat-status", "code-agent-summary", "code-agent-summary-label", "code-agent-summary-capability", "code-agent-summary-trace", "right-sidebar", "right-sidebar-resize", "command-form", "command-send", "hardware-list", "control-list", "wiring-body", "records-list", "gate-review-body" ]) { assert.match(html, new RegExp(workbenchElement)); } for (const removedSelectorTerm of [ "id=\"resource-explorer\"", "id=\"explorer-toggle\"", "id=\"explorer-resize\"", "id=\"resource-tree\"", "explorer-capabilities", "capability-count", "tree-count", "quick-actions", "data-focus-command" ]) { assert.doesNotMatch(html, new RegExp(escapeRegExp(removedSelectorTerm)), `removed resource explorer selector returned: ${removedSelectorTerm}`); } for (const removedUiText of [ "资源浏览与硬件资源树", "资源浏览", "硬件资源树", "只读工作区", "常用能力", "收起资源树", "展开资源树", "拖拽调整左侧资源树宽度", "资源查看", "查看接线", "查看状态", "编写任务" ]) { assert.doesNotMatch(`${html}\n${app}\n${styles}`, new RegExp(escapeRegExp(removedUiText)), `removed resource explorer copy returned: ${removedUiText}`); } assert.match(html, /id="hardware-sidebar"/); assert.match(html, /id="activity-rail"/); assert.match(html, /id="left-sidebar-toggle"/); assert.match(html, /aria-label="折叠左侧导航"/); assert.match(html, /title="折叠左侧导航"/); assert.match(html, /aria-controls="activity-rail"/); assert.match(html, /aria-expanded="true"/); assert.match(html, /id="right-sidebar-resize"/); assert.match(html, /aria-label="拖拽调整右侧硬件状态栏宽度"/); assert.match(html, /aria-valuemin="560"/); assert.match(html, /aria-valuemax="740"/); assert.match(html, /aria-valuenow="728"/); assert.doesNotMatch(app, /setExplorerCollapsed|initExplorerResize|clampExplorerWidth|syncMobileExplorer|collapseExplorerAfterMobileAction/); assert.match(app, /LAYOUT_STORAGE_KEY\s*=\s*"hwlab\.workbench\.layout\.v1"/); assert.match(app, /leftSidebarCollapsed:\s*payload\.leftSidebarCollapsed === true/); assert.match(app, /leftSidebarCollapsed:\s*state\.layout\.leftSidebarCollapsed/); assert.match(app, /function initLeftSidebarToggle/); assert.match(app, /function setLeftSidebarCollapsed/); assert.match(app, /is-left-sidebar-collapsed/); assert.match(app, /展开左侧导航/); assert.match(app, /折叠左侧导航/); assert.match(app, /function initRightSidebarResize/); assert.match(app, /function clampRightSidebarWidth/); assert.match(app, /function rightSidebarResizeBounds/); assert.match(app, /setPointerCapture/); assert.match(app, /localStorage\?\.setItem\(\s*LAYOUT_STORAGE_KEY/); assert.match(app, /rightSidebarWidth:\s*state\.layout\.rightSidebarWidth/); assert.match(app, /ArrowLeft/); assert.match(app, /ArrowRight/); assert.match(app, /event\.key === "Home"/); assert.match(app, /event\.key === "End"/); assert.doesNotMatch(styles, /--explorer-width|--explorer-min-width|--explorer-max-width|\.explorer|\.explorer-resize|\.quick-actions|\.resource-tree|\.tree-panel|\.tree-row|\.tree-label/); assert.match(styles, /--right-sidebar-width:\s*728px/); assert.match(styles, /--right-sidebar-min-width:\s*560px/); assert.match(styles, /--right-sidebar-max-width:\s*740px/); assert.match(styles, /--right-width:\s*clamp\(var\(--right-sidebar-min-width\),\s*var\(--right-sidebar-width\),\s*var\(--right-sidebar-max-width\)\)/); assert.match(styles, /--right-width-expanded:\s*clamp\(var\(--right-sidebar-min-width\),\s*var\(--right-sidebar-width\),\s*var\(--right-sidebar-max-width\)\)/); assert.match(styles, /--rail-collapsed-width:\s*44px/); assert.match(styles, /\.workbench-shell\.is-left-sidebar-collapsed\s*{[^}]*--rail-width:\s*var\(--rail-collapsed-width\);/s); assert.match(styles, /\.workbench-shell\.is-left-sidebar-collapsed \.activity-rail \.rail-button:not\(\.sidebar-toggle\),\s*\n\.workbench-shell\.is-left-sidebar-collapsed \.rail-spacer\s*{[^}]*display:\s*none;/s); assert.match(styles, /\.sidebar-toggle\[aria-expanded="false"\]\s*{/); assert.match(styles, /\.right-sidebar-resize\s*{[^}]*cursor:\s*col-resize;/s); assert.match(styles, /@media \(max-width: 1240px\)[\s\S]*?\.right-sidebar-resize\s*{[\s\S]*?display:\s*none;/); for (const viewId of ["workspace", "gate"]) { assert.match(html, new RegExp(`data-view="${viewId}"`)); } assert.match(html, /data-route="help"/); assert.match(html, /data-view="help"/); assert.match(html, /
\s*\[data-app-shell\]\s*{[^}]*height:\s*100%;[^}]*overflow:\s*hidden;[^}]*overscroll-behavior:\s*contain;/s); assert.match(styles, /\.workbench-shell\s*{[^}]*height:\s*100dvh;[^}]*max-height:\s*100dvh;[^}]*overflow:\s*hidden;[^}]*overscroll-behavior:\s*contain;/s); assert.match(helpMarkdown, /^# 云工作台内部使用说明/m); for (const helpTerm of [ "活动栏与默认工作区", "Agent 对话与工作清单", "完整中文标签", "内部复核", "BOX-SIMU", "Gateway-SIMU", "hwlab-patch-panel", "控制", "接线", "可信记录", "诊断", "Gate", "SOURCE", "LOCAL", "DRY-RUN", "DEV-LIVE", "BLOCKED", "res_boxsimu_1:DO1 -> hwlab-patch-panel -> res_boxsimu_2:DI1", "same-origin `/v1`", "`/v1/diagnostics/gate`", "只读 `/json-rpc`", "`/health/live`", "`:16666`", "`:16667`" ]) { assert.match(helpMarkdown, new RegExp(escapeRegExp(helpTerm)), `missing help term: ${helpTerm}`); } const defaultFirstViewportText = visibleTextFromHtml(defaultFirstViewportHtml(html)); for (const backendTerm of ["Gate", "诊断", "验收", "BLOCKED", "SOURCE", "DRY-RUN", "DEV-LIVE", "M0-M5", "M3 基本", "trace/evidence", "执行轨迹", "OPENAI_API_KEY", "Secret", "secretRef", "hwlab-code-agent-provider", "m3-route-required", "waiting-for-dev-live"]) { assert.equal(textHasForbiddenTerm(defaultFirstViewportText, backendTerm), false, `default workbench must not expose backend term ${backendTerm}`); } for (const fullNavLabel of ["工作台", "内部复核", "使用说明"]) { assert.match(html, new RegExp(`>${escapeRegExp(fullNavLabel)}<`), `missing full nav label ${fullNavLabel}`); } for (const oneCharNavLabel of [">台<", ">证<", ">诊<", ">帮<"]) { assert.doesNotMatch(html, new RegExp(escapeRegExp(oneCharNavLabel)), `legacy one-character nav label must be removed: ${oneCharNavLabel}`); } assert.doesNotMatch(html, /data-side-tab-jump="records"/u, "trusted records must not remain a left-side standalone shortcut"); const wiringPanel = html.match(/]*\bid="panel-wiring"[\s\S]*?<\/section>/u)?.[0] ?? ""; assert.match(wiringPanel, /data-wiring-layout="two-column-long-table"/u, "wiring panel must use the #276 two-column long-table layout"); assert.equal((wiringPanel.match(/${escapeRegExp(legacyWideHeader)}<\\/th>`), `legacy wide wiring column must not return: ${legacyWideHeader}`); } const gateHtml = sectionSource(html, "gate"); assert.match(gateHtml, /

内部复核<\/h2>/); assert.match(gateHtml, /id="gate-source-status"/); assert.match(gateHtml, /id="gate-status-filter"/); assert.match(gateHtml, /id="gate-search"/); assert.match(gateHtml, /id="gate-refresh"/); assert.match(gateHtml, /class="gate-table"/); assert.equal((gateHtml.match(/class="gate-table"/gu) ?? []).length, 1, "/gate must contain one main table"); for (const gateHeader of ["类别", "检查项", "状态", "归因对象", "关键细节", "证据/trace", "更新时间", "下一步"]) { assert.match(gateHtml, new RegExp(`${escapeRegExp(gateHeader)}<\\/th>`), `missing gate table column ${gateHeader}`); } for (const gateStatus of ["通过", "阻塞", "失败", "待验证", "信息"]) { assert.match(`${gateHtml}\n${app}`, new RegExp(escapeRegExp(gateStatus)), `missing gate status ${gateStatus}`); } for (const forbiddenGateMarker of ["workspace-panel", "gate-grid", "gate-split", "milestone-grid", "trace-list", "health-body", "diagnostics-list", "method-list", "unblock-list"]) { assert.doesNotMatch(gateHtml, new RegExp(escapeRegExp(forbiddenGateMarker)), `/gate must not contain legacy dashboard marker ${forbiddenGateMarker}`); } assert.match(app, /fetchJson\("\/v1\/diagnostics\/gate"/); assert.match(app, /function renderGateTable/); assert.match(app, /function normalizeGateRow/); assert.match(app, /function gateLiveBlockerRow/); assert.match(app, /sourceKind:\s*"LIVE-BACKEND"/); assert.match(app, /live 后端聚合不可用;页面不会回退到 SOURCE 静态拓扑。/); assert.doesNotMatch(app, /function\s+(?:renderTrace|renderUnblockList|renderDiagnostics|renderGateDiagnostics|renderMethodList)\s*\(/); assert.match(styles, /\.gate-table\s*{/); assert.match(styles, /\.gate-controls\s*{/); assert.doesNotMatch(styles, /\.gate-grid\s*{/); assert.doesNotMatch(styles, /\.milestone-grid\s*{/); assert.doesNotMatch(styles, /\.trace-list\s*{/); for (const trustedRecordTerm of requiredTrustedRecordTerms) { assert.match(app, new RegExp(escapeRegExp(trustedRecordTerm)), `missing trusted-record term: ${trustedRecordTerm}`); } assert.match(codeAgentM3Evidence, /export function extractCodeAgentM3Evidence/); assert.match(codeAgentM3Evidence, /operationId/); assert.match(codeAgentM3Evidence, /auditId/); assert.match(codeAgentM3Evidence, /evidenceId/); assert.match(codeAgentM3Evidence, /readback-mismatch/); assert.match(codeAgentM3Evidence, /direct-path-invalid/); assert.match(codeAgentM3Evidence, /未产生\/不可证明/); assert.match(codeAgentM3EvidenceContractSummary(), /operation\/audit\/evidence/u); assert.equal(extractCodeAgentM3Evidence(m3EvidenceFixture).verdict.key, "accepted"); assert.equal(m3EvidenceRows(extractCodeAgentM3Evidence(m3EvidenceFixture)).find((row) => row.label === "auditId")?.value, "aud_check_m3_evidence"); assert.match(app, /extractCodeAgentM3Evidence\(result\)/); assert.match(app, /function messageM3EvidencePanel/); assert.match(app, /function m3EvidenceRowElement/); assert.match(app, /function copyButton/); assert.match(app, /isCodeAgentM3SkillCompletion\(value\)/); assert.match(functionBody(app, "renderWiringList"), /M3_TRUSTED_ROUTE\.fromResourceId/); assert.match(functionBody(app, "renderWiringList"), /M3_TRUSTED_ROUTE\.patchPanelServiceId/); assert.doesNotMatch(functionBody(app, "renderWiringList"), /activeConnections\.map/u); assert.doesNotMatch(functionBody(app, "renderWiringList"), /link\.fromResourceId/u); assert.match(buildScript, /buildCloudWebDist/); assert.match(distContractScript, /"help\.md"/); assert.match(distContractScript, /"code-agent-m3-evidence\.mjs"/); assert.match(distContractScript, /"third_party\/marked\/marked\.esm\.js"/); assert.match(distContractScript, /"third_party\/marked\/LICENSE"/); assert.match(distContractScript, /"live-status\.mjs"/); assert.match(distContractScript, /gate\/index\.html/); assert.match(distContractScript, /diagnostics\/gate\/index\.html/); assert.match(distContractScript, /help\/index\.html/); assert.match(markedLicense, /MarkedJS/); assert.match(markedLicense, /Permission is hereby granted, free of charge/); assert.match(html, /live-status/); assert.match(app, /function workbenchApiSurfaceStatus/); assert.match(app, /classifyWorkbenchLiveStatus/); assert.match(liveStatus, /export function classifyWorkbenchLiveStatus/); assert.match(liveStatus, /label:\s*"API 正常"/); assert.match(liveStatus, /label:\s*"API 错误"/); assert.match(liveStatus, /label:\s*"等待验证"/); assert.match(liveStatus, /label:\s*"只读模式"/); assert.match(liveStatus, /internalRawStatuses/); assert.match(liveStatus, /function classifyCodeAgentProbe/); assert.match(liveStatus, /function classifyM3ControlProbe/); assert.match(liveStatus, /function classifyM3StatusProbe/); assert.match(liveStatus, /\/v1\/agent\/chat/); assert.match(liveStatus, /\/v1\/m3\/io/); assert.match(liveStatus, /\/v1\/m3\/status/); assert.match(liveStatus, /hwlab-cloud-api/); assert.match(liveStatus, /hwlab-patch-panel/); assert.match(liveStatus, /hwlab-gateway-simu/); assert.match(liveStatus, /hwlab-box-simu/); assert.match(liveStatus, /runtime_durable_adapter_/); assert.match(liveStatus, /fallback_text_chat_only/); assert.match(liveStatus, /provider_http_/); assert.match(liveStatus, /rawStatus/); assert.match(liveStatus, /evidenceSummary/); assert.match(liveStatus, /function evidenceSummaryFrom/); assert.match(liveStatus, /api_degraded_without_attribution/); assert.match(liveStatus, /API readiness is not pass and no concrete blocker was provided/); assert.match(liveStatus, /trust\?\.evidenceId/); assert.doesNotMatch(liveStatus, /API returned \$\{status\}/); assert.doesNotMatch(functionBody(app, "workbenchApiSurfaceStatus"), /surface\.degraded/); assert.doesNotMatch(app, /API 错误 \/ 只读模式/); assert.doesNotMatch(app, /可信记录受阻 \/ 只读模式/); assert.doesNotMatch(app, /API 降级 \/ 只读模式/); assert.doesNotMatch(functionBody(app, "workbenchApiSurfaceStatus"), /runtime_durable_adapter_query_blocked|DB live readiness|Gate|诊断|验收|M0-M5|执行轨迹/u); assert.match(html, /placeholder="输入要发送给 Code Agent 的消息;不会直接触发硬件变更。"/); assert.match(html, />发送<\/button>/); assert.doesNotMatch(html, />添加草稿<\/button>/); assert.doesNotMatch(html, /主流程[\s\S]*添加草稿/u); assert.match(app, /sendAgentMessage/); assert.match(app, /fetchJson\("\/v1\/agent\/chat"/); assert.match(app, /fetchJson\("\/v1\/m3\/io"/); assert.match(app, /fetchJson\("\/v1\/m3\/status"\)/); assert.match(html, /id="m3-control-form"/); assert.match(html, /id="m3-control-status"/); assert.match(html, />写入 DO1<\/button>/); assert.match(html, />读取 DI1<\/button>/); assert.match(app, /function runM3IoAction/); assert.match(app, /function renderM3ControlStatus/); assert.match(app, /frontendBypass=false/); assert.match(app, /按钮只走受控后端/); assert.match(app, /页面不直连模拟器,也不伪造硬件状态。/); assert.match(styles, /\.m3-control-form\s*{/); assert.match(html, /data-hardware-tab="overview"[\s\S]*>总览<\/button>/); assert.match(html, /data-hardware-tab="gateways"[\s\S]*>Gateway-SIMU<\/button>/); assert.match(html, /data-hardware-tab="box1"[\s\S]*>BOX-SIMU-1<\/button>/); assert.match(html, /data-hardware-tab="box2"[\s\S]*>BOX-SIMU-2<\/button>/); assert.match(html, /data-hardware-tab="patch"[\s\S]*>Patch Panel<\/button>/); assert.match(app, /function initHardwareTabs/); assert.match(app, /function hardwareKvGroup/); assert.match(app, /function m3LinkStatus/); assert.match(app, /sourceFallbackM3Status/); for (const hardwareLabel of ["链路状态", "接线盘连接", "在线", "更新时间", "阻塞原因", "接线状态", "连接已激活"]) { assert.match(app, new RegExp(escapeRegExp(hardwareLabel)), `missing Chinese hardware label ${hardwareLabel}`); } assert.match(styles, /\.hardware-status-tabs\s*\{/); assert.match(styles, /\.kv-list\s*\{/); assert.match(styles, /\.right-sidebar \.table-wrap\s*\{[^}]*overflow-x:\s*hidden;/s); assert.match(app, /Code Agent 调用失败/); assert.match(app, /normalizeBlockedAgentResult/); assert.match(app, /isBlockedAgentResponse/); assert.match(app, /isHttpNon2xxStatus/); assert.match(app, /providerStatus/); assert.match(app, /codeAgentAvailability/); assert.match(app, /codeAgentAvailabilityFrom/); assert.match(app, /sanitizeCodeAgentAvailability/); assert.match(app, /latestCompletedAgentMessage/); assert.match(app, /服务受阻/); assert.match(app, /provider_unavailable/); assert.match(app, /OPENAI_API_KEY/); assert.match(app, /只有真实完成回复才显示为完成/); assert.match(app, /不能因为只有会话编号就当成实况完成/); assert.match(app, /TRUSTED_CODE_AGENT_PROVIDERS/); assert.match(app, /UNTRUSTED_CODE_AGENT_PROVIDER_PATTERN/); assert.match(app, /isRealCompletedChatResult/); assert.match(app, /isRealCompletedChatMessage/); assert.match(app, /hasRealCodeAgentEvidence/); assert.match(app, /isTrustedCodeAgentProvider\(value\?\.provider\)/); assert.match(app, /Boolean\(value\?\.model\)/); assert.match(app, /Boolean\(value\?\.backend\)/); assert.match(app, /Boolean\(value\?\.traceId\)/); assert.match(app, /Boolean\(value\?\.conversationId \|\| value\?\.sessionId\)/); assert.match(app, /value\?\.session\?\.status === "idle"/); assert.match(app, /typeof value\?\.session\?\.idleTimeoutMs === "number"/); assert.match(app, /Boolean\(value\?\.session\?\.lastTraceId\)/); assert.match(app, /Boolean\(value\?\.sessionReuse\)/); assert.match(app, /value\?\.sessionMode === CODEX_APP_SERVER_SESSION_MODE/); assert.match(app, /value\?\.implementationType === CODEX_APP_SERVER_IMPLEMENTATION_TYPE/); assert.match(app, /threadIdFrom\(value\)/); assert.match(app, /hasProviderTrace\(value\)/); assert.match(app, /value\?\.capabilityLevel === "long-lived-codex-stdio-session"/); assert.match(app, /value\?\.session\?\.codexStdio === true/); assert.match(app, /value\?\.runner\?\.codexStdio === true/); assert.match(app, /value\?\.runner\?\.writeCapable === true/); assert.match(app, /value\?\.runner\?\.durableSession === true/); assert.match(app, /providerTrace:\s*result\.providerTrace/); assert.match(app, /CODEX_APP_SERVER_RUNNER_KIND/); assert.match(app, /CODEX_APP_SERVER_PROTOCOL/); assert.match(app, /value\?\.providerTrace\?\.protocol === CODEX_APP_SERVER_PROTOCOL/); assert.match(app, /providerTrace\.command/); assert.match(app, /providerTrace\.terminalStatus/); assert.match(app, /session:\s*result\.session/); assert.match(app, /sessionMode:\s*result\.sessionMode/); assert.match(app, /sessionReuse:\s*result\.sessionReuse/); assert.match(app, /implementationType:\s*result\.implementationType/); assert.match(app, /runnerLimitations:\s*result\.runnerLimitations/); assert.match(app, /codexStdioFeasibility:\s*result\.codexStdioFeasibility/); assert.match(app, /longLivedSessionGate:\s*result\.longLivedSessionGate/); assert.match(app, /conversationFacts:\s*result\.conversationFacts/); assert.match(app, /"Prefer":\s*"respond-async"/); assert.match(app, /function waitForAgentMessageResult/); assert.match(app, /\/v1\/agent\/chat\/result\/\$\{encodeURIComponent\(traceId\)\}/); assert.match(app, /function messageTracePanel/); assert.match(app, /CODE_AGENT_TRACE_PREVIEW_LIMIT\s*=\s*14/); assert.match(app, /function messageTraceToolbar/); assert.match(app, /function messageTraceCountText/); assert.match(app, /显示最近\s+\$\{CODE_AGENT_TRACE_PREVIEW_LIMIT\}\s+\/\s+可读\s+\$\{displayTotal\}\s+\/\s+原始\s+\$\{rawTotal\}/); assert.match(app, /展开全部/); assert.match(app, /复制 JSON/); assert.match(app, /下载 trace/); assert.match(app, /function renderTraceEventList/); assert.match(app, /function traceDisplayRows/); assert.match(app, /function traceDisplayRow/); assert.match(app, /function traceNoiseSummaryRow/); assert.match(app, /assistant stream x\$\{visibleEvents\.length\}/); assert.match(app, /compressed=\$\{assistantChunks\.length\} assistant chunks/); assert.match(app, /waiting first assistant token/); assert.match(app, /function compactTraceTextTail/); assert.match(app, /function isAssistantChunkTraceEvent/); assert.match(app, /function downloadTraceJson/); assert.match(styles, /\.message-trace-toolbar\s*\{/); assert.match(styles, /\.message-trace-count\s*\{/); assert.match(styles, /\.message-trace-action\s*\{/); assert.match(styles, /\.message-trace-events-full\s*\{[\s\S]*?max-height:\s*min\(360px,\s*42vh\)[\s\S]*?overflow:\s*auto/); assert.match(app, /function messageRuntimePathPanel/); assert.match(app, /codeAgentRuntimePathFromMessage/); assert.match(app, /function runnerTraceHeadline/); assert.match(app, /function traceEventMeta/); assert.match(app, /function subscribeRunnerTrace/); assert.match(app, /function pollRunnerTrace/); assert.match(app, /TRACE_STREAM_FALLBACK_MS\s*=\s*2500/); assert.match(app, /fallbackStop\s*=\s*pollRunnerTrace\(traceId,\s*messageId\)/); assert.match(app, /TRACE_POLL_INTERVAL_MS/); assert.match(app, /function updateMessageTrace/); assert.match(app, /function runnerTraceFromSnapshot/); assert.match(app, /new EventSource\(`\/v1\/agent\/chat\/trace\/\$\{encodeURIComponent\(traceId\)\}\/stream`\)/); assert.match(app, /source\.addEventListener\("runnerTrace"/); assert.doesNotMatch(app, /providerTraceSummary\(message\.providerTrace\)/); assert.doesNotMatch(app, /function messageEvidenceFields/); assert.match(app, /codeAgentSummaryRow\("sessionId\/status", summary\.sessionIdStatus/); assert.match(app, /codeAgentSummaryRow\("workspace", summary\.workspace/); assert.match(app, /codeAgentSummaryRow\("sandbox", summary\.sandbox/); assert.match(app, /codeAgentSummaryRow\("runnerKind", summary\.runnerKind/); assert.match(app, /codeAgentSummaryRow\("protocol", summary\.protocol/); assert.match(app, /codeAgentSummaryRow\("implementationType", summary\.implementationType/); assert.match(app, /codeAgentSummaryRow\("providerTrace\.command", summary\.providerTraceCommand/); assert.match(app, /codeAgentSummaryRow\("providerTrace\.terminalStatus", summary\.providerTraceTerminalStatus/); assert.match(app, /codeAgentSummaryRow\("运行路径语义", summary\.runtimePathLabel/); assert.match(app, /codeAgentSummaryRow\("toolCalls", summary\.toolCalls/); assert.match(app, /codeAgentSummaryRow\("skills", summary\.skills/); assert.match(app, /codeAgentSummaryRow\("conversation facts", conversationFactsSummary\(summary\.latestMessage\?\.conversationFacts\)/); assert.match(app, /codeAgentSummaryRow\("fact traces", conversationFactTracesSummary\(summary\.latestMessage\?\.conversationFacts\)/); assert.match(app, /codeAgentSummaryRow\("runnerTrace", summary\.runnerTrace/); assert.match(app, /function sessionSummary/); assert.match(app, /function conversationFactsSummary/); assert.match(app, /function codeAgentAvailabilitySummary/); assert.doesNotMatch(app, /function longLivedSessionGateSummary/); assert.doesNotMatch(app, /function conversationFactRows/); assert.match(app, /not-codex-stdio/); assert.match(app, /not-write-capable/); assert.match(app, /process-local-session-registry/); assert.match(app, /Code Agent 完成证据不足/); assert.match(app, /本次不会标记为真实完成/); assert.match(app, /untrusted_completion/); assert.match(app, /function classifyCodeAgentCompletion/); assert.match(app, /function isSourceFixtureChatResult/); assert.match(app, /function isSourceFixtureCompletion/); assert.match(app, /function isSourceFixtureCompletedChatMessage/); assert.match(app, /Code Agent SOURCE 回复/); assert.match(app, /SOURCE fixture 只可显示为 SOURCE 回复,不能冒充 DEV-LIVE/); assert.match(app, /function messageTracePanel/); assert.match(app, /function subscribeRunnerTrace/); assert.match(app, /new EventSource\(`\/v1\/agent\/chat\/trace\/\$\{encodeURIComponent\(traceId\)\}\/stream`\)/); assert.match(app, /source\.addEventListener\("runnerTrace"/); assert.match(app, /function pollRunnerTrace/); assert.match(app, /function updateMessageTrace/); assert.match(app, /function runnerTraceFromSnapshot/); assert.doesNotMatch(app, /function messageEvidencePanel/); assert.doesNotMatch(app, /function messageAttributionPanel/); assert.doesNotMatch(app, /function codeAgentCapabilityFactsPanel/); assert.doesNotMatch(app, /codeAgentAttributionFromMessage/); assert.doesNotMatch(app, /codeAgentFactsFromMessage/); assert.doesNotMatch(app, /compactHwlabApiFact/); assert.match(app, /function renderCodeAgentSummary/); assert.match(app, /currentCodeAgentStatusSummary/); assert.match(app, /classifyCodeAgentStatusSummary/); assert.match(app, /codeAgent\.status/); assert.match(app, /当前部署 revision/); assert.match(codeAgentFacts, /openai-responses-fallback/); assert.match(codeAgentFacts, /text-chat-only/); assert.match(codeAgentFacts, /OpenAI fallback:只是文本回答/); assert.match(codeAgentFacts, /字段缺失:证据不足/); assert.match(codeAgentFacts, /codeAgentAttributionFromMessage/); assert.match(codeAgentFacts, /stateless-one-shot/); assert.match(codeAgentFacts, /controlled-readonly-session-registry/); assert.match(codeAgentFacts, /read-only-session-tools:只读长会话/); assert.match(codeAgentFacts, /hwlab-m3-io-skill-cli/); assert.match(codeAgentFacts, /route", fact\.route/); assert.match(codeAgentFacts, /operationId", fact\.operationId/); assert.match(codeAgentFacts, /auditId", fact\.auditId/); assert.match(codeAgentFacts, /evidenceId", fact\.evidenceId/); assert.match(codeAgentFacts, /blocker", fact\.blocker/); assert.match(codeAgentStatus, /fallback-text-chat-only/); assert.match(codeAgentStatus, /stateless-one-shot/); assert.match(codeAgentStatus, /read-only-session-tools/); assert.match(codeAgentStatus, /skill-cli-api-control/); assert.match(codeAgentStatus, /currentDeploymentRevision/); assert.match(codeAgentStatus, /unsafeGreenForNonReady/); assert.match(codeAgentStatus, /provider_config_blocked/); assert.doesNotMatch(codeAgentStatus, /sourceMain|SOURCE main|待部署 commit|targetCommit/u); assert.match(app, /sourceKind:\s*completion\.sourceKind/); assert.match(app, /function\s+agentStatusTone\s*\(/); assert.match(app, /m3_io_result/); assert.doesNotMatch(app, /status === "failed" \? "dev-live"/); assert.doesNotMatch(`${html}\n${app}`, /provider_unavailable[\s\S]{0,120}tone-[\w-]*dev-live/iu); assert.doesNotMatch(app, /tone:\s*state\.conversationId\s*\?\s*"dev-live"/); assert.doesNotMatch(`${html}\n${app}`, /sk-[A-Za-z0-9._-]{8,}/u); assert.match(styles, /\.message-card\.status-completed\s*{/); assert.match(styles, /\.message-card\.status-source\s*{/); assert.match(styles, /\.message-user\s*{/); assert.match(styles, /\.message-agent,\s*\n\.message-system\s*{/); assert.match(styles, /\.message-pending-context\s*{/); assert.match(styles, /\.message-session-context\s*{/); assert.match(styles, /\.message-compact-details\s*{/); assert.match(styles, /\.workbench-dialog-layer\s*{/); assert.match(styles, /\.workbench-dialog\s*{/); assert.match(app, /function openWorkbenchDialog/); assert.match(app, /document\.body\.append\(layer\)/); assert.match(styles, /\.message-m3-evidence\s*{/); assert.match(styles, /\.message-m3-rows\s*{/); assert.match(styles, /\.message-m3-row\s*{/); assert.match(styles, /\.copy-chip\s*{/); assert.match(styles, /\.message-trace\s*{/); assert.match(styles, /\.message-trace-events\s*{/); assert.match(styles, /\.message-trace-line\s*{/); assert.match(styles, /\.message-trace-body\s*{/); assert.doesNotMatch(styles, /\.message-attribution\s*{/); assert.doesNotMatch(styles, /\.message-evidence\s*{/); assert.doesNotMatch(styles, /\.code-agent-facts\s*{/); assert.match(styles, /\.code-agent-summary\s*{/); assert.match(styles, /\.tone-border-warn/); assert.match(styles, /\.tone-border-blocked/); assert.match(styles, /\.tone-border-ok/); assert.match(styles, /\.hwlab-api-fact-code\s*{/); assert.match(styles, /\.hwlab-api-fact-code\s*{[^}]*user-select:\s*all;/s); assert.match(styles, /\.rail-button\s*{[^}]*min-width:\s*0;[^}]*min-height:\s*36px;[^}]*text-align:\s*center;[^}]*word-break:\s*keep-all;/s); assert.match(styles, /\.side-tab\s*{[^}]*min-width:\s*0;[^}]*min-height:\s*34px;[^}]*overflow-wrap:\s*anywhere;[^}]*word-break:\s*keep-all;/s); assert.match(styles, /\.(?:status-dot|state-tag|badge)[^{]*{[^}]*max-width:\s*100%;[^}]*line-height:\s*1\.25;[^}]*white-space:\s*normal;[^}]*overflow-wrap:\s*anywhere;/s); assert.match(styles, /\.probe-card\s*{[^}]*min-width:\s*0;/s); assert.match(styles, /\.probe-card strong\s*{[^}]*line-height:\s*1\.2;[^}]*overflow-wrap:\s*anywhere;/s); assert.match(styles, /@media \(max-width: 860px\)[\s\S]*?\.message-m3-rows\s*{[\s\S]*?grid-template-columns:\s*1fr;/); assert.match(styles, /@media \(max-width: 860px\)[\s\S]*?\.message-runtime-path,\s*\n\s*\.message-session-context,\s*\n\s*\.message-trace,\s*\n\s*\.message-m3-evidence,\s*\n\s*\.message-actions\s*{[\s\S]*?grid-column:\s*1;/); for (const userFacingFunctionName of [ "codeAgentStatusMessage", "codeAgentPromptText", "codeAgentControlSummary", "codeAgentBlockedSummary", "codeAgentAvailabilitySummary", "failureMessage", "safeFailureReason" ]) { assert.doesNotMatch(functionBody(app, userFacingFunctionName), /OPENAI_API_KEY|hwlab-code-agent-provider|Secret|secretRef|m3-route-required|waiting-for-dev-live|BLOCKED 凭证缺口/u, `${userFacingFunctionName} must keep default copy sanitized`); } assert.match(app, /state\.conversationId/); assert.match(app, /conversationId/); assert.match(app, /state\.threadId/); assert.match(app, /threadIdForNextRequest/); assert.match(app, /threadId:\s*requestedThreadId/); assert.match(app, /function\s+completedSessionContinuity/); assert.match(app, /function\s+codeAgentContinuityBlocker/); assert.match(app, /code_agent_session_evidence_missing/); assert.match(app, /Code Agent completed 但会话证据缺失/); assert.match(app, /继续当前会话/); assert.match(app, /新会话\/会话降级/); assert.match(app, /messageId/); assert.match(app, /createdAt/); assert.match(app, /updatedAt/); assert.match(app, /error\?\.message/); assert.match(app, /DEFAULT_API_TIMEOUT_MS\s*=\s*4500/); assert.match(app, /DEFAULT_CODE_AGENT_TIMEOUT_MS\s*=\s*600000/); assert.match(app, /CODE_AGENT_SUBMIT_TIMEOUT_MS/); assert.match(app, /CODE_AGENT_TIMEOUT_MS/); assert.match(functionBody(app, "sendAgentMessage"), /timeoutMs:\s*CODE_AGENT_SUBMIT_TIMEOUT_MS/); assert.match(functionBody(app, "waitForAgentMessageResult"), /CODE_AGENT_TIMEOUT_MS/); assert.match(app, /HWLAB_CLOUD_WEB_CONFIG/); assert.match(app, /sourceKind:\s*"PENDING"/); assert.match(app, /旧 4500ms/); assert.match(app, /function messagePendingContextPanel/); assert.match(app, /message-pending-context/); assert.match(app, /function\s+restartRunningAgentMessage/); assert.match(app, /先取消当前 trace,再用同一输入重新发送/); assert.match(app, /canRetryTerminalAgentMessage/); assert.match(app, /结构化 blocker/); assert.match(app, /Code Agent 超过 \$\{response\.timeoutMs\}ms 无新事件/); assert.match(app, /agentFailurePresentation/); assert.match(app, /agentStatusLabel/); assert.match(app, /Code Agent 无新事件超时/); assert.match(app, /Code Agent Runner 忙碌/); assert.match(app, /Code Agent Session 受阻/); assert.match(app, /Code Agent API 错误/); assert.match(app, /等待超时/); assert.match(app, /Provider 不可用/); assert.match(app, /Runner 忙碌/); assert.match(app, /Session 受阻/); assert.match(app, /API 错误/); assert.match(app, /后端失败/); assert.doesNotMatch(app, /发送失败/); assert.doesNotMatch(html, /M3 Diagnostics Console/); assert.match(styles, /html,\s*\nbody\s*{[^}]*height:\s*100%;[^}]*overflow:\s*hidden;/s); assert.match(styles, /body\s*>\s*\[data-app-shell\]\s*{[^}]*min-height:\s*0;/s); assert.match(styles, /\.workbench-shell\s*{[^}]*height:\s*100(?:d)?vh;[^}]*overflow:\s*hidden;/s); assert.match(styles, /\.view\s*{[^}]*min-height:\s*0;[^}]*overflow:\s*auto;/s); assert.match(styles, /\.(?:compact-list|conversation-list|hardware-list)[^{]*{[^}]*min-height:\s*0;[^}]*overflow:\s*auto;/s); assert.doesNotMatch(html, /task-panel|task-list|下一步任务草稿/); assert.doesNotMatch(app, /taskList|renderTaskList/); assert.doesNotMatch(styles, /\.task-panel|\.task-list/); assert.match(styles, /@media \(max-width: 860px\)[\s\S]*?\.activity-rail\s*{[\s\S]*?grid-row:\s*1 \/ 3;/); assert.match(styles, /@media \(max-width: 860px\)[\s\S]*?\.center-workspace\s*{[\s\S]*?grid-row:\s*1;/); assert.match(styles, /@media \(max-width: 860px\)[\s\S]*?\.right-sidebar\s*{[\s\S]*?grid-row:\s*2;/); assert.match(styles, /@media \(max-width: 520px\)[\s\S]*?\.command-bar\s*{[\s\S]*?grid-template-columns:\s*minmax\(0, 1fr\) auto auto;/); assert.match(app, /fetchJson\("\/v1"\)/); assert.match(app, /fetchJson\("\/v1\/diagnostics\/gate"/); assert.match(app, /fetchJson\("\/v1\/live-builds"\)/); assert.match(html, /id="live-build-latest"/); assert.match(html, /id="live-build-list"/); assert.match(html, /id="live-build-toggle"[^>]*aria-haspopup="dialog"[^>]*aria-expanded="false"/); assert.match(styles, /\.live-build-summary\s*{/); assert.match(app, /initLiveBuildOverlay/); assert.match(app, /openWorkbenchDialog/); assert.match(app, /aria-expanded/); assert.match(app, /formatBeijingTime/); assert.match(app, /Asia\/Shanghai/); assert.match(app, /最新镜像构建时间/); assert.match(app, /构建时间不可用/); assert.match(app, /revision \$\{shortToken\(latest\.revision\)\}/); assert.match(app, /revision \$\{shortToken\(service\.revision\)\}/); assert.match(app, /liveBuildSourceLabel/); assert.match(app, /liveBuildDesiredStateText/); assert.match(app, /外部镜像或非 HWLAB 构建产物/); assert.doesNotMatch(app, /artifact report|artifact-report/); assert.match(app, /live health/); assert.match(styles, /\.live-build-summary-label\s*{[^}]*min-width:\s*0;[^}]*max-width:\s*100%;[^}]*overflow:\s*hidden;[^}]*text-overflow:\s*ellipsis;[^}]*white-space:\s*nowrap;/s); assert.match(styles, /\.live-build-list\s*{[^}]*min-width:\s*0;[^}]*max-height:\s*min\(352px,\s*calc\(58dvh - 54px\)\);[^}]*overflow:\s*auto;/s); assert.match(styles, /\.live-build-row-head\s*{[^}]*grid-template-columns:\s*minmax\(0,\s*0\.8fr\)\s*minmax\(0,\s*1\.2fr\);/s); assert.match(styles, /@media \(max-width: 860px\)[\s\S]*?\.live-build-row-head\s*{[\s\S]*?grid-template-columns:\s*1fr;/); assert.match(styles, /\.live-build-meta span\s*{[^}]*overflow-wrap:\s*anywhere;/s); assert.match(app, /fetchJson\("\/health\/live"\)/); assert.match(app, /callRpc\("system\.health"\)/); assert.match(app, /callRpc\("cloud\.adapter\.describe"\)/); assert.match(app, /callRpc\("audit\.event\.query"/); assert.match(app, /callRpc\("evidence\.record\.query"/); assert.match(artifactPublisher, /HWLAB_API_BASE_URL/); assert.match(artifactPublisher, /HWLAB_CLOUD_WEB_PROXY_TIMEOUT_MS/); assert.match(artifactPublisher, /cloudApiProxyTimeoutMs/); assert.match(cloudWebProxy, /setTimeout\(\(\) => \{/); assert.match(cloudWebProxy, /cloud api proxy timed out after/); assert.match(artifactPublisher, /readOnlyRpcMethods/); assert.match(artifactPublisher, /requestUpstream/); assert.match(artifactPublisher, /proxyCloudApiRequest/); assert.match(artifactPublisher, /isCloudWebSseRoute\(url\.pathname\)/); assert.match(cloudWebProxy, /text\\\/event-stream/); assert.match(cloudWebProxy, /x-accel-buffering/); assert.match(cloudWebProxy, /flushHeaders/); assert.match(cloudWebProxy, /response\.write\(chunk\)/); assert.match(cloudWebProxy, /response\.headersSent\)[\s\S]*?response\.destroy\(error\);[\s\S]*?settle\(resolve\);[\s\S]*?return;/); assert.match(artifactPublisher, /response\.headersSent \|\| response\.writableEnded/); assert.match(artifactPublisher, /if \(!response\.writableEnded\) response\.destroy\(error\);/); assert.doesNotMatch(artifactPublisher, /await fetch\(target/); assert.match(artifactPublisher, /cloudWebProxyRoutePolicy\(request\.method,\s*url\.pathname\)/); assert.match(cloudWebRoutes, /"\/v1\/agent\/chat"/); assert.match(cloudWebRoutes, /"\/v1\/agent\/chat\/cancel"/); assert.match(cloudWebRoutes, /"\/v1\/m3\/io"/); assert.match(cloudWebRoutes, /"POST \/v1\/agent\/chat"/); assert.match(cloudWebRoutes, /isPublicCodeAgentPollRoute/); assert.match(cloudWebRoutes, /\/v1\/agent\/chat\/result\//); assert.match(cloudWebRoutes, /\/v1\/agent\/chat\/trace\//); assert.match(cloudWebRoutes, /const GET_PROXY_PREFIXES = Object\.freeze\(\["\/v1\/"\]\)/); assert.match(cloudWebRoutes, /const GET_PROXY_ROUTES = new Set\(\["\/v1"\]\)/); assert.match(cloudWebRoutes, /authRequired: proxy && !publicRoute/); assert.match(codexStdioSession, /turn:completed:transport_closed_after_assistant/); assert.match(codexStdioSession, /turn:completed:idle_timeout_after_assistant/); assert.match(codexStdioSession, /firstNonEmpty\(finalResponse,\s*assistantText\)/); assert.match(codexStdioSession, /CODEX_STDIO_FIRST_TOKEN_PROGRESS_MS/); assert.match(codexStdioSession, /turn:waiting:first_assistant_token/); assert.match(codexStdioSession, /progressOnly:\s*true/); assert.match(codexStdioSession, /\["assistant-message", "turn\/completed"\]\.includes\(activity\.waitingFor\)/); assert.match(codexStdioSession, /does not reset the backend idle timer/); assert.match(codexStdioSession, /--powershell-stdin/); assert.match(codexStdioSession, /-EncodedCommand/); assert.match(codexStdioSession, /Windows filesystem inventory/); assert.match(codexStdioSession, /Select-Object -First/); assert.match(codexStdioSession, /ConvertTo-Json -Compress/); assert.match(codexStdioSession, /C:\\\\Users\\\\liang\\\\\.agents\\\\skills\\\\keil/); assert.match(gatewayShellTool, /function powershellEncodedCommand/); assert.match(gatewayShellTool, /Buffer\.from\(`\$\{prologue\}\$\{source\}`,\s*"utf16le"\)\.toString\("base64"\)/); assert.match(gatewayShellTool, /--powershell-stdin/); assert.match(gatewayShellTool, /powershell\.exe -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -EncodedCommand/); assert.match(artifactPublisher, /"system\.health"/); assert.match(artifactPublisher, /"cloud\.adapter\.describe"/); assert.match(artifactPublisher, /"audit\.event\.query"/); assert.match(artifactPublisher, /"evidence\.record\.query"/); assert.match(artifactPublisher, /readonly_rpc_required/); assert.match(artifactPublisher, /cloud_api_proxy_failed/); assert.match(artifactPublisher, /cloud_api_proxy_timeout/); assert.match(artifactPublisher, /Code Agent 代理等待 cloud-api 超过/); assert.match(artifactPublisher, /输入已保留,可稍后重试/); assert.match(artifactPublisher, /retryable:\s*true/); assert.match(artifactPublisher, /blocker:\s*\{/); assert.match(artifactPublisher, /const traceId = request\.headers\["x-trace-id"\] \|\| null/); assert.match(artifactPublisher, /traceId,\s*\n\s*route: url\.pathname/); assert.match(artifactPublisher, /url\.pathname\.replace\(\/\\\/\+\$\/,\s*""\) \|\| "\/"/); assert.match(artifactPublisher, /routePath === "\/gate"/); assert.match(artifactPublisher, /routePath === "\/diagnostics\/gate"/); assert.match(artifactPublisher, /routePath === "\/help"/); assert.match(app, /degraded/); assert.deepEqual(runtime.serviceRoute, [ "browser/CLI/gateway", "master hwlab-edge-proxy", "frp", "D601 hwlab-dev/hwlab-router", "cloud-web/cloud-api" ]); assert.doesNotMatch(app, /callRpc\("hardware\./); assert.doesNotMatch(app, /hardware\.operation\.request/); assert.doesNotMatch(app, /hardware\.invoke\.shell/); assert.doesNotMatch(app, /audit\.event\.write/); assert.doesNotMatch(app, /evidence\.record\.write/); assert.doesNotMatch(app, /https?:\/\/[^"']*(?:gateway-simu|box-simu|patch-panel)[^"']*/iu); assert.doesNotMatch(frontendSource, /--live --confirm-dev --confirmed-non-production/); assert.doesNotMatch(frontendSource, /74\.48\.78\.17:666[67]\b|:666[67]\b/); const sourceSummary = loadMvpGateSummary(repoRoot); assert.equal(gateSummary.endpoint, sourceSummary.endpoint); assert.equal(gateSummary.gateStatus, sourceSummary.gateStatus); assert.equal(gateSummary.milestones.length, 6); assert.deepEqual( gateSummary.milestones.map((item) => item.id), ["M0", "M1", "M2", "M3", "M4", "M5"] ); assert.equal(gateSummary.artifactCount, 13); assert.equal(gateSummary.healthCount, 14); assert.equal(gateSummary.gatewaySessionCount, 2); assert.equal(gateSummary.boxResourceCount, 2); assert.equal(gateSummary.topology.patchPanel.serviceId, "hwlab-patch-panel"); assert.deepEqual(gateSummary.topology.gateways.map((gateway) => gateway.gatewayId), ["gwsimu_1", "gwsimu_2"]); assert.deepEqual(gateSummary.topology.boxResources.map((resource) => resource.resourceId), ["res_boxsimu_1", "res_boxsimu_2"]); assert.deepEqual(gateSummary.topology.patchPanel.activeConnections, [ { fromResourceId: "res_boxsimu_1", fromPort: "DO1", toResourceId: "res_boxsimu_2", toPort: "DI1" } ]); assert.doesNotMatch( JSON.stringify(gateSummary.topology), /res_m5-control-relay|res_m5-target-board|out1|reset/u, "16666 default topology must not render M5/reset wiring" ); assert.equal(gateSummary.m5DryRunTopology.patchPanel.patchPanelStatusId, "pps_m5-0001"); assert.equal(gateSummary.agent.agentServiceId, "hwlab-agent-mgr"); assert.equal(gateSummary.agent.workerServiceId, "hwlab-agent-worker"); assert.equal(gateSummary.operations.length, 2); assert.ok(gateSummary.operations.every((operation) => operation.dryRun === true)); assert.ok(gateSummary.operations.every((operation) => operation.requestedAt && operation.updatedAt)); assert.equal(gateSummary.auditEvents.length, 7); assert.ok(gateSummary.auditEvents.every((event) => event.dryRun === true)); assert.ok(gateSummary.auditEvents.every((event) => event.traceId && event.occurredAt)); assert.equal(gateSummary.evidenceRecords.length, 2); assert.ok(gateSummary.evidenceRecords.every((record) => record.traceId && record.createdAt && record.dryRun === true)); assert.equal(gateSummary.safety.allowNetwork, false); runCloudWebM3ReadonlyContract(); runM3ControlPanelGuard(); runWorkbenchHardwarePanelContract(); console.log("hwlab-cloud-web check ok: workbench shell, M3 control guard, hardware evidence panel, and read-only diagnostics contract are present"); function escapeRegExp(value) { return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); } function sectionSource(source, viewId) { const start = source.search(new RegExp(`]*\\bdata-view=["']${escapeRegExp(viewId)}["'][^>]*>`, "u")); if (start === -1) return ""; const tagPattern = /<\/?section\b[^>]*>/giu; tagPattern.lastIndex = start; let depth = 0; for (let match = tagPattern.exec(source); match; match = tagPattern.exec(source)) { if (match[0].startsWith("]*\\bdata-view=["']${escapeRegExp(viewId)}["'][^>]*>`, "u")); return index === -1 ? source : source.slice(0, index); } function defaultFirstViewportHtml(source) { return `${beforeSection(source, "help")}\n${rightSidebarDefaultHtml(source)}`; } function rightSidebarDefaultHtml(source) { const start = source.search(/]*\bclass=["'][^"']*\bright-sidebar\b[^"']*["'][^>]*>/u); if (start === -1) return ""; const wiringStart = source.slice(start).search(/]*\bid=["']panel-wiring["'][^>]*>/u); if (wiringStart !== -1) return source.slice(start, start + wiringStart); const end = source.slice(start).search(/<\/aside>/u); return end === -1 ? source.slice(start) : source.slice(start, start + end); } function visibleTextFromHtml(fragment) { return fragment .replace(//giu, " ") .replace(//giu, " ") .replace(/<[^>]+>/gu, " ") .replace(/ /gu, " ") .replace(/>/gu, ">") .replace(/</gu, "<") .replace(/&/gu, "&") .replace(/\s+/gu, " ") .trim(); } function textHasForbiddenTerm(text, term) { if (/^[A-Za-z][A-Za-z0-9-]*$/u.test(term)) { return new RegExp(`\\b${escapeRegExp(term)}\\b`, "u").test(text); } return text.includes(term); } function compactLayoutSmokeFailures(report) { return { status: report.status, sourceMode: report.sourceMode, artifacts: report.artifacts, blockers: (report.blockers ?? []).map(compactLayoutFailure), failures: (report.failures ?? []).map(compactLayoutFailure) }; } function compactLayoutFailure(failure) { return { checkId: failure.checkId ?? failure.scope ?? null, viewport: failure.viewport ?? null, selector: failure.selector ?? null, failureType: failure.failureType ?? "blocked", summary: failure.summary ?? null, artifact: failure.artifact ?? null }; } function functionBody(source, functionName) { const match = source.match(new RegExp(`function\\s+${escapeRegExp(functionName)}\\s*\\([^)]*\\)\\s*\\{`, "u")); if (!match) return ""; let depth = 0; for (let index = match.index + match[0].length - 1; index < source.length; index += 1) { const char = source[index]; if (char === "{") depth += 1; if (char === "}") { depth -= 1; if (depth === 0) return source.slice(match.index, index + 1); } } return ""; }