# dsh-better-summary 详细设计 > 状态:**v1 已实现并全部验证通过**(typecheck / 143 项单元与组件测试 / 5 条 E2E lane 全绿) > 目标版本:v1(无撤销 / 内联 diff / 保留「在文件夹中显示」) > 约束:不修改 `deepseek-harness/` 任何源码;关键路径不依赖 git > 实现与设计的差异见 [§18 实施记录](#18-实施记录与设计的差异)。 --- ## 目录 1. [背景与目标](#1-背景与目标) 2. [决策记录](#2-决策记录) 3. [技术调研结论(证据链)](#3-技术调研结论证据链) 4. [总体架构](#4-总体架构) 5. [数据推导抽象层](#5-数据推导抽象层) 6. [卡片 UI 规格](#6-卡片-ui-规格) 7. [设置与动态调整](#7-设置与动态调整) 8. [Host 半边设计](#8-host-半边设计) 9. [包与工程形态](#9-包与工程形态) 10. [配置项总表](#10-配置项总表) 11. [边界与降级矩阵](#11-边界与降级矩阵) 12. [测试与 E2E 验证](#12-测试与-e2e-验证) 13. [安全与隐私](#13-安全与隐私) 14. [性能](#14-性能) 15. [风险与缓解](#15-风险与缓解) 16. [实施步骤](#16-实施步骤) 17. [未来增量](#17-未来增量) 18. [实施记录(与设计的差异)](#18-实施记录与设计的差异) 18. [附录 A:DSH 接口证据索引](#附录-adsh-接口证据索引) 19. [附录 B:落地前待验证项](#附录-b落地前待验证项) 20. [附录 C:术语](#附录-c术语) --- ## 1. 背景与目标 ### 1.1 背景 DSH 的对话流在每轮结束时渲染一行「任务产出」:文件 chip + 省略计数 + 「在文件夹中显示」。该位置是 **list** 槽 `conversation.chat.turnTail`:条目按 `id` 划分单元格,同一单元格内按 priority **升序遮蔽**,只有最低的存活条目渲染;新 `id` 追加一行,复用已有 `id` 即顶替该行。在本部署中,这一行实际由已安装的第三方插件 **`dsh-better-sidebar`** 接管渲染(它沿用 DSH 自带产出行的单元格 `id`,priority `-1`,遮蔽自带 `ui-deliverables` 的 `0`);DSH 自带的 `ui-deliverables` 被遮蔽,一旦其上的条目注销即作为该单元格的下一顺位重新渲染(即回落行)。 用户希望把它换成 **Codex 风格** 的汇总卡片:`已编辑 N 个文件` + 总计 `+X −Y` + 每文件 `+a −d` + 折叠展开 + Review 内联 diff + 保留「在文件夹中显示」。 ### 1.2 目标 | | 内容 | |---|---| | 交付形态 | 常驻 bundle 插件(独立仓库、独立 npm 包),以 profile bundle 方式挂载 | | 视觉 | Codex 风格卡片,替换现有 chip 行 | | 数据 | 精确的每文件新增/删除行数与总计,**不依赖 git**,非 git 目录同样可用 | | 交互 | 折叠/展开、按文件内联 diff、审查模式、行点击打开文件、「在文件夹中显示」 | | 配置 | 在 DSH 设置中以独立页面动态调整,保存即生效 | | 可扩展 | 数据推导按「变更来源」抽象,新增工具/新增来源只需注册策略 | | 验证 | 单元 + 组件 + Playwright E2E(含真实工具链的确定性 lane 与复用本机已配置 API 的真实模型 lane) | ### 1.3 非目标(v1) 撤销(Undo)、全屏 review 面板、git 补漏层、非工具途径(`bash`/代码生成器)改动的可见性、设置变更的跨客户端推送、双语 README。 --- ## 2. 决策记录 设计过程中被明确否决或推迟的方案,记录于此以免后人重复讨论。 | # | 议题 | 结论 | 理由 | |---|---|---|---| | D1 | 实现机制:动态 Cordis 插件 vs 常驻 bundle 包 | **常驻 bundle 包** | 用户要求长期生效;本部署已跑通 bundle 渠道(`dsh-better-sidebar` 等 3 个包) | | D2 | 行数统计来源:`git diff --numstat` | **否决** | 强依赖 git;非 git 目录、临时目录直接失效 | | D3 | 行数统计来源:DSH 工具结果里持久化的 applied diff | **采纳** | `write`/`edit` 把 `{diffs: FileDiff[]}` 写进 tool result `meta` 并随会话日志持久化;含前后文本与上下文,精确且与 git 无关 | | D4 | 撤销实现:`git restore` | **否决** | 会连坐"任务开始前就存在的未提交改动",且依赖 git | | D5 | 撤销实现:反向应用 hunk | **推迟到 v2** | 机制可行(把 hunk 的 `newText` 换回 `oldText`,含 staleness 校验),但 v1 按用户决策不做 | | D6 | Review 形态:全屏 overlay 面板 | **推迟到 v2** | v1 采用卡片内联 diff,单槽单组件,复杂度最低 | | D7 | 「在文件夹中显示」:复用 `dsh-better-sidebar` 的侧栏定位 | **否决** | 其服务 `BetterSidebarService` 只暴露 `openTab`/`registerTab`/…,**没有** explorer reveal 接口;跨插件耦合也不可取 | | D8 | 「在文件夹中显示」:自研原生打开 | **采纳** | 与 DSH 早期"原生文件夹交接"语义一致;Host 侧 argv 数组 + 路径围栏,零耦合 | | D9 | 数据推导:硬编码在组件里 | **否决** | 用户要求抽象化,便于后续接入其它工具 | | D10 | 配置载体:仅 cordis Config | **否决** | 用户要求能在 DSH 设置中动态调整;cordis Config 只保留部署级项 | | D11 | E2E 数据来源:录制会话 fixture | **备选** | 会话以 `session.jsonl.zstd` 存储,fixture 绑定会话格式版本;首选 mock 模型 lane | | D12 | E2E 真实模型 lane:是否需要 API key 环境变量 | **不必要** | 可直接复用本机 `$DSH_HOME` 中已配置的 provider 与凭据(只读复制进 scratch 环境),故该 lane 为必选 | --- ## 3. 技术调研结论(证据链) 以下为设计所依赖的 DSH 行为,均已在本仓库源码中确认(路径相对 `deepseek-harness/`)。 ### 3.1 槽与接管 | 事实 | 证据 | |---|---| | `conversation.chat.turnTail` 是 **list** 槽,scope `session`;条目按 `id` 划分单元格,同格内按 priority **升序遮蔽**,**只有最低的存活条目渲染**(无 `select` 座位、无 `matched` 注入);新 `id` 追加一行,复用已有 `id` 即顶替该行 | 槽 catalog;`packages/client/ui-slots/src/index.ts` 的 `SlotCore.register` / `SlotCore.entriesOfSlot` | | owner props 恰好是 `{ turn, seq, openFile }`(条目在渲染期自行据此组装数据) | `packages/client/ui-chat/src/client/chat/TurnTailNodeView.tsx:25-26` | | 当前占用者:`dsh-better-sidebar`(同一 `id`,-1)、`ui-deliverables`(同一 `id`,0) | 运行时槽树查询 | | list 槽要求注册项带 `options.id`(缺失即在加载期抛 `list slot "conversation.chat.turnTail" requires options.id`);同一单元格的同 priority 再注册也会抛错,必须换 priority 才能遮蔽。常驻 bundle 包不经过动态守卫,priority 由注册者自带(本包默认 `-2`,设置项可调) | `packages/client/ui-slots/src/index.ts` 的 `SlotCore.register`;`packages/extensions/cordis-client-runner/src/client/guard.ts` 的 `guardedSlots`(动态包对非 chain 槽自动分配 shadowing rank) | | 该槽标准 props 含 `useChat` / `useSession` / `useWorkspaces` / `t`(经 `locale` 选项) | 槽 catalog `standardProps` | ### 3.2 数据来源 | 事实 | 证据 | |---|---| | `write`/`edit` 执行时把 applied diff 分块写进 tool result `meta`,**随会话日志持久化**(供 replay 复现 diff 卡片) | `packages/fs/tool-fs/src/diff.ts:14-20` | | 每个 `FileDiff = { path, oldText: string\|null, newText: string }` 是一个**含 3 行上下文**的 hunk(上下文行同时出现在两侧) | `packages/fs/tool-fs/src/diff.ts:33-57` | | 工具执行结果本身返回 `{ path, before, after }` | `packages/fs/tool-fs/src/write.ts:121-126`、`edit.ts:140-144` | | 会话事件 `'tool/result'` 携带 `meta?: JsonValue` | `packages/core/session/src/types.ts:353-360` | | Client 侧 Chat 节点保留该 meta | `packages/client/ui-chat/src/client/conversation-nodes/tool.ts:65` | | `ChatSnapshot` 暴露 `order` / `nodes: ChatNodeStore` / `locations.getTurn(turn)` | `packages/client/ui-chat/src/client/contract/snapshot.ts:92-99` | | 产出文件清单来自 Turn data:`owner.turn.data.get('deliverables')` → `{ produced: [{ seq, path }] }`(**只有路径,无行数**) | `packages/client/ui-deliverables/src/client/turn-deliverables.ts:122-145` | | `str_replace_editor` **无** result meta,只有调用期 `presentCall` 派生的 diffs(`create` 纯新增 / `str_replace` 片段 / `insert` 无文本) | `packages/fs/tool-str-replace-editor/src/index.ts:385-415` | ### 3.3 设置 | 事实 | 证据 | |---|---| | Host `settings.register(ns, schema, { base, applies, validate })` → `SettingsScope`(`get` / `watch`);`update/replace/mutate` + `describe` 返回 `{ ns, value, revision, base, user, applies }`;命名空间必须是小写连字符标识符 | Host `settings` 服务契约 | | `applies: 'live' | 'restart'` 是语义声明,供设置界面提示 | `SettingsApplies` | | `settingsController` 提供 `@Remote describe / update / replace / mutate`,即客户端可用的 `ctx.remote.settings` 命名空间 | Host 服务目录 | | 设置页槽:`settings.section`(list,注册项 `{ id, order, label }`);`settings.general.item` 用于单行偏好 | 运行时槽树查询 | | 设置与凭据的存储位置:`$DSH_HOME/settings.yaml`、`$DSH_HOME/.credentials.yaml`(`$DSH_HOME/.env` 为只读回落) | `packages/settings/settings-file/src/index.ts:57`、`packages/credentials/credentials-local/src/index.ts:61` | ### 3.4 客户端半边与主题 | 事实 | 证据 | |---|---| | 静态 client bundle 形态:`window.__ModuleLoader__.load({ id: <包名>, factory: (require) => {...} })`(CJS closure),平台外部化 `react` / `react/jsx-runtime` / `react-dom` / `cordis` / `ui-slots` / `ui-primitives`,其余内联;CSS Modules 编译为哈希类名并在 factory 执行时注入 `