# HWLAB Cloud Workbench Reference（历史路径）

本文不再承载 Cloud Workbench 需求规格正文。

统一规格出处是 UniDesk OA：

- [PJ2026-0104 客户端](https://github.com/pikasTech/unidesk/blob/master/project-management/PJ2026-01/specs/PJ2026-0104-client.md)
- [PJ2026-010401 Web工作台](https://github.com/pikasTech/unidesk/blob/master/project-management/PJ2026-01/specs/PJ2026-010401-web-workbench.md)
- [PJ2026-010402 HWLAB CLI](https://github.com/pikasTech/unidesk/blob/master/project-management/PJ2026-01/specs/PJ2026-010402-hwlab-cli.md)
- [PJ2026-010403 API契约](https://github.com/pikasTech/unidesk/blob/master/project-management/PJ2026-01/specs/PJ2026-010403-api-contract.md)

历史 layout smoke、浏览器排障和实现细节只作为运行参考，不定义需求边界。需要修改 Web 工作台、同源 CLI、trace/result 或公开入口口径时，只更新 UniDesk OA。

## Workbench 运行参考

Workbench 页面和组件必须明确区分“未加载完成”和“已加载但为空”。未加载完成时不得渲染由 workspace、localStorage、默认对象或 stub 记录拼出来的伪数据；这类数据只能作为内部恢复线索，不能在列表、表格、卡片或状态栏中冒充真实加载结果。已经确认后端返回且集合为空时，才显示空态文案。

Workbench 状态对象必须服从 UniDesk OA Web SPEC 中已经定下来的单一权威 API 绑定。Session rail 的会话集合只允许消费 `/v1/agent/conversations` 成功返回的 conversation 集合；当前 selected conversation 如果需要出现在左侧列表中，必须通过显式 query/path/body 传入稳定 conversation id，由该列表 API 在同一响应中返回，后端不得隐藏读取 workspace selected、localStorage、Web snapshot 或上一轮页面状态。前端不得用 workspace selected snapshot、stub、localStorage 或 route 状态补出 session tab，也不得把前端拼接出的 status、final response、markdown、running 动效或 stub 传回后端变成事实。Session 运行状态必须按 `sessionId` 绑定到单一 session 状态 API；Code Agent turn、trace 阅读和 final response 必须按 `traceId` 绑定到单一 trace/result snapshot API。任何权威 API 失败时只能保留上一份成功结果或显示未加载/错误态，不得切换到另一条 fallback 路径形成一条会话、旧 running 态或劣化 markdown。

Cloud Web 的通用加载态使用 `web/hwlab-cloud-web/src/components/common/LoadingState.vue`。新增或修复页面加载态时优先复用该组件，并通过明确的 ready/loading 状态控制展示；不要在每个组件里重新实现一套 spinner、点状动画或默认占位数据。紧凑区域可以使用组件的 compact 形态，文案默认保持“加载中”。

Session rail 是该规则的高频区域。`/v1/agent/conversations` 还未返回时，即使 workspace 中已有 `selectedConversationId`、sessionId、traceId 或 selected conversation snapshot，也不能把选中 session stub 渲染成单条 `.session-tab`，更不能让它占满整个 session 列表高度。加载窗口应只显示 `LoadingState`，并隐藏当前 trace 元信息、复制/删除等依赖真实 active tab 的动作；待 conversations ready 后再渲染真实 session tabs，或在真实空集合时显示空态。

Workbench 只能维护一条会话恢复与提交路径。首次打开、新建后继续、从左侧 session rail 切换、直接进入 `/workbench/sessions/<conversationId>` 恢复时，都必须以当前 route/active conversation id 作为会话真相，并通过同一条 conversation detail hydration 路径得到 messages、turn state、trace/status 和 markdown 渲染输入；不得另写只消费列表 snapshot、workspace stub 或 localStorage selected id 的恢复分支。

Workbench 的 URL 反射必须服从用户当前导航和组件生命周期。`activeConversationId`、hydrate、select conversation 或列表刷新等异步状态只能在当前 route 仍属于 Workbench section、路径仍是 `/workbench`/`/workspace` 系列且 Workbench 组件仍 active 时，才允许把 URL 反射到 `/workbench/sessions/<conversationId>`；用户已经点击 Dashboard、API Keys、Admin、Settings 或其他非 Workbench 导航后，晚到的 Workbench 响应只能更新 store，不得再调用 `router.replace`/`router.push` 把全局 route 拉回 Workbench。新增 session 恢复或 URL 反射入口时必须复用共享路由守卫，例如 `web/hwlab-cloud-web/src/router/workbench-navigation.ts`，不要在业务组件里各自手写跳转判断。

Workspace 中的 `selectedConversationId`、`selectedAgentSessionId` 和 selected snapshot 只能作为恢复线索。只有它们的 conversation id 与当前 active conversation id 完全一致，且已经拿到真实 detail 或真实 messages snapshot 时，才允许驱动 composer、message list、final response markdown 和 trace 入口。conversation id 不一致、detail 仍在加载、或 snapshot 只有 trace/session stub 时，不得覆盖当前消息、pending user message、pending agent message、final response markdown 或右上角运行详情入口。

提交消息必须形成新的 selection epoch。`submitMessage()` 开始后，前端应先把 workspace selection 对齐到当前 route/active conversation，再插入本轮 pending user message 和 pending agent message；任何更早的 hydrate/select/list 响应如果属于旧 epoch，不能再覆盖本轮消息、composer 所属会话或 final response 渲染结果。向 `/v1/agent/chat` 发起请求时，payload 中的 `conversationId` 必须等于当前 route/active conversation id；如果本地 workspace selected session 仍指向旧会话，应先对齐本地状态，而不是把提交落到旧会话。

Session rail 的运行中状态以目标 conversation 的真实 in-flight turn 为准。某个会话正在执行 Code Agent 请求时，对应 `.session-tab` 保持原有单行标题和最近用户消息时间，但必须暴露 `data-running="true"` 并显示执行中动效；turn 进入 completed、failed 或 canceled 后清除动效并回到 `data-running="false"`。切换会话、恢复会话或取消请求不能改变 session tab 的标题来源：左侧只展示用户第一句话和以最后一条用户消息发送时间计算的更新时间。

关闭 Workbench 加载态问题时，浏览器验收应从当前 node/lane 的 public origin 进入，并在同源会话中对目标列表 API 施加短暂延迟，观察 in-flight DOM 和恢复 DOM。延迟窗口应能看到 `#session-tabs[data-loading="true"]`、`.session-tab` 数量为 0、`.loading-state` 和 `.loading-spinner` 存在、`#session-status` 为“加载中”；延迟结束后应恢复为真实数据或真实空态。延迟时间应低于前端请求超时，避免把接口 timeout 后的降级状态误判为恢复态。
