18 KiB
v0.3 Workbench Vue 迁移语义合同
本文定义 Cloud Workbench 从 React useWorkbenchStore 迁移到 Vue/Pinia 时必须保留的 API、store 和 trace 语义。Vue 实现可以复用 Sub2API frontend 的 api/store/composable 分层形态,但不能照搬 Sub2API 的 bearer token 鉴权和通用刷新模型。
基线来源
| 来源 | 基线文件 | 可迁移结论 |
|---|---|---|
| HWLAB React Workbench | web/hwlab-cloud-web/src/state/workbench.ts、runner-trace.ts、trace-reattach.ts、services/api/client.ts、types/domain.ts |
当前语义权威。Vue 迁移必须保持显式 session、inactivity-timeout、trace reattach、result/trace 同源 API 和 HWPOD evidence label。 |
| HWLAB Web 规格 | docs/reference/spec-v02-hwlab-cloud-web.md |
Code Agent 没有浏览器侧 total-timeout / attempt cap;唯一 abort 信号是 activityRef 驱动的 inactivity-timeout。 |
Sub2API frontend v0.1.136 |
src/api/client.ts、src/stores/auth.ts、src/stores/app.ts、src/composables/useAutoRefresh.ts、src/composables/useClipboard.ts |
可复用 Composition API Pinia store、独立 api module、toast/clipboard/auto-refresh composable 的组织方式;不可复用 localStorage bearer token 鉴权。 |
React Workbench 语义提取
| React 模块 | 当前语义 | Vue 迁移归属 |
|---|---|---|
services/api/client.ts::fetchJson / fetchText |
同源 fetch、credentials=same-origin、统一 ApiResult<T>、recordApiTiming;activityRef 每秒重算 idle window,超时错误必须说明 waitingFor 和 lastEventLabel。 |
src/api/client.ts 保持 fetch client,不换成 Sub2API axios bearer client。 |
services/api/client.ts::api |
同源代理覆盖 auth bootstrap、live probe、workspace、conversation、Code Agent send/steer/result/cancel、HWPOD node-ops、skills 等端点。 | 拆成 src/api/workbench.ts、src/api/auth.ts;只拆模块,不改 endpoint path 和 timeout contract。 |
state/workbench.ts::initialState |
providerProfile、codeAgentTimeoutMs、gatewayShellTimeoutMs 从 localStorage 读取并 clamp;默认 Code Agent inactivity budget 为 1_800_000ms。 |
stores/workbench state 初始化;持久化 key 保持 hwlab.workbench.*.v1。 |
state/workbench.ts::hydrate |
并发读取 /v1/workbench/workspace 和 /v1/agent/conversations,用 workspace selected conversation 生成 message 列表并记住 projectId。 |
stores/workbench.hydrate();首屏可由 useWorkspaceBootstrap 提供 workspace 快照,后续再补 conversation list。 |
state/workbench.ts::refreshLive |
每 30s 读取 health/live、health、REST index、adapter、HWPOD specs、HWPOD node-ops、live-builds,并推导 Code Agent availability。 | stores/workbench.refreshLive() + useAutoRefresh;只能在 workspace 入口启用,性能页不得初始化。 |
state/workbench.ts::createSession / selectConversation / deleteCurrentSession |
session 由用户显式创建、选择、删除;普通 send 不隐式创建 session;select payload 带 updatedByClient。 |
stores/workbench actions;Vue UI 必须先暴露 session 创建/选择入口,再允许普通 turn。 |
state/workbench-reducer.ts::composerFromState |
通过当前 request、running agent message、workspace active trace 推导 composer;running turn 时 submit mode 为 steer 且 route 为 /v1/agent/chat/steer。 |
stores/workbench getter composer;不要在组件中重复推导。 |
state/workbench.ts::submitMessage |
生成 user/pending message,先 persistConversation,再 POST send/steer;submit kickoff 调 recordActivity(),POST 使用同一个 activityRef;terminal 后 merge trace/result 并再次持久化。 |
stores/workbench.submitMessage() 调 useTraceSubscription;message mutation 和 persistence 保持 store 内聚。 |
state/runner-trace.ts::subscribeToTrace |
result 和 trace 两路同源 poll;每次成功 poll 调 onActivity;5xx 不刷新 activity 且继续 poll;非 5xx 失败走 infrastructure error;外层 for(;;) 没有 total timeout。 |
composables/useTraceSubscription.ts 的唯一 active polling 入口。 |
state/runner-trace.ts::mergeTraceResults |
terminal result 与 trace snapshot 合并;优先使用 finalResponse、terminal assistant event、result text,保留 agentRun、events、traceSummary、terminalEvidence。 |
api/workbench 或 composables/useTraceSubscription 附近的纯函数;必须有单元测试。 |
state/trace-reattach.ts |
hydrate 后发现 activeTraceId 且当前没有 pending,就创建 placeholder 并主动订阅到 terminal;完成/失败都写回 conversation。 |
composables/useTraceReattach.ts watch store.activeTraceId 与 store.chatPending。 |
components/hwpod/HwpodNodeOpsSidebar.tsx |
evidence label 为 Contract、Specs、Spec、Node、Ops、per-op label 和 probe;status/freshness/blocker 来源必须是同源 /v1/hwpod-node-ops 与 /v1/hwpod/specs?probe=1。 |
Vue HWPOD panel 保留同名 label、data-hwpod-detail 语义和 blocker/status 规则。 |
Sub2API 分层对照
Sub2API v0.1.136 对照必须来自真实 frontend 源码,不允许凭记忆复写。当前迁移基线读取过以下文件:
| Sub2API 文件 | HWLAB 吸收结论 |
|---|---|
frontend/package.json、frontend/vite.config.ts |
Vue 3、Pinia、Vue Router、Vue I18n、Tailwind/PostCSS 作为单一前端栈;HWLAB 删除 React 入口和 React 依赖,不做长期双栈。 |
frontend/src/main.ts、frontend/src/App.vue |
main.ts 只负责安装 Pinia/router/i18n 并挂载 app;App.vue 保持全局 shell、route outlet、toast/弹窗类横切组件,不承载业务状态机。 |
frontend/src/router/index.ts、frontend/src/router/title.ts |
路由按 public/user/admin 分段懒加载,metadata 表达 auth/admin/title;HWLAB 保留默认首屏 workbench,并保持 Cloud Web 同源入口。 |
frontend/src/components/layout/AppLayout.vue、AppSidebar.vue、AppHeader.vue |
平台壳拆成 layout 组件和导航数据;HWLAB 采用同类分层,但导航语义改为 Workbench、Code Agent、HWPOD、Access、Provider。 |
frontend/src/components/common/NavigationProgress.vue、StatusBadge.vue、Skeleton.vue、EmptyState.vue |
common 组件只承载视觉/反馈原语,可复用为 HWLAB 的状态标记、加载态、空态、导航进度。 |
frontend/src/views/user/DashboardView.vue、frontend/src/views/admin/DashboardView.vue |
user/admin view 只组合 layout、common 和业务 store,不把 API fetch 散落到 app shell;HWLAB 的 workbench/dashboard/admin 页面遵循同样边界。 |
frontend/src/i18n/index.ts、frontend/src/style.css、frontend/tailwind.config.js |
i18n 初始化、Tailwind base/components/utilities、颜色和字体 token 集中管理;HWLAB 保留自己的文案和 Web session 约束,不照搬 Sub2API token/Secret/刷新策略。 |
| Sub2API 形态 | 可吸收部分 | HWLAB 必须改写部分 |
|---|---|---|
api/client.ts axios instance、request/response interceptor、统一 unwrap { code, data } |
api client 是独立模块,业务 endpoint 放在 src/api/*.ts;请求取消保持原错误,便于调用方忽略。 |
HWLAB 不使用 localStorage auth_token、refresh_token 或 401 token refresh queue;保留 same-origin cookie session、ApiResult<T> 和 activityRef inactivity-timeout。 |
stores/auth.ts Composition API Pinia:ref state、computed getter、actions、定时器 cleanup |
auth store 的组织方式、启动时 checkAuth/bootstrap、loading/error state 可复用。 |
HWLAB stores/auth 只消费 /auth/bootstrap、/auth/session、/auth/login、/auth/logout;不得持久化 bearer token 或引入 Sub2API refresh token。 |
stores/app.ts toast、global loading、cached settings |
toast/loading 与 useClipboard 依赖 app store 的方式可复用。 |
Workbench live refresh、workspace hydrate 不放进 app store;app store 只保留 UI 横切状态。 |
composables/useAutoRefresh.ts localStorage 保存 enabled/interval/countdown/fetching,组件卸载清 timer |
可作为 Workbench live refresh 和 HWPOD panel refresh 控件基础。 | 不得用 auto-refresh 替代 Code Agent trace subscription;trace 仍由 useTraceSubscription 无限 poll 并使用 activityRef。 |
composables/useClipboard.ts Clipboard API + textarea fallback + toast |
可直接迁移为工作台复制 trace/result/session id 的通用 composable。 | 成功/失败文案走 HWLAB app store/i18n;不要把复制行为和业务状态 mutation 耦合。 |
React 到 Vue 功能补齐分层
React 到 Vue 迁移不能只以 shell/auth smoke 作为完成信号。每次大版本前端迁移都必须先建立 React 基线功能表、目标 Vue 模块表和运行面验证表,再按功能层关闭退化;issue #775 的裸 JS 到 React 迁移退化和 issue #1149 的 React 到 Vue 迁移退化都按这个口径复盘。
| 补齐层 | React 基线能力 | Vue 目标模块 | 验收口径 |
|---|---|---|---|
| R1 Workbench 会话与 trace | Markdown 消息、assistant/user action、trace summary、status summary、copy/retry/cancel/replay。 | stores/workbench、components/workbench/*、useTraceSubscription。 |
同源 Workbench route 能创建/选择 session、提交 turn、展示 trace/result/status,并保留 message action。 |
| R2 Composer 与 session 生命周期 | drafts、provider profile selector、普通 send 不隐式建 session、stale trace cleanup、session delete/select。 | stores/workbench getter/action、composer 组件、session sidebar。 |
未选 session 的普通 send 返回 session_required;切换/删除 session 不遗留 running trace 或过期 draft。 |
| R3 Live status 与 HWPOD node ops | health/live、adapter、REST index、live builds、HWPOD specs、node-ops status/freshness/blocker。 | api/workbench.liveSurface、HWPOD panel、auto-refresh composable。 |
面板数据来自 /v1/hwpod-node-ops 与 /v1/hwpod/specs?probe=1,不得用 fixture 或 blocked fallback 代替。 |
| R4 Access 与 Provider Profiles | OpenFGA subject/resource/decision/relationship 管理,provider profile CRUD、auth JSON/config TOML 脱敏。 | api/access、api/providerProfiles、admin views、common form/table/dialog。 |
CRUD、校验、错误提示和敏感字段脱敏都走同源 API;不得把 profile secret 泄露到 UI、日志或 fixture。 |
| R5 Gate、Performance、RUM 与 Skills | gate/run status、performance timeline、RUM event/timing、skills install/list/status。 | api/gates、api/performance、api/skills、dashboard/admin views。 |
每个 route 有空态、加载态、失败态和真实 API smoke;performance route 不启动 Workbench live refresh。 |
| R6 common UI 与横切 composables | table/form/dialog/toast/clipboard/route title/chunk recovery/navigation progress。 | components/common/*、stores/app、router meta、useClipboard、useTableLoader、useForm。 |
所有业务页复用 common 原语;chunk load 失败可恢复,route title 不回退为 Sub2API 文案。 |
Sub2API 是分层和组件治理参考,不是 HWLAB 的配置来源、运行面来源或鉴权模型。吸收 Sub2API 类平台代码时必须在对照表中明确“可吸收形态”和“必须改写语义”:common table/form/dialog/pagination、router meta/title/chunk recovery、Pinia/api/composable 分层、敏感字段脱敏可以吸收;bearer token、refresh token、上游账号配置、Caddy 暴露策略和 API runtime truth 不得迁入 HWLAB。
运行面来源和覆盖判定
迁移后出现“源码曾经成功、运行面又退回”的现象时,先按 source propagation 链路判定,不要直接归因为其他服务覆盖。检查顺序固定为:目标 lane 固定 repo、GitHub source branch、Git mirror sourceHead、PipelineRun sourceCommit、镜像 digest、Argo revision、pod runtime 文件和 public route smoke;只有这些证据显示同一文件在 Git truth 之外被重写时,才进入外部覆盖调查。
已知容易误判为覆盖的来源包括旧 deploy/deploy.yaml、历史 guard/preflight、D601 git mirror 同步窗口、落后 worktree 触发的旧 PipelineRun,以及运行面热补被正常 GitOps rollout 覆盖。处理这类问题时,先把旧入口从源码和 CI/CD 路径中清除,再通过原入口 smoke 证明当前 runtime 已经来自目标 commit。
Caddyfile 写入属于 YAML-first 平台运维能力,不属于前端迁移代码。多个 YAML 来源共存时必须使用按 site/key 定位的 managed block merge:每个模块只能维护自己的声明块,禁止整文件覆盖、禁止把 Sub2API 的 Caddy block 当作 HWLAB 公共入口模板、禁止从运行面 Caddyfile 反推本地 YAML。HWLAB public exposure 继续以 HWLAB lane YAML/control-plane 为 truth;Sub2API Caddy 规则只能作为“共享 Caddy 模块支持多来源互不影响”的架构参照。
Vue Pinia / Composable 目标映射
| 目标文件 | 责任边界 | 必须暴露的接口 |
|---|---|---|
src/api/client.ts |
fetch 基础设施、ApiResult<T>、ActivityRef、inactivity-timeout、RUM timing。 |
fetchJson<T>(path, options)、fetchText(path, options)、type ActivityRef、type ActivityRefSource。 |
src/api/workbench.ts |
Workbench 业务 endpoint wrapper;只表达 HTTP path 和 timeout,不持有 UI state。 | workspace、updateWorkspace、selectConversation、conversations、saveConversation、deleteConversation、sendAgentMessage、steerAgentMessage、getAgentChatResult、cancelAgentMessage、liveSurface、hwpodSpecs、hwpodNodeOpsHealth。 |
src/api/auth.ts |
Web session bootstrap/login/logout wrapper。 | bootstrap()、session()、login(username,password)、logout();返回同源 session 状态。 |
src/stores/auth.ts |
Web session 状态。 | state: authenticated、user、actor、expiresAt、loading、error; actions: bootstrap、login、logout; getter: isAuthenticated。 |
src/stores/workbench.ts |
Workbench durable state、derived composer、session lifecycle、message mutation、conversation persistence。 | state: workspace、conversations、messages、providerProfile、timeouts、live、codeAgentAvailability、chatPending、currentRequest、lastActivityAt; getters: activeProjectId、activeConversationId、sessionTabs、composer、activeTraceId; actions: hydrate、refreshLive、createSession、selectConversation、deleteCurrentSession、submitMessage、cancelAgentMessage、retryAgentMessage、replayAgentTrace、recordActivity、timeout/profile setters。 |
src/composables/useTraceSubscription.ts |
active trace polling lifecycle;替代 React subscribeToTrace。 |
start(config)、stop()、running; config 包含 traceId、projectId、initial、inactivityTimeoutMs、onActivity、onSnapshot、onComplete、onInfrastructureError。 |
src/composables/useTraceReattach.ts |
watch store active trace and reattach after hydrate/refresh。 | 自动 watch enabled、store.activeTraceId、store.chatPending; 创建 placeholder、订阅、完成/失败后 persist。 |
src/composables/useWorkspaceBootstrap.ts |
首屏 bootstrap 合并 auth/workspace,避免重复请求。 | bootstrapWorkspace(projectId) 返回 auth state + workspace snapshot;必须消费 /auth/workspace-bootstrap?projectId=...。 |
src/composables/useAutoRefresh.ts |
UI refresh control for live surface only。 | 复用 Sub2API enabled、intervalSeconds、countdown、fetching、setEnabled、setInterval;shouldPause 默认在 hidden/performance route 时暂停。 |
src/composables/useClipboard.ts |
文本复制和 toast。 | copyToClipboard(text, successMessage?)、copied。 |
Trace / Result 不变量
submitMessage和useTraceReattach必须共享useTraceSubscription,不得拆成 submit-only 和 hydrate-only 两套 polling。getAgentChatResult(resultUrl, inactivityTimeoutMs, activityRef)的 timeout 入参不随 wall-clock 缩小;外层循环没有累计耗时上限、attempt cap 或timeoutMs * N兜底。- 每一次成功 result poll 或 trace poll 都调用
store.recordActivity();5xx retry 不调用,非 5xx 业务失败进入 infrastructure error。 - terminal 完成前至少尝试合并 trace snapshot;final assistant text 优先级保持
finalResponse、terminal assistant event、result text、普通 assistant event。 - steer POST 如果 timeout/ECONNRESET 但目标 trace 仍 running,Vue store 必须释放本次 submit lock、保留原 agent message running/steerable,不得把原 turn 标 failed。
session_failed、thread-resume-failed、provider continuation 失效、运行面中断或用户取消后,不得自动滚动新 session;下一步必须由用户显式创建或选择 session。
迁移顺序
- 先迁
types/domain.ts、trace merge 纯函数和api/client.ts,用无浏览器单测锁住activityRefinactivity 行为。 - 再迁
api/workbench.ts与stores/auth.ts,确认同源 session contract 未引入 bearer token。 - 迁
useTraceSubscription和useTraceReattach,用 fake API 断言无 total-timeout、成功 poll 刷新 activity、5xx 不刷新 activity、terminal result 合并 trace。 - 迁
stores/workbench.ts,把 React reducer 的 action 语义变成 Pinia action/getter;组件只读 getter 和调 action。 - 最后迁 UI 组件,HWPOD、trace panel、session sidebar 和 composer 只消费 store/composable,不内联业务推导。
验证要求
- 源码层:Vue 迁移 PR 至少覆盖
api/clientinactivity、useTraceSubscriptionpolling、stores/workbench.composer、useTraceReattachplaceholder/terminal persistence、HWPOD label mapping。 - 静态检查:保留
bun run --cwd web/hwlab-cloud-web check或 Vue 等价入口;不得恢复旧 quick prompt、旧 layout smoke 或与当前契约无关的门禁。 - 功能退化检查:迁移关闭前必须按“React 到 Vue 功能补齐分层”逐项跑 route/API smoke;shell/auth 可用只能证明应用能启动,不能证明 Workbench、Access、Provider、HWPOD、Gate、Performance、Skills 或 common UI 已补齐。
- 同路径验收:显式创建或选择 Code Agent session 后,通过 Web 同源 path 提交 turn,
result/trace --render web使用同一 trace row;未选择 session 的普通 send 必须返回session_required。 - HWPOD 验收:Vue 面板必须从
/v1/hwpod-node-ops与/v1/hwpod/specs?probe=1显示 status/freshness/blocker,不得用 fixture 或 blocked fallback 声称真实硬件 DEV-LIVE。