Files
pikasTech-HWLAB/docs/reference/spec-v02-hwlab-cloud-web.md
T
2026-06-06 17:38:39 +08:00

40 KiB
Raw Blame History

v0.2 hwlab-cloud-web 服务规格

hwlab-cloud-webv0.2 浏览器工作台,运行在 hwlab-v02 namespace,内部端口 8080,公网经 FRP 暴露为 http://74.48.78.17:19666/

Provider API Key 配置入口也归属 Cloud Web:左侧顶级导航必须提供“管理”页面,具体路由、状态展示、API Key 写入表单、验证结果展示和脱敏规则见 spec-v02-provider-management.md。Cloud Web 不直接调用 AgentRun,不保存完整 API Key,也不把 AgentRun token 暴露给浏览器。

在系统中的职责划分

  • 向用户提供 Cloud Workbench、Code Agent 对话、live status、HWPOD node-ops 右侧面板、trace 展示和帮助内容。
  • 只消费 hwlab-cloud-api,不直接访问 Postgres、gateway、host/node 资源、FRP、Kubernetes 或 provider Secret。
  • 为浏览器提供同源代理,避免前端直接跨域调用内部 ClusterIP。
  • Web 登录按 spec-v02-auth.md 走 Keycloak OIDC;未登录用户进入 Keycloak 登录/注册,callback 后由 cloud-api 发行 24 小时 hwlab_session
  • Cloud Web 提供 API key 管理入口,让用户查看默认 API key、创建新 key、revoke 或 regenerate;浏览器日常请求仍使用 Web session,不要求用户手动输入 API key。
  • Cloud Web 提供 admin-only Access 页面,让管理员按用户管理 role/status、Code Agent session 可见性和工具 capability;页面只调用 cloud-api /v1/admin/access* 同源 API,不直接访问 OpenFGA、Postgres、Kubernetes 或 Keycloak admin API。
  • Cloud Web 提供 admin-only Provider 管理页面,让管理员通过 cloud-api /v1/admin/provider-profiles* 同源 API 配置 AgentRun provider API Key;页面不得直接访问 AgentRun、Kubernetes Secret、Moon Bridge 或 provider upstream。
  • Cloud Web 与 hwlab-cli client 必须共享同一组非视觉业务 API。浏览器遇到的 Code Agent continuation、trace/result 和 HWPOD node-ops 问题,必须能通过 hwlab-cli client 走同一 19666 Cloud Web path 复现;不能让 CLI 长期绕到 19667 Cloud API 后把 Web 路径缺口误判为业务已通过。
  • Cloud Web 只承担浏览器 UI 和 hwlab-cli client 的同源代理。AgentRun runner 内的 hwpod 不走 Cloud Webrunner 使用映射到发起用户的 HWLAB_API_KEY 直连 Cloud APICloud Web 不保留 HWPOD 运行路由。
  • 浏览器启动后必须从 GET /v1/workbench/workspace hydrate 账号 workspace;同一个账号在多个浏览器标签页、多个浏览器或 CLI profile 中应看到同一个 workspaceId、selected conversation/session/thread、provider profile 和 active trace。浏览器 localStorage 只能作为短期缓存,并必须绑定 actor,不能作为 workspace authority。
  • Code Agent session 管理必须全部手动化。Workbench 可以从账号 workspace 恢复“已显式选中”的 session,但不能在普通发送、页面刷新、trace replay、失败恢复或 provider resume 失败时自动创建、滚动或替换 session。没有已选 session 时,composer 必须展示“新建 session/选择 session”的显式动作;session failed/stale/canceled 后必须保留失败证据,继续前由用户显式新建或选择另一个 session。
  • 显式 session 的 providerProfile 优先于账号 workspace provider profile。Workbench 可以展示 workspace 默认 provider,但对已选 session 发送 turn 时必须使用该 session 的 provider profile;用户想切换 provider 时,应显式创建或选择对应 provider 的 session,不能把旧 workspace provider 静默套到当前 session 上。
  • Cloud Web trace 展示与 hwlab-cli client agent trace --render web 必须共享同一套 trace row 纯转换路径。Web 发生 row 顺序错乱、final response 缺失、assistant 消息被吞、tool call 只显示泛化占位或噪声事件淹没时,先用 CLI 输出同一渲染 row 摘要和 noise event count 复现;CLI 可复现说明是 trace row 转换问题,CLI 不可复现再进入 DOM/CSS/滚动状态调查。默认展示应压制 AgentRun backend echo、token/rate-limit/status/terminal echo 等低价值事件,但原始 trace JSON 仍必须保留用于 --full/下载排障。
  • Cloud Web Code Agent composer 必须无锁:运行中 turn 不得把输入框或发送按钮 disabled。浏览器提交时必须基于已显式选中的 session 工作;没有 session 时返回 session_required 并引导用户新建 session,不能自动开 session。存在 active running trace 时,用户显式 steer 动作走 POST /v1/agent/chat/steer;空闲且 session usable 时,用户显式发送 turn 走 POST /v1/agent/chathwlab-cli client agent composer status 必须能用同一 policy 输出 sessionRequiredsessionUsablesubmitMode=turn|steerroutetargetTraceId
  • Steer 是短连接控制动作,不是等待目标 turn terminal 的长请求。POST /v1/agent/chat/steer 在 cloud-api 成功创建 AgentRun type=steer command 后必须立即以 HTTP 202 返回,并在响应中暴露 accepted=trueshortConnection=trueroute=/v1/agent/chat/steertraceIdsteerTraceIdagentRun.runIdagentRun.targetCommandIdagentRun.steerCommandId。接口不得为了 owner/session/workspace/conversation 持久化写回而延迟 202;这些写回失败只能作为后台可观测问题处理,不能把已 accepted 的 steer 重新标成请求失败。
  • Workbench 对 steer submit 的失败分类必须以目标 trace 的真实状态为准:如果 steer POST 发生 timeout/ECONNRESET/transport failure,但目标 trace/result 仍是 running 或其他非 terminal 状态,前端必须释放本次 submit lock、保留原 agent message 的 running/steerable 状态并继续 trace 轮询,不得把原 turn 改成 failed 或展示“Code Agent 请求失败”。只有目标 trace/result、cloud-api 或 AgentRun 返回 terminal failed/blocker/canceled 时,才把目标消息标为相应终态。
  • Code Agent result completed 只有在同时包含真实 provider/model/trace/conversation 元数据、providerTrace 和可展示的 final assistant response 时,才能被 Web 标记为真实完成;provider=agentrun-v01 只是执行基础设施标识,不得替代上游 provider/model,也不得把 SOURCE、fixture、echo、mock 或 stub 当成 DEV-LIVE 完成。
  • 同一显式 conversation/session 的后续用户消息必须在 AgentRun runner reuse window 有效时复用已存在的 AgentRun run/runner 继续新 command/turn;只有 runner 不可用、已过期或用户显式创建新 session 时才重新 bundle 和启动 runner。每条消息都重新 bundle/runner 属于 v0.2 AgentRun 接入缺口,不能只靠 trace 显示原因当成已完成。
  • runner pod 被删、runner Job 重建或旧 lease 失效后的临时恢复,可以显示新的 AgentRun run/job/command identity,但 Web 必须继续以同一个 HWLAB sessionId / threadId / provider profile 呈现业务会话,并明确区分“同 session 恢复执行壳”和“新业务 session”。只要复用同一 AgentRun SessionRef/PVC/thread 且没有拼接历史 promptreplacement run/job 可以作为 session 持久化恢复证据;它不替代 T2.2 的同 run/runner reuse 目标。
  • AgentRun 会话连续性只有一个标准路径:Cloud Web/CLI 提交的 threadId 必须经 Cloud API adapter 写入 AgentRun command payload.threadIdSessionRef.threadId。前端、CLI、API 和 AgentRun 的协议字段、trace、result 和 conversation facts 都以该字段为唯一 thread identity。
  • Cloud Web 提交 Code Agent turn 时只发送当前用户消息、共享 workspace 的 conversationId/sessionId/threadId、workspace revision 和必要运行元数据;不得发送 conversationContext/messages,也不得把浏览器历史拼入 prompt。历史消息只用于本地 UI 展示和 trace/inspect 可见性,不能替代 AgentRun/Codex stdio 原生 thread/resume
  • Code Agent Timeout ModelHWLAB #795 finalWorkbench 不允许把 Code Agent 长任务总耗时当成失败条件,也不得在浏览器侧引入任何 total-timeout / hard cap / codeAgentTimeoutMs * N 兜底 / 轮询次数上限 / 按 wall-clock 缩小的 per-poll 窗口。Code Agent turn 的唯一 abort 信号是 inactivity-timeout:即 fetchJsontimeoutMs(用户配的 codeAgentTimeoutMs)窗口内没有任何新 activitytrace snapshot / user typing / submit kickoff / server 5xx 重试)。一旦 activityRef.lastActivityAt 在窗口内被刷新,per-poll 窗口必须维持原值不缩小,外层 waitForAgentResult / pollRunnerTracefor(;;) 循环也没有任何累计计时跳出条件。Runaway 保护的责任在调用方:用户可以走 cancel / steer / 关 tab / 关浏览器 / session cancel,而不是浏览器隐式地按 4 × totalTimeoutMs 把活跃 turn 杀掉。AgentRun command/provider/backend 失败后,当前消息可显示 failed/blockersession 是否仍 usable 必须由 session 状态显式表达。thread-resume-failed、provider continuation 失效、运行面中断或用户取消导致 session failed/stale/canceled 时,不得自动清理并滚动到新 session;继续前必须由用户显式创建或选择 session。
  • 同一 AgentRun run 复用多条 command 时,Web trace 展示只显示当前 command 归属事件和必要 run 级状态;旧 command 的 assistant/tool/terminal 尾部不能堆到新 command 末尾。取消轮次的可读进展必须作为脱敏 conversation facts 进入 UI/trace/inspect 证据,而不是靠旧 trace 尾部串线让后续轮次“碰巧看到”;这些 facts 不得作为下一轮模型上下文或 prompt 拼接来源。

Workbench 首屏性能契约

/#/workspace 是 Cloud Web 的用户业务首屏;性能页、构建检查、本地 mock、CLI-only 请求或 sidecar 存活不能替代该入口。首屏性能目标用于用户体感回归判断,不作为新的发布旧门禁:真实浏览器打开 http://74.48.78.17:19666/#/workspace 后,LCP 应稳定低于 3sCLS 应低于 0.1#command-input 必须可见且可输入。

首屏 bootstrap 路径必须保持窄路径:

  • HTML 在 /#/workspace 首屏可以在解析期提前发起一次 GET /auth/workspace-bootstrap?projectId=prj_device_pod_workbench。React auth/workbench 状态必须优先消费 window.HWLAB_CLOUD_WEB_EARLY_WORKSPACE_BOOTSTRAP 的 promise;同一首屏不得再额外发起 /auth/bootstrap/auth/session/v1/workbench/workspace 来重复 hydrate 同一账号 workspace。
  • /auth/workspace-bootstrap 返回的 user/actor/workspace 只能作为首屏 seed。账号 workspace、session、conversation、provider profile 和 revision authority 仍在 hwlab-cloud-api / Postgres;浏览器 localStorage 仍只是 actor-bound cache,不能成为 authority。
  • 首屏允许加载当前工作台可见所需的 conversations、device-pods 和 live-builds 摘要,但 trace replay、events?limit=120、性能 summary、重型右侧面板、旧事件列表和非当前视口内容必须延后到首屏稳定后、用户展开后或具体业务需要时加载。

历史消息渲染必须优先保证首屏稳定,不得牺牲业务语义:

  • 首屏 hydrate 里已经存在的历史 Code Agent 消息先以纯文本 fallback 展示,文本必须按 React 默认规则转义,不能执行 raw HTML。Markdown/GFM renderer 必须通过独立 MarkdownRenderer chunk 懒加载,首屏 LCP 观察窗口内不应加载该 chunk。
  • 首屏历史 final response 必须带稳定标识并用本地滚动边界约束高度,避免长历史回复继续驱动页面级 LCP/CLS。约束只影响首屏已有历史消息的初始展示,不得删除、截断、改写 conversation history,也不得影响新消息、用户显式打开的详情、trace/result 原始证据或首屏后 Markdown 升级。
  • 不得用隐藏历史消息、禁用 composer、删除 Code Agent 结果、减少真实业务能力或把历史 prompt 拼回下一轮消息的方式换取 LCP 数字。历史消息只用于 UI 展示和 trace/inspect 可见性,不能替代 AgentRun/Codex 原生 thread/resume。

测试和门禁只表达当前契约:旧同步 Markdown table 断言、旧 exact-string bundle guard、旧 layout smoke、旧 M3 evidence、旧 DEV/D601 browser gate 和与当前首屏目标无关的历史断言一律拆除,不迁移、不兼容保留。默认 bun run --cwd web/hwlab-cloud-web check 只保留轻量源码层保障:早发 workspace bootstrap 被消费、首屏历史 Markdown 延迟、历史 final response 有稳定高度边界、lazy renderer 仍能在升级后支持 Markdown/GFM 且 raw HTML 不执行。真实 LCP/CLS、请求数量和 RUM/Prometheus 改善必须通过专项 live browser closeout 验证。

内部架构

Code Agent Timeout Model 与 Path Unification(与 #795 / #802 同步固化)

权威规则:Cloud Web 的 state/workbench.ts submitMessagestate/trace-reattach.ts useTraceReattach 必须遵守以下 contract,禁止分裂成 normal / refresh 两套不同代码路径,禁止任何 wall-clock / 轮询次数 / 4× / +60s 兜底来"安全网"式杀掉活跃 turn

  • 单一入口 subscribeToTracestate/runner-trace.ts::subscribeToTrace 是 trace 轮询的唯一入口。waitForAgentResult + pollRunnerTrace 旧 dual 函数对已经删除(HWLAB #802 收口)。submitMessage 在 POST 拿到 202 后调它;useTraceReattachstate.workspace.activeTraceId 变化时调它(路径归一化:refresh 路径也走同一份 active polling,不再是被动显示)。
  • 路径归一化submit path 和 hydrate path 走同一份 subscribeToTrace 逻辑。onActivity 在每一次成功的 result poll 和 trace poll 上都调(不是只在新 snapshot 上调),所以在正常操作下 per-poll inactivity 窗口永远不 fire
  • 失败持久化subscribeToTraceonInfrastructureError4xx / non-5xx 非 200)会被 submitMessageuseTraceReattach 同时调 persistConversation,让 cloud-api 留痕前端超时/失败。这样 F5 之后用户能直接看到前端 fail 上下文,不只是 backend completed。
  • 唯一 abort 信号per-poll fetchJson 的 inactivity-timeout,由 activityRef 驱动。activityRef.lastActivityAttotalTimeoutMs 窗口内被刷新就不 abort。
  • 没有 total-timeout / 轮询次数上限:外层 for(;;) 循环没有任何累计计时跳出条件。per-poll getAgentChatResult(url, totalTimeoutMs, activityRef) 直接传 totalTimeoutMs随墙钟缩小成 totalTimeoutMs - (Date.now() - startedAt)
  • 没有 Math.max(totalTimeoutMs * N, ...) 兜底:之前的 max(4x, +60s) 已经按 PR 反馈彻底删除(PR #798)。
  • activity 来源store.recordActivity()App.tsx 串到 <CommandBar onTyping>+ submitMessage updateActivity() on submit + subscribeToTraceonActivity 在每次成功 poll 上调(保证 inactivity 不被误触)。
  • Runaway 保护责任:用户在 Web 上有 cancel / steer / 关 tab 三个明确逃生口;CLI 上有 --timeout-ms 让调用方自行决定。在浏览器侧加隐式硬上限。
  • Hydrate 路径必须 re-attach running traceuseTraceReattach 在 mount / activeTraceId 变化时通过 subscribeToTrace 主动订阅到 terminal;不在 useEffect cleanup 之前 abortplaceholder 消息用 nextProtocolId("msg") 创建。
  • POST 失败分类cold-start runner / network down → onInfrastructureErrorpersistConversation fail 分支;不要把"POST 超时"和"backend 5xx"写死成同一字符串。

不变量(用于 #802 / 未来回归测试):

  1. bun run --cwd web/hwlab-cloud-web check 全过:bun run scripts/tsc-check.ts 严格 React TSX 0 explicit anybun test 5 pass / 0 fail。
  2. scripts/fetchJson-inactivity.test.tselapsed >= 10_000 断言(10s 持续活动窗口内永远活)。
  3. state/runner-trace.ts 只能导出 subscribeToTracewaitForAgentResult / pollRunnerTrace / TRACE_HARD_CAP_ATTEMPTS 必须不存在。
  4. state/trace-reattach.ts 必须存在并被 workbench.ts 引用;workbench.ts 不再直接 await subscribeToTrace 以外的方式做 trace 轮询。
  5. live http://74.48.78.17:19666/app.js 不含 Math.max(t*4while (...)totalTimeoutMs>= 比较、> 120 之类的硬上限 pattern。
  6. CLI end-to-endhwlab-cli client agent send --wait 跑通 + refresh 后用 client agent trace <traceId> --render web 拿同一份 row。

历史与收敛(蒸馏自 #775 / #777 / #791 / #795 / #797 / #798 / #802 的过程):

  • React 收敛前的旧单页 fetchJson 走 inactivity-timeoutscheduleTimeout 每秒重算 remainingMs = timeoutMs - (now - lastActivityAt))。React 收敛后 #777 把 client.ts 加了 ActivityRef 抽象,但 workbench.ts / runner-trace.ts 没把 ref 串起来,于是 Web UI 退化成 total-timeout#795 出现"bootshar 9.7s 跑完但 Web 37s 处 timeout"的回归。
  • #795 PR #797 修了一版但留了 max(4x, +60s) 兜底 capPR #798 删 hard-cap 改成 for(;;),但前端 fail 状态机没改(fail 消息只入本地 statecloud-api 不知情)+ hydrate path 没有 re-attach 主动订阅。
  • #802 收口:用 subscribeToTrace 一个入口统一 submit + hydrateonActivity 在每次成功 poll 上调让 inactivity 永远不 firefail 消息也 persistConversation

权威规则:Cloud Web 的 state/workbench.ts submitMessagestate/runner-trace.ts waitForAgentResult / pollRunnerTrace 必须遵守以下 contract,禁止用任何 wall-clock / 轮询次数 / 4× / +60s 兜底来"安全网"式杀掉活跃 turn

  • 唯一 abort 信号per-poll fetchJson 的 inactivity-timeout,由 activityRef 驱动。activityRef.lastActivityAttotalTimeoutMs 窗口内被刷新就不 abort。
  • 没有 total-timeoutwaitForAgentResultpollRunnerTrace 的外层循环是 for (;;),没有 Date.now() - startedAt >= <cap> 的跳出。per-poll getAgentChatResult(url, totalTimeoutMs, activityRef) 直接传 totalTimeoutMs随墙钟缩小成 totalTimeoutMs - (Date.now() - startedAt)
  • 没有轮询次数上限TRACE_HARD_CAP_ATTEMPTS 已删除。"轮询 120 次还没结束"不是 abort 条件;如果上游永远不返回 terminal 且永远不说话,浏览器就持续 poll 到用户 cancel / 关 tab。
  • 没有 Math.max(totalTimeoutMs * N, ...) 兜底:之前 round 9 留的 max(4x, +60s) 兜底按 PR 反馈彻底删除,理由是它会重新引入 #795 这类"trace 一直在推但被外层 cap 误杀"的回归。
  • activity 来源store.recordActivity()App.tsx 串到 <CommandBar onTyping>+ submitMessage submit kickoff + pollRunnerTrace 在新 snapshot 时 mutate ref。runner-trace.ts 不直接调 updateActivity,避免与 state/workbench.tssubmitActivityRef 重复。
  • Runaway 保护责任:用户在 Web 上有 cancel / steer / 关 tab 三个明确逃生口;CLI 上有 --timeout-ms 让调用方自行决定。在浏览器侧加隐式硬上限。

不变量(用于 #795 / 未来回归测试):

  1. bun run --cwd web/hwlab-cloud-web check 全过:bun run scripts/tsc-check.ts 严格 React TSX 0 explicit anybun test 5 pass / 0 failscripts/fetchJson-inactivity.test.ts 3 case + dist-contract 2 case)。
  2. scripts/fetchJson-inactivity.test.ts 必须包含 "持续 activity > inactivity 窗口" 用例:例如 200ms cadence 推 activity 共 10sinactivity 窗口 3srequest 必须活到 10s+ 才因 activity stop 而 abort。elapsed >= 10000 是不变量;不变量失效即视为本节契约被破坏。
  3. live http://74.48.78.17:19666/app.js 不能含 Math.max(t*4while (...)totalTimeoutMs>= 比较、> 120 之类的硬上限 pattern;如出现即视为本节契约被破坏。

历史与收敛(蒸馏自 #775 / #777 / #791 / #795 的过程):

  • React 收敛前的旧单页 fetchJson 走的是 inactivity-timeoutscheduleTimeout 每秒重算 remainingMs = timeoutMs - (now - lastActivityAt)),waitForAgentMessageResult 也按 idleMs >= CODE_AGENT_TIMEOUT_MS 走。React 收敛后 #777 把 client.ts 加了 ActivityRef 抽象,但 workbench.ts / runner-trace.ts 没把 ref 串起来,于是 Web UI 退化成 total-timeout#795 出现"bootshar 9.7s 跑完但 Web 37s 处 timeout"的回归。

  • #795 PR #797 修了一版,但留了 max(totalTimeoutMs * 4, totalTimeoutMs + 60s) 兜底 cap。用户反馈"硬上限等于又引入 #795 同一类回归",第二轮把 for(;;) + 删 attempt > TRACE_HARD_CAP_ATTEMPTS + 删 startedAt 累计计时 + per-poll 传 totalTimeoutMs(不缩小),实现"完全无 total / 轮询上限"。

  • Round 10 起 commit / spec / 测试同步固化为本节。

  • web/hwlab-cloud-web/src/App.tsx 是浏览器端主入口,和 src/components/**src/state/**src/services/**src/types/** 共同组成实际 bundle 输入集合,组织 Workbench 状态、Code Agent 会话缓存、trace 轮询和 HWPOD node-ops 面板。

  • internal/dev-entrypoint/http.mjs 提供静态服务、health 和 HTTP proxy 基础能力。

  • internal/dev-entrypoint/cloud-web-routes.mjs 定义可代理到 cloud-api 的同源 API route 和认证边界。

  • web/hwlab-cloud-web/auth.ts 管理工作台登录态、Keycloak redirect/callback 状态和 API key 管理 UI 调用;真正的登录鉴权和用户权限 authority 仍应收敛到 cloud-api。

  • web/hwlab-cloud-web/views/access 或等价模块实现 Admin Access 页面:ActivityRail 顶层入口、用户列表、权限矩阵、pending diff、OpenFGA readiness/mismatch 展示和保存结果。页面必须复用同源 fetch client 和 auth blocker,不新增直连 OpenFGA client。

API 接口说明

接口 说明
GET / Cloud Workbench 首屏。
GET /healthGET /health/live 返回 cloud-web 自身 health 和 build metadata。
GET /help 返回可用 route 摘要。
GET /auth/oidc/loginGET /auth/oidc/callbackGET /auth/sessionPOST /auth/logout 同源代理到 cloud-api 的 Keycloak/Web session 入口;登录鉴权最终规格见 spec-v02-auth.md
GET/POST /v1/api-keys... 同源代理到 cloud-api 的 API key 管理入口;短期测试允许当前用户重复查看默认 key 明文。
GET/PATCH/PUT/DELETE /v1/admin/access... 同源代理到 cloud-api 的 Admin Access API;用于 Access 页面读取 summary/user matrix、授予/撤销 tool capability 和 role/status。
GET /v1GET /v1/... 同源代理到 hwlab-cloud-api;公开的 Code Agent result/trace 轮询按 route policy 处理。
GET/PATCH /v1/workbench/workspace... 同源代理到 cloud-api 的账号 workspace authority,用于 Web/CLI 共享工作区和 revision 冲突保护。
POST/GET/PATCH /v1/agent/sessions... 同源代理到 cloud-api 的显式 Code Agent session 生命周期入口;Web 不在普通 send 中隐式创建 session。
POST /v1/agent/chatPOST /v1/agent/chat/steerPOST /v1/agent/chat/cancel 同源代理到 cloud-api 的 Code Agent 入口;steer 必须走同一个 19666 Web path,由 cloud-api/AgentRun 判断目标 turn 是否可接收,并在 AgentRun steer command 创建后短连接 202 返回。
POST /v1/hwpod-node-ops 受控同源代理到 cloud-api 的 HWPOD node-ops 入口;用于 Web/CLI 同路径只读 smokeCode Agent 正式业务入口仍是 runner 内 hwpod 直达 Cloud API。
POST /v1/web-performance 浏览器 RUM 上报入口;只允许低基数性能事件和数值,Cloud API 聚合后进入 Prometheus,详见 spec-v02-observability-monitoring.md
GET /v1/web-performance/summary 性能监控顶级页读取的同源摘要接口;返回低基数 WebUI 体感性能 JSON,包含样本数、route p95、Web Vitals、long task 和错误/超时问题队列,不返回 Prometheus 原始文本或高基数 trace/session/conversation/thread/user 标识。
POST /v1/m3/ioPOST /json-rpc 同源代理到受控 API;不能绕过 cloud-api 直连硬件服务。

测试规格

Cloud Web 的默认校验入口是 bun run --cwd web/hwlab-cloud-web check。该入口必须在 v0.2 CI 的 hwlab-cloud-web 镜像发布前执行,且保持秒级或低十秒级,不引入浏览器、Playwright、公网或真实 provider 依赖。

Browser/layout/live smoke 属于显式专项诊断,不进入默认 Cloud Web check。所有 repo-owned Playwright smoke、layout smoke 和专项截图脚本必须通过 scripts/src/browser-launcher.mjs 启动 Chromium;不得直接 chromium.launch() 绑定到偶然存在的 Playwright browser cache。launcher 的浏览器来源判定顺序是显式 env(HWLAB_PLAYWRIGHT_CHROMIUM / PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH / CHROMIUM_PATH)、G14 系统 Chromium(优先 /snap/bin/chromium)、最后才是 Playwright cache。诊断入口是 npm run web:browser:doctor,输出必须包含 playwrightPackageSourceplatformmanagedBrowser.cacheStatusinstallCheck.statusbrowser.browserSourcebrowser.executablePathbrowser.fallbackUsedbrowser.candidatePathsfailureCode 和 remediation;浏览器不可用、auth bootstrap 未完成和真实 layout 失败不得再统一表现成 #command-input hidden 超时。

临时 live DOM closeout probe 也必须使用 repo-owned 入口,不得在 /tmp 或 issue 验收脚本里手写 Playwright import/launch。标准入口是 npm run web:dom-probe -- start --url http://74.48.78.17:19666/ --fresh-session,它会从 workspace 解析依赖、复用统一 launcher、把 PID/stdout/stderr/result/screenshot 写入 .state/web-live-dom-probe/,并立即返回 status 短查询命令;npm run web:dom-probe -- status <jobId> 用于轮询结果。前端-only DOM/renderer/scroll 验收默认不提交 Code Agent;只有显式 --message 才经 UI 提交一条真实消息,并按默认策略尝试取消运行中的 turn。npm run web:browser:guard 是最小 grep guard,用于确认 scripts/tools/web/ 下没有新增绕过 launcher 的直接 Chromium launch 调用,唯一允许文件是 scripts/src/browser-launcher.mjs

WebUI 性能监控 issue 的 live closeout 不能只检查页面可见或 sidecar 存活;必须用 19666 Web 入口触发真实浏览器 RUM 上报,再按 spec-v02-observability-monitoring.md 查询 hwlab_webui_* Prometheus 指标。LCP、Navigation Timing、业务 API timing、Long Task、CLS/INP/FID 近似只表达用户感知性能趋势,不替代 trace/result/inspect 的高基数排障证据。

Workbench 首屏性能 issue 的 closeout 必须用 repo-owned browser launcher / live DOM probe 或其专项扩展访问 http://74.48.78.17:19666/#/workspace,并记录 LCP、CLS、command input 可用性和首屏请求清单。若现有探针缺少 LCP/CLS 或 request counting,可先扩展该探针;不要在 issue closeout 里长期手写临时 Playwright launcher 或绑定偶然存在的 Playwright browser cache。

Cloud Web 顶级性能页是观测面,不是业务工作台。访问 /performance / #/performance 时不得初始化 Workbench store 的 workspace hydrate 或 live refresh,也不得触发 HWPOD node-ops、agent conversations、live-builds、system.health 等 workspace-only 请求。性能页只允许主动请求 GET /v1/web-performance/summary 和静态资源;如果需要调查 workspace 或 HWPOD 性能,必须从 workspace 原入口触发样本,而不是让性能页自己制造业务流量。

Live smoke 登录前必须等待前端 auth bootstrap 结束(body[data-auth-state] 不再是 checking,且 login submit 已可见/可用)再填表;登录后必须断言 URL query 不含 usernamepassword,防止原生 form submit 泄漏凭据并伪装成 layout 超时。

Workbench build summary 的顶部 chip 可以展示 /v1/live-builds.latest,但 latest 是跨所有 HWLAB runtime service 的最新构建,不是 Cloud Web 专属字段。Cloud Web 布局或 build-time issue 的验收必须打开详情或读取 services[]serviceId=hwlab-cloud-web 的行,分别核对 build time、env image、actual commit、revision 和 source metadata;不得因为 latest.serviceId 指向 hwlab-cloud-api 就判定 Web 构建时间缺失。

Cloud Web check 必须先对实际 bundle 输入集合运行 TypeScript 语义检查。语法检查和 Bun build 只能证明源码可解析或可打包,不能稳定发现未绑定标识符;isRequestTraceEvent is not defined 这类错误必须由 semantic check 在发布前拦截。实现上可以生成与 dist build 相同顺序的临时 app entry,再执行 tsc --noEmit 或等价 TS checker;只跑 node --checkbun build 或源码字符串断言不满足本规格。

Cloud Web 单元测试必须自动发现并执行 repo-owned web/hwlab-cloud-web/**/*.test.ts,不允许只维护硬编码文件清单。app-trace 的 trace row/render helper 必须有纯逻辑单测,直接构造 request、setup、commandExecution、assistant markdown 和 completion events,证明 trace 展示路径不会因为漏定义 helper、事件分类漂移或 markdown body 渲染变更而在浏览器运行期崩溃。

Cloud Web check 通过后仍需执行 bundle build 和 dist freshness 校验,确保实际发布的 dist/app.js 来自同一组 TypeScript 输入。默认 check 不恢复旧 quick prompt、旧 layout smoke、旧 M3 evidence、旧 DEV/D601 browser gate 或 Playwright;这些只能作为显式专项诊断,不得进入默认 CI/CD 关键路径。

T1

阅读 docs/reference/spec-v02-hwlab-cloud-web.md,然后用 cli 手动测试以下内容:访问 http://74.48.78.17:19666//health/live,确认页面和 health 均来自 v02 cloud-web,而不是 DEV/PROD 端口。

T1.1

阅读 docs/reference/spec-v02-hwlab-cloud-web.md 和 docs/reference/spec-v02-observability-monitoring.md,然后用真实浏览器或 repo-owned live DOM probe 打开 http://74.48.78.17:19666/#/workspace。验收必须记录:LCP <3s、CLS <0.1#command-input 可见且未 disabled、首屏只有一次 /auth/workspace-bootstrap?projectId=prj_device_pod_workbench,并且首屏观察窗口内 /auth/bootstrap/auth/session/v1/workbench/workspace、trace replay、events?limit=120MarkdownRenderer chunk 均未出现。随后用同源 /v1/web-performance/summary 和受控 Prometheus 查询确认 /workspace WebUI RUM 样本存在;summary 仍处于 warming 时必须如实说明,不能用性能页自身样本替代 workspace 样本。

T2

阅读 docs/reference/spec-v02-hwlab-cloud-web.md,然后用 cli 手动测试以下内容:先通过同源 19666 显式创建或选择 Code Agent session,再提交 Code Agent 短连接请求并轮询 result,确认请求经 cloud-web proxy 到 hwlab-cloud-api,且 trace 可回放;未创建或选择 session 的普通 send 必须返回 session_required,不能自动创建 session。

阅读 docs/reference/spec-v02-hwlab-cloud-web.md,然后用 cli 手动测试以下内容:对运行中 Code Agent trace 调用 hwlab-cli client agent steer <traceId> --message ...,确认请求走 Cloud Web 同源 POST /v1/agent/chat/steer;成功路径必须在短请求内返回 HTTP 202、accepted=trueshortConnection=trueagentRun.steerCommandId,再通过原 trace 的 result/trace --render web 看到 agentrun:steer:acceptedagentrun:steer:command-created。Web 层不能返回 serviceId=hwlab-cloud-web 的 404;目标不存在、非运行中或 runner 拒绝时必须透传 cloud-api/AgentRun 的结构化业务状态,不得把仍 running 的原 turn 误标 failed。

阅读 docs/reference/spec-v02-hwlab-cloud-web.md,然后用 cli 手动测试以下内容:未选择 session 时运行 hwlab-cli client agent composer status 必须显示 sessionRequired=true;显式创建 session 并启动一个真实运行中的 Code Agent turn 后,再运行 hwlab-cli client agent composer status,确认输出当前 sessionIdcomposer.submitMode=steercomposer.route=/v1/agent/chat/steer 和当前 targetTraceId;随后运行 hwlab-cli client agent composer submit --message ...,确认 CLI 按 Web composer policy 走 steer,但不自动创建或滚动 session。

T2.1

阅读 docs/reference/spec-v02-hwlab-cloud-web.md,然后用 cli 手动测试以下内容:对浏览器暴露的 Code Agent trace 运行 hwlab-cli client agent trace <traceId> --render web --limit 80,确认 CLI 与 Web 使用同一 trace row 转换;若 final response 缺失、assistant row 顺序错乱或噪声事件过多,先用 CLI 固定复现再修实现。

T2.2

阅读 docs/reference/spec-v02-hwlab-cloud-web.md,然后用 cli 手动测试以下内容:先显式创建 session,再在同一 conversation/session 连续发送两条 Code Agent 消息,确认第二条复用第一条的 AgentRun runId 和 runner jobName、生成新的 commandId,且不重新 materialize bundle/启动新 runnerresult completed 必须包含真实 provider/model/providerTrace/trace/conversation 和 final assistant response。复用失败原因只能作为诊断,不作为本测试通过条件;如果 runner pod 已被删除或旧 lease 已失效,replacement run/job 只能作为同 session/PVC/thread 的恢复证据记录,不能算作本测试的 run/runner reuse 通过。如果 session failed/stale,必须显式创建新 session 再继续。

T2.3

阅读 docs/reference/spec-v02-hwlab-cloud-web.md,然后用 cli 手动测试以下内容:对一轮已取消但取消前存在 assistant/tool 进展的 trace 发送同一 conversation/session/thread 后续问题,确认请求和 result 只携带标准 threadId,不出现历史 thread 别名字段;--render web 输出不得把上一 command 的尾部 assistant/tool/terminal row 堆到新 command 末尾。

T3

阅读 docs/reference/spec-v02-hwlab-cloud-web.md,然后用 cli 手动测试以下内容:打开 Workbench HWPOD node-ops 面板,确认 status/freshness/blocker 显示来自 /v1/hwpod-node-ops,未登录或未授权时必须显示认证/授权 blocker,不得把 fixture 或 blocked fallback 写成真实硬件 DEV-LIVE。

T3.1

阅读 docs/reference/spec-v02-hwlab-cloud-web.md,然后用 cli 手动测试以下内容:通过 19666 Cloud Web 同源 path 对 /v1/hwpod-node-ops 提交只读 node.health plan,确认与 19667 Cloud API 的 route policy 对齐;如果 Cloud API 返回业务级 4xxCloud Web 也应透传业务错误,不应在 Web 层直接 404。

T4

阅读 docs/reference/spec-v02-hwlab-cloud-web.md,然后用 cli 手动测试以下内容:运行 bun run --cwd web/hwlab-cloud-web check,确认输出或日志显示已执行 Cloud Web TypeScript 语义检查、自动发现的单元测试、bundle build 和 dist freshness 校验;不得用只跑 bun build 或浏览器手工刷新替代该检查。

T5

阅读 docs/reference/spec-v02-hwlab-cloud-web.md,然后用 cli 手动测试以下内容:确认 trace 渲染相关单测覆盖 request、setup、commandExecution、assistant markdown 和 completion row;该测试必须能在无浏览器、无 Playwright、无公网、无真实 provider 的环境中执行。

T6

阅读 docs/reference/spec-v02-hwlab-cloud-web.md 和 docs/reference/spec-v02-openfga-authorization.md,然后用 cli 手动测试以下内容:登录 admin 后打开 Cloud Web Access 页面,确认 ActivityRail 显示 Access 入口,页面加载 /v1/admin/access/summary 和用户权限矩阵;普通用户访问同一路由必须显示 authorization blocker。通过页面授予/撤销一次 tool:hwpod capability 后,hwlab-cli client access users inspect <user> 必须看到同一 effective matrix。

Session state 持久化与 eviction reset

HWLAB v0.2 Code Agent chat turn 默认走 sessionStorage=persistent:每次 turn 由 code-agent-agentrun-adapter.ts 在创建 run 之前显式调 POST /api/v1/sessions,让 AgentRun v0.1 同步创建 RWO PVCagentrun-v01-session-<sessionId>1GiStorageClass 走 env AGENTRUN_SESSION_STORAGE_CLASS 默认 local-path)。runner Job manifest 渲染时多挂一个 agentrun-sessions volume + AGENTRUN_SESSION_PVC_NAME / _NAMESPACE / _MOUNT_PATH / AGENTRUN_CODEX_ROLLOUT_SUBDIR env,使 codex app-server 自己把 rollout JSONL 写进 PVC,跨 runner pod 重建天然 thread/resume:completed

runner pod 或 runner Job 丢失但 PVC 仍存在时,下一轮必须继续使用同一个 HWLAB sessionId、标准 threadId、session provider profile 和 AgentRun SessionRef/PVC 执行 resume。当前临时恢复允许 Cloud API 启动 replacement run/job 作为执行壳;验收必须记录 replacement run/job 与原业务 session 的对应关系,并证明没有通过历史 prompt、messages 或 fake resume 续接。长期目标仍是 runner reuse window 内同一 AgentRun run/runner 多 command。

Eviction reset UX

当 AgentRun v0.1 上报 failureKind=session-store-evicted(PVC 被删或 TTL 到期)时,HWLAB cloud-api 走 reset UX

  • conversationId,发新 sessionIdnewSessionIdAfterEviction(baseSessionId, traceId) = <base>-reset-<trace8>),threadId=null 强制走 thread/start
  • 旧 session 的 storageKind 已被 AgentRun 标为 evictedHWLAB adapter 不再 reuse 旧 mapping。
  • Workbench 把 errorCode=session_storage_evicted 归到 session-blocked categoryUI 文案与 session_failed 区分:「Code Agent session 存储已失效(PVC 被回收 / TTL 到期),HWLAB 已为你开新 sessionId,可继续发送下一条消息。」

禁止路径:

  • 不允许 fake thread/resume:completedPR #78 已锁定的 v0.1 contract)。
  • 不允许 idleTimeoutMs 拉成永驻当成本特性。
  • 不允许 runner Job 启动后再做 copy/restore(本方案撤掉的路径,禁止复活)。

实现情况

规格项 状态 说明
调 POST /api/v1/sessions 同步建 session + PVC 已实现 code-agent-agentrun-adapter.ts::ensureAgentRunSessionPersistent 默认 sessionStorage=persistentenv HWLAB_CODE_AGENT_AGENTRUN_SESSION_STORAGE=ephemeral 可退回 metadata-only 模式。
runner Job 直接挂载 PVC 已实现/已通过 HWLAB v0.2 原入口复测 runner Job manifest 由 AgentRun v0.1 渲染 per-session PVC 直接挂载;HWLAB 侧以同 session/thread/PVC resume 作为恢复证据,不走 copy/restore。
eviction reset UX 已实现 submitAgentRunChatTurn 走两步尝试,第一步在 ensureSession 或 runner-jobs POST 收到 session-store-evicted 时用新 sessionId + threadId=null 重试。
UX 错误码 session_storage_evicted 目标状态/需专项复测 session-store-evicted 必须归入 session-blocked,并向用户区分“PVC 被回收,需要新 session”与普通 provider/backend 失败;不能用 fake resume 或自动无感滚动掩盖。

规格的实现情况

规格项 状态 说明
Workbench 首屏 已实现 当前页面直接进入工作台,不是 landing page。
Workbench 首屏性能契约 已实现 首屏通过 early workspace bootstrap、seed 复用、历史 Markdown 延迟升级和历史 final response 稳定高度边界降低 LCP/CLS;关闭性能 issue 前必须用 19666/#/workspace 原入口复测。
cloud-api 同源代理 已实现 受 route policy 控制;HWPOD node-ops POST 必须与 Cloud API route policy 对齐。
Code Agent UI/trace/result 已实现 支持 provider profile、timeout、trace 轮询和取消。
Code Agent 无锁 composer 已实现 Web/CLI 共享 composer policy;运行中输入框保持可编辑并自动走 steer。
账号 workspace hydrate/sync 已实现 启动读取 account_workspacesCode Agent 请求携带 workspace revision,终态再同步 workspace。
HWPOD node-ops 面板 已实现 当前消费 /v1/hwpod-node-ops 的只读健康 plan,展示 node/action/result/blocker 摘要。
Admin Access 授权页面 目标状态 新增 admin-only ActivityRail 页面,管理 OpenFGA relation/tool capability/role status,并与 CLI client access 共用同一路径。
Provider API Key 管理页面 已实现 #/management admin-only 管理页面走 /v1/admin/provider-profiles* 同源 API 并委托 AgentRun;CLI 同路径入口用于非视觉验收。
完整多用户 admin/user UI 未完全实现 登录态存在;Keycloak/Web session/API key 需按 spec-v02-auth 收敛,权限 authority 仍需按 spec-user-access 收敛到 cloud-api。