Files
pikasTech-HWLAB/docs/reference/spec-v03-workbench-vue-migration.md
T
2026-06-12 02:28:36 +08:00

90 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 不隐式创建 sessionselect payload 带 `updatedByClient`。 | `stores/workbench` actionsVue UI 必须先暴露 session 创建/选择入口,再允许普通 turn。 |
| `state/workbench-reducer.ts::composerFromState` | 通过当前 request、running agent message、workspace active trace 推导 composerrunning turn 时 submit mode 为 `steer` 且 route 为 `/v1/agent/chat/steer`。 | `stores/workbench` getter `composer`;不要在组件中重复推导。 |
| `state/workbench.ts::submitMessage` | 生成 user/pending message,先 `persistConversation`,再 POST send/steersubmit 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/titleHWLAB 保留默认首屏 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 shellHWLAB 的 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 storeapp store 只保留 UI 横切状态。 |
| `composables/useAutoRefresh.ts` localStorage 保存 enabled/interval/countdown/fetching,组件卸载清 timer | 可作为 Workbench live refresh 和 HWPOD panel refresh 控件基础。 | 不得用 auto-refresh 替代 Code Agent trace subscriptiontrace 仍由 `useTraceSubscription` 无限 poll 并使用 `activityRef`。 |
| `composables/useClipboard.ts` Clipboard API + textarea fallback + toast | 可直接迁移为工作台复制 trace/result/session id 的通用 composable。 | 成功/失败文案走 HWLAB app store/i18n;不要把复制行为和业务状态 mutation 耦合。 |
## 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 snapshotfinal assistant text 优先级保持 `finalResponse`、terminal assistant event、result text、普通 assistant event。
- steer POST 如果 timeout/ECONNRESET 但目标 trace 仍 runningVue store 必须释放本次 submit lock、保留原 agent message running/steerable,不得把原 turn 标 failed。
- `session_failed``thread-resume-failed`、provider continuation 失效、运行面中断或用户取消后,不得自动滚动新 session;下一步必须由用户显式创建或选择 session。
## 迁移顺序
1. 先迁 `types/domain.ts`、trace merge 纯函数和 `api/client.ts`,用无浏览器单测锁住 `activityRef` inactivity 行为。
2. 再迁 `api/workbench.ts``stores/auth.ts`,确认同源 session contract 未引入 bearer token。
3.`useTraceSubscription``useTraceReattach`,用 fake API 断言无 total-timeout、成功 poll 刷新 activity、5xx 不刷新 activity、terminal result 合并 trace。
4.`stores/workbench.ts`,把 React reducer 的 action 语义变成 Pinia action/getter;组件只读 getter 和调 action。
5. 最后迁 UI 组件,HWPOD、trace panel、session sidebar 和 composer 只消费 store/composable,不内联业务推导。
## 验证要求
- 源码层:Vue 迁移 PR 至少覆盖 `api/client` inactivity、`useTraceSubscription` polling、`stores/workbench.composer``useTraceReattach` placeholder/terminal persistence、HWPOD label mapping。
- 静态检查:保留 `bun run --cwd web/hwlab-cloud-web check` 或 Vue 等价入口;不得恢复旧 quick prompt、旧 layout smoke 或与当前契约无关的门禁。
- 同路径验收:显式创建或选择 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。