--- title: 0.2.0 版本规划:可视工作台 --- # 0.2.0 版本规划:把工作台"亮出来" > 0.1.0 打通了「模型 → AnimationSpec IR → MP4」的全链路,但整个工作台只活在对话流里: > 用户看不见 spec 的结构、每次改了什么、渲染进行到哪。0.2.0 的主题是**可视工作台**—— > 在 dsh Web 客户端里挂上工作台面板;同时清掉 0.1.0 留下的、会阻碍 UI 落地的遗留项。 --- ## 1. 遗留问题盘点与处置 ### 1.1 README「已知限制与说明」逐条处置 | # | 已知限制 | 现状核实 | 0.2.0 处置 | | --- | --- | --- | --- | | 1 | `group` 图层类型预留未实现,不支持属性降级为警告 | `codegen.ts` 里 `group: null`,遇 group 图层整层跳过 | **实现**。教学动画常需要「一组元素整体移动/缩放」,codegen 按 children 组合 Motion Canvas 节点即可;validate 同步放开 | | 2 | 旁白 / 字幕轨道预留未实现(TTS 与音画对齐) | IR 字段仍在,无任何消费方 | **继续推迟到 0.3**。字幕脱离旁白没有独立价值(用户可用 text 图层手工做),不为本版本凑功能 | | 3 | `anim/*` 事件落盘需在真机 Web 会话确认(`ignorable` 标记由宿主负责) | 冒烟只验证了「过得了无损 JSON 校验」,未验证真机持久化 | **必做,且是 UI 的前置**。面板状态 = 事件流 fold,落盘不可靠则刷新即丢。列进 M0 前置验证 | | 4 | 渲染弹有头浏览器窗口、约 4 秒编辑器加载、长片分钟级 | **该条已过时**:commit 4b1bc00 后默认 headless 无窗口出片,有头只是调试后门 | **更新 README**;真实剩余限制是「preview 整片低分辨率渲染后挑帧」,优化为只渲到最晚抽帧点(见 1.3 #5) | | 5 | Windows `dsh plugin add` 的 pnpm 转发问题 | 文档已有绕过方案 | 无代码动作,维持文档 | ### 1.2 设计稿承诺、0.1.0 未做 | # | 项 | 证据 | 0.2.0 处置 | | --- | --- | --- | --- | | 1 | **client 工作台面板整个未开始** | 设计草案 §8.2 阶段表「client 面板未开始」 | **本版本主体**,见 §2 | | 2 | **渲染未走 `ctx.jobs` 后台化** | `ops.ts` 的 `opRender` 同步 await,事件里 `jobId: 'sync'`;长渲染阻塞工具调用且无法中途取消 | **必做**。走 `ctx.jobs.start()`,模型侧天然获得 `job_output` / `job_kill`;同步等待 + 进度事件双通道 | | 3 | 渲染进度事件缺失 | 设计稿计划 7 种事件,实际只有 4 种(缺 `render-start` / `render-progress` / `preview-ready`) | 随 #2 一起补:`anim/render-start` / `anim/render-progress` 驱动面板进度条 | | 4 | 编排收口未做(agent preset + 系统提示词段) | 设计草案阶段 5 未开始 | **轻量版**:`anim-studio` preset 固定工具集 + 分镜方法论提示词段 | | 5 | `anim_asset_import` 未实现 | 设计稿 §4 工具表有,代码没有 | **P1 视排期**。图片素材当前只能 http URL / 本地路径透传,先真机确认是否真的卡人再定 | | 6 | 场景 camera(平移/缩放) | 最终 IR 已无此字段(`types.ts` 的 Scene) | 正式放弃,不挂账(需要时按轨道关键帧表达) | ### 1.3 代码内挂账(注释里自己承认的欠账) | # | 项 | 证据 | 0.2.0 处置 | | --- | --- | --- | --- | | 1 | **会话恢复未接**:`foldEvents` 就绪但 `apply()` 不读会话历史 | `index.ts`「dsh 的会话读取 API 在真机上确认后接进来即可」 | **必做,UI 前置**。宿主重启后 store 为空,旧 specId 全部不可编辑;面板也依赖 fold | | 2 | 渲染注册表 Symbol 挂载 → 应换 cordis Service | `index.ts` Symbol.for 段注释 | 换。真机挂载已验证过,欠的就是这一步重构 | | 3 | `SpecStore` / `foldEvents` 住在 host 包(`@dsh-anim/tools`),client 不能 import host 实现 | 官方硬规则:client 绝不 import host 实现进浏览器 bundle | **把 fold / store 纯逻辑下沉**到共享层(`@dsh-anim/spec` 或新 `@dsh-anim/store`),host 与 client 共用同一份 fold,状态还原不漂移 | | 4 | `onProgress` 链路断了三层 | `runtime.ts` 支持(有头时写窗口标题)→ `adapter.ts` 支持(透传)→ `ops.ts` 没接 | 随 jobs 化接通:进度 → `anim/render-progress` 事件 → 面板进度条 | | 5 | preview = 整片低分辨率渲染后挑帧,`atMs` 不改变渲染量 | `adapter.ts` preview 注释自认 | 优化:只渲到最晚抽帧点(截短渲染区间),抽查第 2 幕就不用渲完后 6 幕 | | 6 | store 无快照,每次从事件 0 开始 fold | `store.ts` 注释自辩 | 不动(几十场景微秒级),继续观察 | --- ## 2. UI 工作台设计(本版本主体) ### 2.1 平台事实(已核实,写作时点 dsh 0.1.5-rc.2) - **包侧声明**:package.json 声明 `dsh.client`(`platform: 'web'` + `inject` 依赖边),`exports['./client']` 导出构建好的 bundle,entry id = 包名(client-modules 文档实证)。 - **`ctx.jobs` 真实存在**:`start({ kind, label, owner, run })` → `{ cancel, done, readOutput }`;`start` 要求 owner 有 attached job controller(无主任务可绕过,但优先 `owner: exec.agent` 真机验证);模型侧 `job_output` / `job_kill` 由 `dsh-tool-jobs` 提供(jobs 子系统文档实证)。 - **client 包全套在 npm 上有 0.1.5-rc.2**:`dsh-client-ui-conversation`(ConversationNodeDefinition 所在)、`dsh-client-runtime`、`dsh-client-connection`、`dsh-client-locale`、`dsh-client-modules`、`dsh-client-ui-jobs` 等。 - **`conversation.chat.node` keyed renderer + ConversationNodeDefinition**:设计草案 §3 的方案基于 rc.1 官方 client 包拆包实证;**rc.2 的确切签名还没拆包核对过**——这是 M0 第一件事。 ### 2.2 包形态决策:同包双入口 在现有单发布包内加 client 面:`packages/client`(新 workspace 包)→ 打成 `lib/client.js`(浏览器 bundle),`package.json` 增加 `dsh.client` 声明与 `exports['./client']`。 - 选同包不选独立发布包:单仓单包的发布、版本、离线打包链路全部天然同步,offline-packager 侧只需要 `files` 白名单加一行; - 硬约束不变:**client 绝不 import host 实现代码**(官方要求)。共享的只有纯数据层——IR 类型 + 校验 + patch + fold,全部在共享包里(见 1.3 #3); - 构建上 `build.mjs` 增加第二个 esbuild entry(client 面向浏览器、CJS/模块表形态,具体产物包装以 M0 拆包结论为准)。 ### 2.3 面板功能分级 ``` ┌──────────────────────────────────────────────────┐ │ P0 只读工作台(会话流里的 anim-studio 节点) │ │ · spec 卡片:标题 / 时长 / 画布 / 状态 │ │ · 场景列表:每幕名称、时长、图层数 │ │ · 修改历史:note + 版本号(来自 spec-patched 事件) │ │ · 产物卡片:MP4 路径 + 渲染参数(render-finished) │ ├──────────────────────────────────────────────────┤ │ P1 预览与进度 │ │ · preview 帧图内联显示(路径→URL 机制 M0 核对) │ │ · 渲染进度条(render-start/-progress 事件驱动) │ │ · 渲染完成显示视频卡片(能内嵌播放就内嵌) │ ├──────────────────────────────────────────────────┤ │ P2 最简交互 │ │ · 「撤销这步 / 预览第 N 幕」按钮 → 生成结构化指令 │ │ → agent.followup()/steer() 走模型回合执行 │ │ (设计草案 §6 路径 2,零新增机制; │ │ loopback RPC 直改 spec 留 0.3) │ └──────────────────────────────────────────────────┘ ``` 事件消费侧遵守设计草案 §3 的硬规则:`match` / `start` / `update` 必须是纯函数(实时流与日志回放两条路径共用)。现有 4 种事件够 P0;P1 的进度条依赖新增的 `render-start` / `render-progress`。 ### 2.4 需要真机核对的三件事(M0,全部是 UI 的前置) 1. **client API 实证**:拆 `@deepseek-ai/dsh-client-ui-conversation@0.1.5-rc.2` 与 `dsh-client-modules`,核对 ConversationNodeDefinition 签名、slots 注册方式、client bundle 的产物包装形态,先在真机 `dsh web` 挂出一个**空面板**; 2. **事件落盘**:真机 Web 会话里确认 `anim/*` 事件持久化、刷新后能从日志 fold 回来(同时喂 M1 的会话恢复); 3. **产物可见性**:渲染出的帧图 / MP4 是本地磁盘路径,Web 客户端把它们显示出来走什么机制(宿主文件引用路由?还是需要插件自己提供静态服务)——决定 P1 的帧图卡片是内联显示还是退化为路径 + 宿主 `@file` 引用。 --- ## 3. 里程碑与验收 | 里程碑 | 内容 | 验收标准 | | --- | --- | --- | | **M0 前置验证**(1–2 天) | §2.4 三件事 + `ctx.jobs` 可用性与 controller 归属验证 | 真机 `dsh web` 能看到空面板;事件刷新后可 fold 恢复;jobs `start/kill` 真机走通;产物显示机制有结论 | | **M1 遗留清障**(3–4 天) | 会话恢复 fold 接入;`ctx.jobs` 后台渲染 + `render-start/-progress` 事件 + 取消;fold/store 下沉共享包;registry 换 cordis Service;preview 截短渲染区间;README 已知限制刷新 | 重启宿主后旧 specId 可继续 patch;渲染期间模型可用 `job_output` 看进度、`job_kill` 取消;`pnpm smoke` 全绿且覆盖新事件 | | **M2 工作台面板**(5–8 天,本版本主体) | P0 只读面板 → P1 帧图/视频卡片 + 进度条 → P2 最简指令按钮 | 会话里做一支片子全程不开对话也能看清状态;刷新/回放后面板状态一致;点「撤销上一步」能驱动一次真实 patch | | **M3 收口**(2–3 天) | `group` 图层实现;`anim-studio` preset + 分镜方法论提示词段;离线打包链路验证(含 `lib/client.js`);examples 与 README 全面更新 | group 图层端到端渲出;preset 一句话出片;offline-packager 打出的 tgz 装上后面板可用 | 版本内顺序:M0 → M1 → M2 → M3,M0 不通过则 M2 降级为「只读面板 + 路径展示」。peer 依赖维持 `>=0.1.5-rc.2`,发布前在真机锁定 verified 版本写进 README。 ## 4. 风险 | 风险 | 等级 | 应对 | | --- | --- | --- | | client API 在 rc.2 的形态与设计稿(rc.1 实证)有出入 | **高** | M0 第一优先级拆包实证;空面板是最小可验证单元,先跑通再铺功能 | | `ctx.jobs` 的 owner/controller 归属在插件场景的约束(start 可能因无 controller 被拒) | 中 | 真机验证;兜底方案是无主任务 + 插件内注册表 | | 磁盘路径产物在 Web 端无现成显示机制 | 中 | M0 核对;P1 退化为「路径 + 宿主 @file 引用」,不阻塞 P0 | | 离线打包不认识 client bundle | 低-中 | `files` 白名单 + M3 实测一遍 offline-packager 全链路 | | dsh 0.1.x 破坏性变更(持续风险) | 持续 | 锁 verified 版本;IR / 工具层与宿主解耦的既有策略不变 | ## 5. 明确不做(推到 0.3+) TTS 旁白与音画对齐、字幕轨道、loopback RPC 直改 spec、Remotion / Manim 渲染 Provider、场景相机、store 快照优化、`anim_asset_import`(若 M0 发现素材是真实阻塞则提前)。 --- ## 6. 实施进度 分支:`feat/0.2.0-workbench`。 | 项 | 状态 | 说明 | | --- | --- | --- | | M1 会话恢复 | ✅ 代码完成 | 挂载时 `session.snapshotEvents()` 探测 → fold → `store.adopt`(含撤销历史);冒烟含二次挂载恢复回环 | | M1 jobs 后台渲染 | ✅ 代码完成 | `anim_render` 走 `ctx.jobs`(结构探测,不可用自动退同步);新增 `anim/render-start` / `anim/render-progress`(5% 节流)/ `render-finished`(带 status/error);事件经 thunk 闸保证 start→progress→finished 因果序 | | M1 注册表服务化 | ✅ 代码完成 | `ctx.reflect.provide('animRenderers')`,provider 可 `inject: ['animRenderers']`;`plugins.d.ts` 改为真正的模块扩充(原全局脚本的 ambient 声明会遮蔽 cordis 模块——踩过) | | M1 store 下沉 | ✅ 代码完成 | 新共享包 `@dsh-anim/store`(SpecStore / foldEvents / adopt),为 client 面板复用铺路 | | M1 preview 截短 | ✅ 代码完成 | `truncateSpecAtMs`:只渲染到最晚抽帧点;截断点前时间线逐毫秒等价 | | M1 README 刷新 | ✅ 完成 | headless 默认、后台渲染、会话恢复、包结构补 store | | M0 真机三项 | 🟡 重大发现已修复,待复验 | 见 §8:宿主 ctx 无 session 服务,0.1.x 事件从未落盘;落盘/恢复已改 `exec.agent.session` 正道。真机复跑(14:44 会话,跑在修复构建之前)确认:渲染管线正常、9 次 render 全走 sync 回退——`ctx.jobs` 在插件 ctx 上不可见(但 dsh-tool-jobs 已挂载、系统提示词有 jobs 引导),已加 `anim_diagnose` 宿主服务可见性报告 + jobs 两级回退(owner → 无主 → 同步)。剩余:重启 dsh 重跑,用日志复核事件持久化 + 产物显示机制 | | M0 client API 拆包 | ✅ 完成(离线部分) | 结论见 §7;真机空面板已由 §7 的 keyed toolview 方案替代落(见下) | | M2 面板 | ✅ 代码完成(P0+P1 主体),待真机验收 | **方案落点有变**:不走 ConversationNodeDefinition,改用 dsh-client-ui-tool 声明的 keyed 插槽 `tool.call.toolview`(按线上工具名认领渲染权,官方 read/search toolview 同款机制;语义是「每次工具调用的视图」,天然规避了自定义 Definition 的节点放置/回放一致性问题)。宿主侧配套:9 个工具补齐 `output.presentationMeta`(卡片数据源,回放可重建);`ctx.inject(['webServer'])` 挂 `/dsh-anim` 前缀路由——`media`(产物进浏览器,Range/ETag/放行白名单)+ `api/state`(specs+渲染簿,后台渲染卡片轮询进度)。**视频预览**(用户明确要求,dsh Web 无视频预览能力):`anim_render` 成片内嵌 `