# 更新日志

本文件格式遵循 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/)，版本号遵循语义化版本。

## [2.3.24] - 2026-09-18

### 新增

- **「仅撤回对话」模式（撤回范围二选一）**：对生成结果不满意、但文件改动恰是想要（或已人工修整）时，整段回退会把文件一并覆盖。确认面板新增撤回范围 radio 组（原生 input、键盘可达；默认「回退文件与对话」与现状逐项一致；首条用户消息因对话无从回退不渲染）——选「仅撤回对话」后文件清单降级为参考语义、安全快照预告隐藏（不打安全快照：零文件改动即无不可逆操作缺口），按钮与 executing/done 文案随模式分叉。实现＝`ExecuteArgs` 增 `scope?: 'both' | 'session-only'`（缺省/非法值回落 both：老 Client 与直调 API 行为不漂移），Host execute 开头分叉——session-only 不进串行队列、零 git 写操作（保留 NO_SNAPSHOT/AGENT_BUSY 两道护栏，跳过 STALE 校验/安全快照/rollback/rescue 全链），直接 `resolveCutSeq` + `resolveStaleQueueItemIds` 返回 `count: 0`；fork/归档/子会话导航/排队消息清理/lineage/草稿回填链与模式无关，原样复用。新增 `tests/unit/routes-scope.test.js` 5 例（session-only 零 git 调用与透传、agentBusy 拦截、NO_SNAPSHOT、非法 scope 回落 both、不传 scope 现状回归钉）。

## [2.3.23] - 2026-09-18

### 修复

- **撤回后子会话不打开（dsh 0.1.6-alpha.2 移除 `ISessions.open`，实弹冒烟发现）**：alpha.2 起 `ISessions` 只保留列表快照与 `retain`/`using` 引用模型，契约注释写明「navigation belongs to view owners」——原调用的 `sessions.open(sessionId)` 已不存在，守卫判断静默跳过，于是撤回的 execute/fork/lineage 全部正常，唯独 fork 出的子会话不会被打开：页面停在工作区空态。导航入口迁至独立 `uiWorkspace` 服务（`dsh-client-ui-workspace`）的 `openSession(target: SessionTarget)`（`SessionTarget = SessionId | SubagentAddress`，官方 UI 同样传裸 sessionId；`ctx.workspaces` 只有归档能力、无导航）。修复＝client `inject` 声明 `uiWorkspace` + `ctx.uiWorkspace` 类型化（`ClientUiWorkspaceService`），导航点改双版本分支：`uiWorkspace.openSession(id)` 优先、旧版回退 `sessions.open(id)`（撤回节点与快照管理「切换」两处）。`tests/probe/api-surface.test.js` 新增「会话导航归属」2 例钉住两侧事实，compat-audit 新增 I37。实弹复验：撤回第二条消息后子会话自动打开、对话回退、标题继承、被撤回消息文本回填输入框。

- **快照管理「切换」把归档会话放进点击路径，一点就落到工作区空态**：按钮判据只查会话是否在官方列表里（`sessions.list`），依据「已归档会话不在列表」的假设；alpha.2 起该快照包含归档会话，而归档会话不是合法的主视图选择——官方导航会先设置选择、随即按「归档选择不保留」把它清空，视图落空态（撤回后归档的原会话最常命中）。修复＝判据叠加归档集合排除：`ClientWorkspacesService` 补 `list` 面，从 `ctx.workspaces.list.getSnapshot().archivedSessionIds` 取归档集合（服务经 `buildSettingsCards` 传入快照管理卡），只有「在官方列表且未归档」的会话才渲染「切换」。实弹复验：归档会话行不再出现「切换」，保留「切换」的活跃会话点击后正常打开。

### 变更

- **旧版回退核验 + 兼容声明下界收窄**：本批次三处改码都在跨版本面上（静态 `uiWorkspace` inject、`plugins.bundle.config` 注册、读 `workspaces` 归档集合），故把全局 dsh 降到纯 `0.1.6-alpha.1`（`npm install -g @deepseek-ai/dsh@0.1.6-alpha.1 --before=2026-09-16`；不带 `--before` 会因 dsh 自身 `^0.1.6-alpha.1` 放行同 tuple 预发布而混入 alpha.2 的包）实弹一轮，确认未升级 dsh 的用户更新插件后不会变砖——三项全过、控制台零报错：旧 slot（`settings.plugin.item`）卡片照常渲染且样式注入正常、`plugins.bundle.config` 在旧渲染器上是静默 no-op；撤回 fork 后子会话由 `uiWorkspace.openSession` 正常打开（切过去后再发消息、新快照记到子会话 id 为证）；快照管理树按 lineage 聚族（`v1/2`/`v2/2`）。同时按 registry 产物逐版核验 `uiWorkspace` 服务可用性：`0.1.2-alpha.2` 起有该服务但无 `openSession`（该线走 `sessions.open` 回退，`ISessions.open` 到 alpha.1 都在）、`0.1.5-rc.2` 起 `openSession` 在位，而 **`0.1.1-rc.2` 线没有该服务**（同线亦缺 `sessions`/`workspaces`：`dsh-api-session-controller`/`dsh-api-workspace-controller` 无该版本发布）——静态声明无法满足，插件 UI 会静默不渲染。据此**7 个 dsh-* peer 范围去掉 `>=0.1.1-rc.2 <0.1.2` 段**，让该组合在安装期被明确拦住而不是装上了白屏；README 双语兼容声明与 badge 上界由 `0.1.6-alpha.1` 更新为 `0.1.6-alpha.2` 并写明不再声明支持 0.1.1-rc.2 及更早的原因；compat-audit I37 补「服务可用性」条目，实弹记录见 `docs/plans/completed/smoke-checklist-records.md`。核验完装回 `0.1.6-alpha.2`（树内 8 个 `@deepseek-ai/dsh-*` 包一致），`npm run check:upgrade` 三层门禁全绿（check:dsh 全一致 / test:probe 39 通过 / verify:host 通过）。

- **README 双语补充快照捕获窗口的边界说明**：快照为异步捕获，从收到消息到 `git add` 之间有约 0.5–1.5 秒窗口（Windows 上一次 PowerShell 启动即约 0.4 秒；index.json 记的 `time` 是脚本发起时刻而非捕获时刻）。秒级完成的琐碎任务若落在窗口内改完文件，该条快照会连带捕获本轮改动——撤回时文件回退成为空操作（预览显示「共 0 个文件将变更」），对话回退不受影响，且撤回前仍先落 `snap-pre-rollback-*` 安全快照，无数据丢失。此为异步快照的固有边界，无法消除（除非让每条消息同步阻塞等快照完成）。
- **dsh 0.1.6-alpha.2 兼容适配：设置卡片迁挂插件管理页 slot（I12）**：全局实装 `@deepseek-ai/dsh@0.1.6-alpha.2`（npm dist-tag `alpha`，tag commit `ddefc45`）后核查发现旧设置页插件 tab 整体移除——`settings.plugin.item` 与 `settings.plugins.tab` 产物字符串归零，新增 ui-plugin-manager 插件管理页，声明 `plugins.item` / `plugins.bundle.config` / `plugins.row.config` 三 slot（bundle 自带配置走 `plugins.bundle.config`，keyed by bundle 包名，page 视图要求表单自含保存控件）。Client 侧改为双键并注册：新增 `plugins.bundle.config`（key=`dsh-recall-plugin`，插件管理页 bundle 页渲染 RecallSettingsCard）+ 保留旧键 `settings.plugin.item`（key=`dsh-recall`，0.1.6-alpha.1 及以前生效）——官方 renderer 对未声明 key 的 `slots.inject` 是静默 no-op（`specDynamic` 为 undefined 直接 return），双版本各吃各键无需运行时探测。其余消费面零破坏：fork 签名与切点 `cut = boundary.seq + 1`（I35 根治）逐字保持；connection `fetch.register` 契约不变（仅新增 `streamBaseUrl?` 可选字段）；client sessions 改 retain/release 引用模型（多实例共存），`binding()` 收窄为「只借已 retain 的会话」——附件重建链（I34）全链 typeof 降级兜底，最坏仅附件不重建（导航面另有两处问题，见上方修复）。peer 范围 `>=0.1.6-alpha.1 <0.1.7` 天然覆盖 alpha.2 无需扩展。机器化断言：`npm test` 361/361 + `check:upgrade` 三层门禁全绿（probe 37/37）。reference/ 镜像按 alpha.2 tag 重拉（仅 06/09 文档文字修订）；compat-audit 头部追加 alpha.2 核验段并更新 I12。实弹冒烟记录见 `docs/plans/completed/smoke-checklist-records.md`。

## [2.3.22] - 2026-09-16

### 修复

- **win32 上宿主把 `ctx.shell` 配成 bash 时功能面整个死亡（issue #15）**：官方 shell 是提供方注册制——一个 composition 恰好一个 `ctx.shell` 实现，win32 的 profile 可以只启用 `bash-sandbox`（禁用 `pwsh-sandbox`）；而插件按 `process.platform` 单选 pwsh 模板，两者之间没有契约保证，于是模板被 bash 执行、第一行编码前导即语法错误（`bash: -c: line 1: syntax error near unexpected token '('`），`ensureGit` 起每一步都失败：快照从未成功、撤回按钮不可用。官方 `ShellExecutor` 公开面（`resolve`/`run`/`start` + `sandboxMode`）没有任何方言标识，**不能查询只能探测**，故修复＝行为探针判方言 + 判成 bash 时改走直连通道：
  - **S1 方言探针**：`runShellMeta` 首调内联执行 `Write-Output <罕见 ASCII 哨兵>`（不带 UTF8_PRELUDE、30s 短超时、直调 `ctx.shell` 不触发失败清扫）；exit 0 且回显哨兵判 pwsh，非零退出（bash 下 command-not-found 即 127）/无输出/reject 一律判 bash。结果按进程缓存并以 in-flight promise 去重（突发 `session/event` 只探一次）。POSIX 不探测——bash 模板与 bash 执行器天然一致。
  - **S2 直连通道**：判 bash 时 `runShellMeta` 分流到 Node `spawn` 直连 `%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe -NoProfile -NonInteractive -Command <单 argv>`（PS 5.1 全平台自带，不赌 PS7；模板本就按 5.1 兼容写），保留官方四项语义：stdin 字节透传（PS 5.1 代码页坑由字节流绕开）、stdout 超截断保留尾部并置 `truncated`（索引读取靠它区分截断与损坏）、超时 `child.kill()`、非零退出仍走 `cleanupAfterGitFailure`；env 复刻官方清洗（剥凭证形状名与全部 `DSH_*`，叠 `NO_COLOR`/`PAGER`/`GIT_PAGER`），cwd 取 `sandboxPolicy.workspaceRoot || process.cwd()`，`windowsHide` 防桌面端闪窗。带 `RECALL_CLEANUP` 哨兵的失败清扫脚本不触发探针（只用缓存）——它只在真实命令失败后被调用，方言早已判定，善后路径不再叠一条探测进程。
  - **默认路径零变化**：`ctx.shell` 即 pwsh 的常规部署（探针判 pwsh）与 POSIX 都不触达直连通道；探针误判的代价也低——两条通道对 pwsh 模板都兼容，判成 bash 只是绕开官方托管环境（无 `dshEnv`/PATH 注入、无进程树级终止）。
  - 新增 `tests/unit/store-shell-dialect.test.js`（31 例：判定/收集/env 清洗/可执行路径四个纯函数 + 假 child 覆盖 stdin 字节透传、截断、超时 kill、spawn error + 分流接线与 in-flight 去重）；`tests/probe/api-surface.test.js` 新增「win32 shell 方言」3 例（ShellExecutor 无方言字段、直连复刻的 PS 5.1 候选路径与 argv 旗标、env 清洗口径）；compat-audit 新增 I36。

## [2.3.21] - 2026-09-15

### 变更

- **dsh 0.1.6-alpha.1 兼容性声明与台账同步**：全局实装 `@deepseek-ai/dsh@0.1.6-alpha.1`（npm dist-tag `alpha`，0.1.6 线首个预发布）后跑三层门禁——`verify:host` 装配断言通过、`npm test` 330/330、`test:probe` 32 例中 1 红。红点即本版唯一行为级变化：**官方修复 `sessions.fork` 按轮次分叉的切点**——由「`cut` 从 `boundary+1` 向后推进到下一个 `turn/start` 之前」改为「`cut` 固定 `boundary.seq + 1`、精确切到选中 `turn/end`」，结束事件之后的排队输入、标题、模型设置均不再复制进子会话 seed，2.3.17 起插件侧 `scanStaleQueueItemIds` 清理的残留排队消息问题（I35）在 0.1.6 线上从源头消失（探针按 2.3.20 预留的「好消息变红」路径改钉新锚点后 32/32 复绿）。**清理逻辑不退役**：peer 范围保留 0.1.5 线段，该线上 fork 切点未修复、清理仍必要；0.1.6 上退化为 `queue-item-not-found` 吞掉的无害空操作。其余消费面零破坏：`snapshotEvents`/`eventAt`/`ownEvents` 仅标 `@deprecated` 未移除（内存跳行为不变，列为前瞻观察项）；`ShellExecutor.start` 异步化不涉及插件（只用 `resolve`+`run`）；`agent/session-start`→`agent/created` 事件改名与插件无关；回填链与 chat.node/settings slot 全部在位。tag 对比（800 commits / 300 文件）按消费面包过滤后唯一源码命中即 fork 实现；reference/ 镜像按 alpha.1 tag 重拉 13 源——10 份内容相同、05/09/13 三文件有实质差异（09 不变式措辞补「纯消息投影」、新增 `agent/created`、桌面 profile 重写；13 钩子行改名；均不触及插件消费的槽位/契约）。**兼容声明同步**：0.1.6 为新 minor 线，7 个 dsh-* peer 各追加 `>=0.1.6-alpha.1 <0.1.7` 段；`dsh.compatibility.dshReleases` 补 `0.1.6-alpha.1: compatible`；README 双语安装兼容声明与 badge、reference/README 归档字段、dsh-contract.md「对应版本」同步。评估实证见 `docs/upgrade-assessments/dsh-0.1.6-alpha.1.md`，compat-audit 新增 0.1.6-alpha.1 核验段并更新 I35 条目。本插件源码零功能变更。

## [2.3.20] - 2026-09-14

### 新增

- **探针：`sessions.fork` 切点推进行为 3 例**（I35 的文档化行为落成机器断言）：`tests/probe/api-surface.test.js` 新增「sessions.fork 切点推进行为」组，直钉 `dsh-api-session-controller` 构建产物的三条锚点——boundary 以「seq >= atSeq 的首条 `turn/end`」解析、`cut` 从 boundary+1 推进到下一个 `turn/start` 前、`seed` 取 `slice(0, cut)` 完整前缀。官方若改为「seed 排除未领取 inbox 项」（撤回残留排队消息的根治方向）或重构改名即红，提示复核 G1 清理逻辑是否可退役；0.1.1 旧版安装的实现锚点未核验，整体 skip（fork 签名探针仍覆盖旧包路径）。探针 31 → 34 例全绿，compat-audit I35 的「无直接探针」缺口随之闭环。

### 修复

- **与同页插件的全局名冲突（issue #14）：client 产物顶层声明整体收进 IIFE 闭包**：client bundle 以 classic `<script>` 原文 serve，esbuild cjs 产物的全部顶层声明都会挂到 `window`——顶层 `var CSS`（样式表数组）直接覆盖浏览器原生 `CSS` 对象（`CSS.supports()` 不再是函数），同页的 dsh-image-gen 等插件一调即崩（`CSS.supports is not a function`），DSH 反过来拒绝加载对方。且顶层名共 23 个（`clockText`/`buildTree`/`sizeText`…），逐个改名堵不完。修复：build-client.mjs 打包格式从 cjs 改为 iife——整个 bundle 包进箭头函数作用域，除 `window.__ModuleLoader__.load` 调用外零全局泄漏，对未来新撞名免疫；loader 契约不受影响（`factory: (require) => {...}` 收参注册与 `require("react")` 运行时加载原样保留，esbuild 的 require→require2 改名照旧）。build-client.mjs 新增产物断言回归钉：format 意外回退 cjs 或出现顶层 var/function/class 声明时构建即红。

- **撤回残留排队消息的自动清理从未命中：改为按 item id 直删**（0.1.5-rc.1 + 2.3.19 真机复现定位）：Host 侧解析与下发都正确（`/api/recall/execute` 响应带出窗口内入队项的 rpcId），失效段在客户端匹配——子会话输入框上方的残留卡片可稳定复现（刷新后仍在），30 秒轮询窗口内始终匹配不到队列行，随后弹「撤回前的一条排队消息未被自动清理…」toast 并打出 `console.warn`。改用官方 `updateQueue` 的寻址键直删：Host 侧 `scanStaleQueueItemIds` 取窗口内 user 来源入队项的 `inserted[].id`（即该消息的 message id）经 execute 响应的 `staleQueueItemIds` 下发；Client 侧对 fork 出的子会话逐项调 `updateQueue(itemId, { kind: 'remove' })`，删除队列快照匹配与 30 秒轮询（改为会话面未就绪时的 5 秒短等待，超时仍 warn + toast）。真机验收：子会话日志出现 `agent/inbox/spliced inserted=[] removed=1`（入队项当场移除），重复删除返回 `queue-item-not-found`，控制台零告警。读取链顺带加固：内存跳改用 `snapshotEvents()`（0.1.5-rc.1 的 `Session` 无 `events` 访问器，旧字段仍兼容），并把 `observeSession` 提到 `readSession` 之前。

## [2.3.19] - 2026-09-12

### 修复

> 本次为 2.3.17 起改动的首次 npm 发布：2.3.17 / 2.3.18 未单独发版，其全部改动随本版本一并上线。

- **2.3.17 的「排队消息」自动清理在真机上从未生效**（本机 `~/.dsh/sessions` 会话日志解压实证：两次撤回的残留排队项分别在 fork 后 92 秒与 9 秒才出现移除事件——均为手动删除，自动清理的重试窗口内子会话日志无任何队列事件）。两个静默失败面，各修一半：
  - **Client 侧重试窗口太短**：队列快照走 control 帧，`fork` 解析后还要等 `open`/staging 完成才到达——实测可晚于数秒，原 10×200ms（2 秒）窗口全部落空后静默放弃，残留卡片一直挂着。修复：扩为 30 秒长轮询（250ms 间隔，命中即停）；窗口耗尽仍无匹配时不再静默——`console.warn` 留排查痕迹，toast 提示「撤回前的一条排队消息未被自动清理，可点击该卡片右上角的删除按钮手动移除」（toast 按文本节流，同一文案 10 分钟至多一次）。
  - **Host 侧解析只认 live 内存事件**：`resolveStaleQueueRpcIds` 此前在 `sessions.get()` 拿不到对象或其 `events` 字段缺失（版本漂移）时恒返回空数组，Client 收到空集合就什么都不做；而切点解析 `resolveCutSeq` 有磁盘降级链兜底、切点永远正常，恰好掩盖了这个缺失。修复：补齐与 `resolveCutSeq` 同款的两跳降级（`sessionQuery.readSession` → `observeSession`，后者覆盖 seeded 父会话——撤回链的父会话本身可能就是上一次撤回 fork 出的子会话；租约 `Symbol.dispose` 释放），两跳都失败才返回空数组。
  - 顺带核验了匹配键契约：宿主队列帧构造（`queueItemsFromInbox` / `promptRpcId`）对所有 `user` 来源行透传 `source.rpcId`，seed 重放行同样携带，`placement: 'queued'` 与 `{ kind: 'remove' }` 动作均与官方 QueueDock 行为一致，无需扩展。

## [2.3.18] - 2026-09-12

### 修复

- **用户消息里的文件块被渲染成原始 JSON**：插件覆盖了整个用户节点渲染（`conversation.chat.node` keyed 渲染器），此前只对 text/image 两类块做呈现——图片走官方 `renderMessageImages`、文本走气泡，其余块一律落进 JSON 兜底 `<pre>`。于是带文档/表格附件（`type: 'file'`）的消息（含撤回后重绘的那些）整块显示成 `{"type":"file","attachment":{…}}` 原文，而官方渲染是文件卡片。修复：新增 `fileCardInfo` 纯函数（文件名 / 扩展名徽标——大写截断 4 字符、无扩展名回退 `FILE` / 体积文本 `6.7KB` 官方风）与 `.dsh-recall-filecard*` 样式，file 块渲染成与官方 `UserStyleBubble` 同形的卡片（品牌色徽标 + 文件名单行省略 + 「MD 6.7KB」元信息），按「附件在上、文本在下」的官方布局排布，并从 JSON 兜底中剔除。新增 4 例单测。
- **被撤回消息里的文件附件回填静默缺失**：官方 `session.attachment`（`readAttachment`）只服务图片——Host 侧按 `referencedImage` 找引用、`readImage` 取字节，file 块直接判 `ATTACHMENT_NOT_REFERENCED`；插件读不到字节就填不回输入框，此前静默跳过。修复：撤回成功后若被撤回消息含 file 块，toast 明示「文件附件无法自动回填（官方接口只支持图片回读），请重新选择文件」，不再让用户以为回填是完整的（文本与图片回填照旧）。

## [2.3.17] - 2026-09-12

### 修复

- **撤回后输入框上方凭空多出一条「排队消息」**：官方 `sessions.fork({ atSeq })` 的切点不是「切点事件本身」，而是从该 `turn/end` 推进到**下一个 `turn/start` 之前**的整段事件——排队投递的用户消息，其 inbox 入队事件（`agent/inbox/spliced`，`target: 'next-turn'`）必然落在「上一个 `turn/end`」与「领取它的那个 `turn/start`」之间，正好被复制进子会话 seed；子会话重建 inbox 后，QueueDock 就把这条本该随撤回消失的消息显示成排队消息，与回填到输入框的内容重复（本机 `~/.dsh/sessions` 会话日志解压实证：父会话 seq 93 `turn/end` → 94 入队 → 95 `turn/start` → 96 领取后移除 → 100 `user/message` 落日志，子会话 seed 恰止于 94）。修复分两半：Host 侧新增 `scanStaleQueueRpcIds` 扫切点窗口内的 inbox 入队项、取出 `source.kind === 'user'` 项的 `rpcId`（prompt 提交身份），经 execute 响应新增的 `staleQueueRpcIds` 下发；Client 侧 fork 出子会话并 open 后 `purgeStaleQueueItems` 读 `sessions.binding(childId).session.getSnapshot().queue`，按 `placement === 'queued'` + rpcId 命中逐项调官方 `updateQueue(itemId, { kind: 'remove' })`（即 QueueDock 的「删除排队消息」）。用 rpcId 而非内容比对——撤回后新发的消息带自己的 rpcId，不会被误删。queue 快照走控制流、fork 后可能晚一两拍才到，故做 10×200ms 有界重试；拿不到 live 事件（冷会话）或服务面缺失时静默放弃（残留项仍可在 QueueDock 手动删除），不影响回填与撤回主流程。新增 `tests/unit/snapshots-queue-residue.test.js`（6 例）与 `pickStaleQueueItemIds` 3 例，`routes-stale` 补 stub 与透传断言，compat-audit 新增 I35。

## [2.3.16] - 2026-09-11

### 变更

- **插件描述与双语 README 文案重写**：包描述由「是什么 / 怎么做」改为「能做什么」——撤回自己发过的消息，工作区文件与对话历史一起回到那条消息发出之前，消息的文本与附件自动放回输入框（文件走独立影子 git 快照、对话走官方 `sessions.fork`、原会话归档可找回）。双语摘要补齐「撤回后可重发」；英文侧去掉过时的 DSH 版本标注；徽章由历史版本（`0.1.2-rc.1` / `0.1.3-alpha.1`，英文侧 `0.1.1-rc.x`）统一为当前核验版本 `0.1.5-rc.2`（兼容范围仍以「安装」节的 peer 声明为权威）；两侧同步新增「撤回完就能重发」亮点与预览区的回填要点。功能、接口与构建产物零变化。

## [2.3.15] - 2026-09-11

### 新增

- **撤回回填扩展为「文本 + 附件」**：此前撤回只把被撤回消息的文本回填到输入框，消息里的附件（图片/文件）不会回来；现在两者一起回填，改完可直接重发。实现沿用官方 composer 自己的附件链路（`dsh-client-ui-conversation` 的 `addFiles` 实证）：**早读**——撤回执行一开始（源会话仍在册、附件引用可解析时）经 `sessions.binding(sessionId).session.readAttachment(attachmentId)` 取回原始字节与 mediaType，重建为浏览器 `File`（缺文件名按 mediaType 派生 `attachment-N.<subtype>`）；**后写**——fork + open 完成后在子会话里 `conversation.createDrafts(sessionId, files)` 注册草稿附件、`shell.actions.addAttachments(ids)` 进入输入态（未接纳时 `releaseDraftAttachments` 释放，与官方 `addFiles` 的失败回滚一致）。附件读取授权绑定「消息所在会话」，故必须用源会话早读——被撤回消息不在 fork 出的子会话日志中，直读子会话拿不到授权。附件读取与草稿注册均逐项 try/catch、服务面全部 typeof 探测：任一步缺失只降级附件，文本回填与撤回主流程不受影响（旧版 dsh 无 `conversation`/`createDrafts`/`readAttachment` 时恒降级）。`refillDraft` 开关语义同步为「文本与附件」（设置描述与 README 双语同步）；新增 `attachmentRefsFromBlocks` / `defaultAttachmentName` 纯函数与 3 例单测，compat-audit 新增 I34。客户端 bundle 结构与既有行为零变化。

## [2.3.14] - 2026-09-11

### 修复

- **撤回 fork 出的子会话内，撤回预览对任何消息都误报「该消息是本会话中第一条用户消息」**：`resolveCutSeq` 冷读取走 `sessionQuery.readSession`，官方实现内部却用 `Session.create(events, header, inheritedEventCount)` 做回放校验——快照模式要求 seeded 头的 `inheritedEventCount` 恰等于 `log.length`（「seeded session constructor seed must equal its inherited prefix」），而读取面交给它的是全量逻辑日志（`snapshotEvents()` 无参＝继承前缀＋自身事件；冷读返回完整存储日志），两者必然不等，`readSession` 对任何 seeded 会话（本插件每次撤回 fork 出的子会话即此类）直接抛错；此前的 `catch` 把异常静默折成 null，与「真首条」不可分——于是子会话里点**任何**消息，面板都显示「该消息是本会话中第一条用户消息，无法回退对话；确认后仅回退项目文件」。修复：`resolveCutSeq` 增加降级链——`readSession` 抛错、或消息根本没出现在它给的事件里（读取面缺继承前缀的版本差异）时，改用 `sessionQuery.observeSession`（经 `Session.fromRestore` 恢复，restore 模式无该约束）读取全量逻辑日志；租约以 `Symbol.dispose` 显式释放（prepared 缓存项靠它减引用，漏释放会卡淘汰）；只有「消息在、其前无 turn/end」才是可信的“首条”，两跳都失败才落到 null。`scanCutSeq` 拆出 `scanCutSeqDetail`（cut + found）供降级判断；契约补 `observeSession` 与 `SessionObservationLease`（可选——旧版 dsh 无此 API 时维持原行为）。**真机复验**（dsh web 0.1.5-rc.2 + POST `/api/recall/preview`）：修前子会话 6 条消息 cutSeq 全 null、父会话正常；修后子会话 156/43/62/88/197、真首条（你好）仍 null、父会话不变。新增 `tests/unit/snapshots-cutseq.test.js`（6 例），compat-audit 新增 I33。插件功能、客户端与其余行为零变化。

## [2.3.13] - 2026-09-11

### 修复

- **DSH Desktop 上插件树加载失败（fiber 永久 pending）**：桌面端 composition（`desktop.cordis.patch.yml`）把 `webserver` row 置 `disabled: true`（Electron 用 file:// + IPC 替代 HTTP），插件 host 侧顶层 `inject` 含 `webServer`，fiber 因此停在 `pending (waiting for service: webServer)` → 「1 entry did not activate」整树加载失败。修复：① 顶层 inject 收敛为 `['shell', 'sessions', 'agents']`；② 删除 `ctx.webServer` 前缀路由注册，改走官方「载体无关」路由注册表 `ctx.connection.fetch.register`——12 个端点各注册一条 `POST /api/recall/<name>` 的 exact 路由（`requestBody: 'buffered'`、插件自留 1MB 请求体上限、`{ ok:false, code, message }` 错误包装与 200 语义不变）；③ `connection` 经 `ctx.inject(['connection'], cb)` 可选注入（服务缺席不 pending，仅 Client API 降级不可用），并用 `cb.effect` 包裹 register 返回的**异步 disposer**——注册本体挂在 connection 插件 fiber 的 effect 上（`owner = this.ctx`），不包会在 HMR 重载时撞「exact Fetch route ... is already registered」。web 端由 client-connection 把 `/api` 挂到 webServer 之下、桌面端由 dsh-desktop-host 以 `createSharedFetchHandler('/api')` 直接分发，客户端 URL 与方法两端完全一致，**客户端 bundle 零改动**。契约类型按官方 `.d.ts` 换为 `ConnectionFetchRoute` / `HostConnectionFetch`（HttpRequest/HttpResponse/WebRoute/WebServer 删除）；`verify:host` 门禁改 connection 桩并新增路由形状（methods/requestBody/fetch）、exact 分发 404、异步 disposer 清零断言；`compat-audit` 新增 I32、`dsh-contract.md` 与 AGENTS.md 同步。插件功能与 `lib/client.js` 零变化。

## [2.3.12] - 2026-09-11

### 变更

- **dsh 0.1.5-rc.2 兼容性声明与台账同步**：全局实装 `@deepseek-ai/dsh@0.1.5-rc.2`（tag `dsh-v0.1.5-rc.2`，commit `fb2c4b9`，npm dist-tag `next`）后跑三层门禁——`test:probe` 31/31、`verify:host` 装配断言通过、`npm test` 307/307；rc.1→rc.2 的 4 commits / 300 文件按插件消费面包与类型源过滤后唯一命中 ui-chat 的 `TurnTailNodeView.module.css`（+3 行纯 CSS 间距），`sessions.d.ts`/`slots.d.ts`/`slot-contract.d.ts` 等类型源零改动；reference/ 镜像按 rc.2 tag 重拉 13 源（12 份内容相同、11 号仅 CRLF 噪声）。`dsh.compatibility.dshReleases` 补 `0.1.5-rc.2`，README 双语兼容声明、镜像索引与契约文档版本字段同步。peer 范围沿用按 minor 线开窗（`>=0.1.5-alpha.1 <0.1.6`），npm semver 的 prerelease 门槛天然放行同 tuple 的 rc.2，无需改 peer 串。评估实证沉淀于 `docs/upgrade-assessments/dsh-0.1.5-rc.2.md`，本插件源码零功能变更。

### 修复

- **DSH Desktop（0.1.5-rc.2）安装插件被 `validateDesktopPluginGraph` 拒绝**：桌面端 profile 固定 `autoInstallPeers: false`，安装时逐个解析 `package.json` 的 peerDependencies 并要求实体包存在于 profile 的 node_modules。插件声明的 `@deepseek-ai/dsh-client-web-react` 是 dsh 0.1.0 时代的历史包（npm 最高 0.1.0-rc.7），官方 0.1.1+ 已不再发布、0.1.5-rc.2 的宿主共享包中不存在，故报 `requires missing @deepseek-ai/dsh-client-web-react`。该声明早已无运行时意义：`lib/client.js` 唯一的模块请求是 `require("react")`，React 由宿主平台基线模块表提供。修复三处：① 删除该历史 peer；② 保留 `react` peer 但补 `peerDependenciesMeta.react.optional`——桌面端 profile 顶层无 `react`，不标 optional 会在下一轮校验继续报 missing；③ 删除过时的 `dsh.client.inject`（新机制下是包名依赖边，指向已不存在的包会被宿主静默跳过；`dsh.client.platform: "web"` 保留，宿主据此识别 client 半侧的运行平台）。其余 8 个 peer 及其范围不变。`dsh.compatibility.dshReleases` 补 `0.1.5-rc.2`，README 双语安装兼容声明同步。插件功能与运行时零变化，`lib/` 产物结构与 2.3.11 相同。

## [2.3.11] - 2026-09-11

### 变更

- **dsh-* peerDependencies 从「逐 tuple OR 窗口」收敛为「按 minor 版本线开窗」**：7 个 `@deepseek-ai/dsh-*` 的 peer 范围由原先每个已核验版本一段的长 OR 串（`>=0.1.1-rc.2 <=0.1.3-alpha.1 || … || >=0.1.5-rc.1 <=0.1.5-rc.1`）改为每条 minor 线一段、上界开区间到下一 minor（`>=0.1.1-rc.2 <0.1.2 || >=0.1.2-alpha.1 <0.1.3 || >=0.1.3-alpha.1 <0.1.4 || >=0.1.5-alpha.1 <0.1.6`）。动机：原形态下 dsh 每发一个 prerelease（alpha.2→rc.1→…）都要人工追加一段，繁琐且易漏。新形态利用 npm semver 的 prerelease 门槛——同 (major,minor,patch) 段内只要有一个带 prerelease 的比较器，即放行该 tuple 的全部 prerelease 与正式版，故 0.1.5 线内的 rc.2、0.1.5 正式版等自动放行，无需再改 peer 声明；未验证的新 minor 线（0.1.6、0.2.0）仍被上界拦截。取舍：同线内未经门禁核验的新版本也会被 npm 放行安装，安全兜底从「peer 范围逐版白名单」转为「升级后跑 `check:upgrade` 三层门禁 + `check:dsh` 哨兵」——即 dsh 发新版后仍需人工核验，只是不再强制改 7 条 peer 串。`scripts/check-dsh-version.mjs` 的复合区间解析器原生支持 `>=x.y.z-pre <x.y.z` 形态，实跑验证放行/拦截语义正确（0.1.5 线全放行、0.1.6 起拦截），脚本零改动。`dshReleases` 兼容矩阵保持逐版本记录不变（它是台账、非安装门禁）。README 双语安装兼容声明同步。

## [2.3.10] - 2026-09-10

### 变更

- **dsh 0.1.5-rc.1 兼容性声明（peer 范围扩窗）**：本地全局实装 `@deepseek-ai/dsh@0.1.5-rc.1`（npm dist-tag `latest`/`next`，0.1.5 系列首个候选版本，tag commit `183f08e`）后跑三层门禁——`test:probe` 31 项探针全绿、`verify:host` 装配断言通过、`npm test` 307 项通过、`check:dsh` 镜像/契约漂移与 peer 越界已消除。`package.json` 的 `dsh.compatibility.dshReleases` 矩阵补 `0.1.5-rc.1` 为 `compatible`，7 个 `@deepseek-ai/dsh-*` 的 `peerDependencies` 范围沿 2.3.8/2.3.9 先例补 `>=0.1.5-rc.1 <=0.1.5-rc.1` 逐 tuple OR 段。契约核验：隔离安装 alpha.2 全量依赖树与全局 rc.1 内嵌包做 tree-SHA 比对，插件消费面 12 个包（`dsh-session`、ui-chat/ui-conversation `slots.d.ts`、session-controller `sessions.d.ts`、settings-plugins `slot-contract.d.ts`、`dsh-settings`/`dsh-shell`/`dsh-session-query`/`dsh-host-webserver`/`dsh-sandbox-policy`、`cordis`、`schemastery`）目录树哈希全部逐字节相同——rc.1 相对 alpha.2 是纯发布层推进，无契约变化。release notes 为 v0.1.2-rc.1 以来的汇总，三项开发者 API 调整（移除 `ctx.agent` 单数、`Inbox` 改 type-only、Web 面板 `conversation`→`main.conversation`）均已在 alpha.1/alpha.2 逐项排除，rc.1 无新增契约点。官方文档镜像按 rc.1 tag 重拉核验：13 源与 alpha.2 归档内容零差异。本插件源码零功能变更（仅 peer 声明 + 文档），`lib/` 产物与 2.3.9 相同。评估实证沉淀于 `docs/upgrade-assessments/dsh-0.1.5-rc.1.md`，兼容性台账（`docs/compat-audit.md`）、契约文档（`docs/dsh-contract.md`）、官方文档镜像索引（`docs/reference/README.md`）与 README 安装兼容声明已同步。

## [2.3.9] - 2026-09-10

### 变更

- **dsh 0.1.5-alpha.2 兼容性声明（peer 范围扩窗）**：本地全局实装 `@deepseek-ai/dsh@0.1.5-alpha.2`（npm dist-tag `alpha`，tag commit `b2e3b2a`）后跑三层门禁——`test:probe` 31 项探针全绿、`verify:host` 装配断言通过、`check:dsh` 镜像/契约漂移与 peer 越界已消除。`package.json` 的 `dsh.compatibility.dshReleases` 矩阵补 `0.1.5-alpha.2` 为 `compatible`，7 个 `@deepseek-ai/dsh-*` 的 `peerDependencies` 范围沿 2.3.8 先例补 `>=0.1.5-alpha.2 <=0.1.5-alpha.2` 逐 tuple OR 段。契约逐项 diff 零破坏：全局实装包 tree-SHA 比对 alpha.1↔alpha.2，插件消费的类型源（`dsh-session` types、ui-chat/ui-conversation `slots.d.ts`、session-controller `sessions.d.ts`、settings-plugins `slot-contract.d.ts`）全部未变。本次唯一需深挖项「Web 插件面板 API 调整——原 `conversation` Slot 迁移为 `main` 的 `conversation` key」逐一排除：顶层布局槽位确已改名（`ConversationSlotProps = PropsRuntime<'main.conversation'>`），但插件从不注册顶层 `conversation` slot，只用 `conversation.chat.node`（keyed/session，路径不变）与 `settings.plugin.item`（不变）；`ctx.get('conversation')`（refillDraft 回填）是服务访问（`InputHub`）非 slot，与被改名槽位是两回事。新增持久事件 `deliverables/presented`（模型显式文件交付，`dsh-tool-present` declaration merging）插件零消费。其余变更（Sidebar 文档预览、`/feedback` 明细、pi-ai 诊断、Base URL 校验、文件夹选择器、Composer 占位、子代理工具指导、MCP 分页、`fs-ext` 免编译、minimal 默认工具、设置本地化）均与撤回链路零交集。本插件源码零功能变更（仅 peer 声明 + 文档），`lib/` 产物与 2.3.8 相同。评估实证沉淀于 `docs/upgrade-assessments/dsh-0.1.5-alpha.2.md`，兼容性台账（`docs/compat-audit.md`）、契约文档（`docs/dsh-contract.md`）、官方文档镜像（`docs/reference/`，仅 09-architecture.md 一行措辞变化）与 README 安装兼容声明已同步。

## [2.3.8] - 2026-09-09

### 变更

- **dsh 0.1.5-alpha.1 兼容性声明（peer 范围扩窗）**：本地全局实装 `@deepseek-ai/dsh@0.1.5-alpha.1`（npm dist-tag `alpha`）后跑三层门禁——`test:probe` 31 项探针全绿、`verify:host` 装配断言通过、`check:dsh` peer 越界已消除。`package.json` 的 `dsh.compatibility.dshReleases` 矩阵补 `0.1.5-alpha.1` 为 `compatible`（并修正 2.3.7 遗漏的 `0.1.3-alpha.2` 条目），7 个 `@deepseek-ai/dsh-*` 的 `peerDependencies` 范围沿 2.3.4 先例补 `>=0.1.5-alpha.1 <=0.1.5-alpha.1` 逐 tuple OR 段。契约逐项 diff 零破坏：本次三项高风险变更逐一排除——① 移除 `ctx.agent`（单数）不影响插件（插件只用 `ctx.agents` 复数注册表）；② `Inbox` 改 type-only 插件零引用；③ **会话格式 V3**（V2→V3 迁移插入 `system/message` 事件并 remap seq，但保留原始 message id）——插件天然免疫：读取全走官方恢复后内存态（seq 坐标系与 `fork({atSeq})` 同源）、消息定位以 `data.id` 为主键、`cutSeqCache` 内存态不跨版本。事件全集 51→54 种（新增 `system/message`、`feedback/message-put`/`message-delete`，`tool/code-dispatch*` 更名 `tool/ptc-dispatch*`）为备忘面同步，`src/types/dsh-contract.ts` 事件 union 与 `docs/dsh-contract.md` §四随之更新，插件 `scanCutSeq` 只扫 `user/message`+`turn/end` 零交集。本插件源码零功能变更（仅类型备忘 + peer 声明 + 文档），`lib/` 产物与 2.3.7 相同。评估实证沉淀于 `docs/upgrade-assessments/dsh-0.1.5-alpha.1.md`，兼容性台账（`docs/compat-audit.md`）、契约文档（`docs/dsh-contract.md`）、官方文档镜像（`docs/reference/`，05/09/13 三份随官方文字修订重拉）与 README 安装兼容声明已同步。

## [2.3.7] - 2026-09-08

### 变更

- **dsh 0.1.3-alpha.2 兼容性声明（peer 范围扩窗）**：本地全局实装 `@deepseek-ai/dsh@0.1.3-alpha.2`（npm dist-tag alpha）后跑 `npm run check:upgrade` 三层门禁全绿（check:dsh 漂移一致 + test:probe 31 项探针 + verify:host 装配断言）；`package.json` 的 `dsh.compatibility.dshReleases` 矩阵补 `0.1.3-alpha.2` 为 `compatible`，7 个 `@deepseek-ai/dsh-*` 的 `peerDependencies` 范围沿 2.3.4 先例补 `>=0.1.3-alpha.2 <=0.1.3-alpha.2` 逐 tuple OR 段（npm semver prerelease 规则要求同 tuple 比较器才放行该 prerelease 线）。契约逐项 diff 零破坏（fork/sessionQuery/chat.node 槽位/settings 槽位/shell.resolve/session-event 域均不变），alpha.1 已知的冷会话性能回退（v1→v2 迁移全量内存物化）已被官方迁移流式化修复，插件冷读路径直接受益，无需任何代码改动——本插件源码零变更，`lib/` 产物与 2.3.6 相同。评估实证沉淀于 `docs/upgrade-assessments/dsh-0.1.3-alpha.2.md`，兼容性台账与契约文档已同步。

## [2.3.6] - 2026-09-06

### 修复

- **修复宿主启动预热的 subprocess 竞态（消除启动噪音）**：预热是唯一在 apply 期就执行 shell 命令的路径，cordis 按 fiber 逐个注入，recall 可能先于 pwsh-sandbox 的 subprocess 注入链完成，`rebuildOrphans` 的 git 命令随即命中「cannot get required service "subprocess" in inactive context」，每次重启刷一条 `recall rebuildOrphans failed`。修复：预热前置短轮询探活（纯编码 prelude 常量、无副作用，10s 窗口），执行器就绪后再跑预热链；超时未就绪则放弃预热——与原先吞错语义一致，只是不再报错刷屏。

## [2.3.5] - 2026-09-06

### 修复

- **规避 DSH-Store 保护性权限信号（issue #517 跟进）**：官方 slot #46「工具调用视图」的 slot 名连续字面量触发 DSH-Store catalog 自动化的 `protectedDsh` 权限信号——其静态扫描对固定 Commit 全部源码文件逐一匹配五个触发模式，该字面量导致插件被标「更新暂缓/下架」，与 peer 范围无关。修复：`src/types/client-contract.ts` 的 slot-id 联合类型该成员改写为 template literal type（`` `tool.call.${'toolview'}` ``）——类型层面与字面量完全等价（可收窄/赋值），源码文本不再含连续子串，五个触发模式全数解除；此类型仅备忘官方 slot 清单、零运行时携带，lib 构建产物无变化。推送新固定 Commit 后 DSH-Store 每 8 小时自动复检。

## [2.3.4] - 2026-09-06

### 变更

- **DSH-Store 兼容性声明补全（issue #517）**：`package.json` 新增 `dsh.compatibility.dshReleases` 矩阵，将 `0.1.2-alpha.1`～`0.1.2-alpha.5`、`0.1.2-rc.1`、`0.1.3-alpha.1` 七项标记为 `compatible`；同步把 7 个 `@deepseek-ai/dsh-*` 的 `peerDependencies` 范围改为逐 tuple OR 窗口 `>=0.1.1-rc.2 <=0.1.3-alpha.1 || >=0.1.2-alpha.1 <=0.1.3-alpha.1 || >=0.1.3-alpha.1 <=0.1.3-alpha.1`——npm semver 的 prerelease 规则要求存在「同 (major,minor,patch) 且自身带 prerelease」的比较器才放行 prerelease 候选，单段长区间（含 `^0.1.1-rc.2` 的展开式）无法代表 `0.1.2-alpha.x` 系列，DSH-Store 因此判 0.1.2 线 peer 越界；逐 tuple 分段后各 prerelease 线各自放行，上界收窄至矩阵最后声明版 `0.1.3-alpha.1`（`<=` 含入），未验证的 `0.1.3` 后续与 `0.2.x` 全线均在窗口外。`scripts/check-dsh-version.mjs` 补齐 `||` 多段解析（`parseRangeSet`）、复合区间 `<=` 上限与 prerelease 门槛（同 tuple 校验——单段长区间不再放宽放行未验证 prerelease 线），替换此前对复合区间的「只认 ^ / ~ / 精确」警告，配套单测补齐 parseRange / parseRangeSet / satisfiesRange / buildReport 四层断言。

## [2.3.3] - 2026-09-05

### 修复

- **设置页风格对齐 DSH 官方插件设置卡**：以本机官方 `dsh-client-ui-settings-plugins` 构建产物为事实源，对卡片描边与圆角、次级/主/危险按钮、输入框、状态徽章、布尔开关（checkbox → role=switch 滑钮）、焦点环、折叠头箭头动效逐部位对齐官方配方，说明文字统一辅助色；「高级：基础排除表」更名为「基础排除表」。全部配色走官方 `--dsw-alias-*` 主题令牌（逐一核验），纯展示层调整，无行为变化。配方沉淀见 `docs/design-tokens.md`。

- **设置页三个折叠区展开态去冗余**：① 删除与折叠头重复的内标题（「快照管理」卡内大标题、「快照排除项」卡内标题）——折叠头已承担分区标题角色，展开后再出现同名标题是纯噪音；② 「基础排除表」展开后 textarea 与说明文字改为通栏——此前只占共享 grid 第二列，左侧长标签列形成竖直死区、编辑框被挤窄；label 保持原位（htmlFor 关联是可访问性契约）；③ 排除配置的存储路径从长句中抽出为独立等宽小字行（break-all 整齐折行），说明正文不再被 Windows 长路径撑出断裂换行，多文件场景（降级工作区）也靠路径行区分。纯展示层调整，无行为变化。

- **设置页配置表单排版再打磨（六点位）**：① 数字输入框从「撑满整行」改为定宽 120px + 数值右对齐，单位（条/小时/MB/天）与状态标签（已修改/已覆盖/环境变量锁定）统一挂在输入框右侧同基线——此前输入框宽度随行内标签有无伸缩、各行右缘参差；② 主标签升为正文字色（label-primary），说明文字维持 12px tertiary 固定在控件下方第二行，主辅层级拉开；③ 五个数字字段补齐单位后缀（此前仅 MB）；④ 「快照行为 / 自动治理」组间加分隔线与加倍留白，组界不再只靠小标题；组内行距统一为 12px；⑤ 「保存」升主色实心按钮（配色经官方主题产物核验：`button-primary-fill` / `label-primary-foreground` / `button-primary-hover`），「放弃修改 / 恢复默认」维持次级灰底；⑥ 三个折叠头（基础排除表 / 排除配置 / 快照管理）的箭头字形盒内改左对齐，与上方表单标签共享同一左缘。纯样式与文案层级调整，无行为变化。

- **设置页数字输入框隐藏原生加减微调按钮**：「自动治理」五个数字输入框为 34px 定宽，原生 spinner（上下箭头）跨引擎渲染不一（Chromium 有 / Firefox 无）、挤占右侧数字区，与「数值右对齐」的纵向扫描相冲——隐藏原生 spinner（`-webkit-appearance:none` + `appearance:textfield`，scope 到 `.dsh-recall-cfg-input` 不外溢），键盘 ↑↓ 微调与直接输入能力保留。纯样式调整。

### 变更

- **新增 `docs/design-tokens.md` 设计令牌与组件配方参考**：把本次对齐所核验的官方事实源（`dsh-client-ui-theme` 令牌定义、`dsh-client-ui-settings-plugins` 组件配方）沉淀为长期规范文档——令牌命名空间说明、颜色/排版速查（含本插件使用处映射）、八类官方组件配方摘录（卡片/按钮组/表单字段/badge/switch/分组折叠/分隔线规格/阴影）、本插件落地约定与 dsh 升级后的重抽比对流程。后续改 client UI 先查此文档，避免凭印象写颜色与字阶。

- **`reference/` 官方文档镜像整合进 `docs/`**：目录 `reference/` → `docs/reference/`（镜像随仓库提交），官方文档镜像与项目文档收敛到同一目录；同步更新 AGENTS.md 合规清单与漂移控制节、`scripts/check-dsh-version.mjs`（镜像读取路径改为 `docs/reference/README.md`）、`scripts/check-upgrade.mjs` 提示文案、package-layout 单测禁入清单（`docs/` 已覆盖镜像）、`docs/dsh-contract.md` 升级指引、compat-audit 台账 I6 复查动作的镜像路径与 docs/README.md 目录索引；compat-audit 头部与各计划文档中的历史核验记录按当时原文保留。

## [2.3.2] - 2026-09-04

### 修复

- **设置页表单跨行对齐修复（V4 跟进）**：配置表单的勾选框（快照行为三行）与数字输入框（自动治理五行）此前各行的 label 列宽独立计算、控件列参差——根因是每行 `.cfg-row` 是独立 grid 容器，V4 的「第一列 `max-content` 自适应最长标签」只在同一 grid 内生效，跨行从未成立。现引入单一共享 grid（`.cfg-grid`）包裹全表单，`.cfg-row` 以 `display:contents` 透明化，label/控件/hint 直接参与同一网格，控件列由全表单最长 label 统一对齐；分组小标题占满整行；行间节奏（原 flex gap 8px）由 hint `padding-bottom` 补偿保持。同时：三个折叠头（高级基础排除表 / 排除配置 / 快照管理）去掉卡片头样式的 16px 左内边距、与表单内容贴左对齐；配置操作区（放弃修改 / 恢复默认 / 保存）移到「高级：基础排除表」折叠头之前——按钮服务整个表单，排在折叠头之后会被误读为折叠区内容。纯布局修复，无行为变化。

- **设置页基础排除表标签关联补漏（V2 跟进）**：「高级：基础排除表」textarea 补 `id` 并与 label 以 `htmlFor` 关联（V2 只覆盖了 numRow 与 exclude.txt 三处，此为同款漏网），读屏可正确播报标签。

- **设置页确认条统一与过渡动效（V9）**：四种删除确认（快照 / 会话 / 工作区 / 全部）收敛为同一 `ConfirmRow` 组件，「确认」统一为危险红 chip、「取消」为普通 chip；确认条与树展开增加 opacity 入场过渡（≤200ms），并尊重系统「减少动效」设置自动关闭。纯交互打磨。

- **设置页响应式补全（V8）**：exclude 快速添加行在 ≤480px 下输入框独占一行、按钮与建议芯片换行排列；操作区按钮行加 `flex-wrap:wrap`，长状态/错误文案不再挤压按钮。与 V4 共用同一 480px 断点。纯样式增补。

- **设置页排版收敛（V7）**：字阶统一为 15/14/13/12 四级（辅助 meta 与标签的 11px 升 12px）；SectionToggle 标题内联样式收敛为 class；全量 line-height 改无单位写法（1.4/1.5）；声明间距刻度变量 `--dsh-recall-space-1/2/3`（4/8/12）。纯样式整理，无行为变化。

- **设置页表单分组与危险操作固定位（V5）**：配置表单 9 字段平铺改为「快照行为 / 自动治理」两组语义分组（带小标题），「高级」维持折叠；快照管理操作区「全部删除」固定为最后一个按钮（刷新 / 立即 gc / 全部删除），不再随「加载更多」出现而漂移。纯结构重排，无行为变化。

- **设置页表单布局 Grid 化（V4）**：配置表单从「label 定宽 130px + hint 缩进 138px」魔法数对齐改为 CSS Grid（label 列 `max-content` 自适应最长标签，控件/hint 归第二列）；≤480px 窄面板下表单纵向堆叠不挤压；快照树缩进量化为 `--dsh-recall-tree-indent` 单一变量（原来 16+8 恰好等于折叠钮 18+6 的巧合）。纯布局重构，无行为变化。

- **设置页健康徽章化与错误区升级（V6）**：快照管理卡片的 git 状态从灰色文案升级为彩色 pill（可用 = 绿 / 不可用 = 红，官方状态行令牌配对），成为卡片顶部横幅；「最近错误」区标题改错误红 + 条数徽章，并从卡片最底部上移到操作区之上——致命状态与历史错误更醒目。纯展示层重排。

- **设置页交互反馈（V3）**：禁用按钮/芯片补 `opacity:.5` + 默认光标，且禁用态 hover 不再变色（未修改时的「保存 / 放弃修改」不再误导）；三张卡片（配置/排除/快照管理）的成功状态消息 4s 自动消退、错误提示常驻、busy 中不消退；打开「快照管理」先见 pulse 加载骨架再出树，不再是一段空白。纯交互反馈增补。

- **设置页可访问性补强（V2）**：快照树折叠钮从纯视觉 `<span>` 升为 `<button>`（Tab/Enter/Space 可达），配 `aria-expanded`/`aria-label`；数字输入 label/input 按 id 关联；三处状态消息加 `role="status"` + `aria-live="polite"`、错误文案补「错误：」文字前缀；三个无标签控件（快照搜索/排除编辑/快速添加）补 `aria-label`；全按钮与输入框补 `:focus-visible` 焦点环（官方 border-l3 惯例）。键盘完全不可达的树折叠是本次最重的功能性缺陷，纯增量修复。

- **设置页语义色修复（V1）**：ExcludeCard「保存」按钮去掉危险红样式、与配置表单保存按钮统一；快照文件清单「已修改」badge 从错误红改为 warning（amber）色系——修改不是错误；配置表单三类状态标签分化（「已修改」warn 底色 /「已覆盖」中性灰 /「环境变量锁定」中性灰 + 左边框）；`btn-danger` 前景色硬编码 `#fff` 换为官方实证的主题感知令牌 `--dsw-alias-bg-layer-3`，hover 亮度集中为 css.ts 顶部语义变量，消灭令牌体系外散落补丁。纯样式改动，无行为变化。

### 变更

- **源码整体迁移 TypeScript（同形态复刻）**：host/client 源码迁入 `src/`（`src/host/` 13 文件 + `src/client/` 6 文件 + `src/types/` 类型库，跨域契约以编译期类型锁死），`lib/` 转为纯构建产物目录——`npm run build` 经 esbuild 逐文件转译 host 产物 + 打包 `lib/client.js`；功能与对外契约零变化（npm 包布局、`main`/`exports`/`files`、`cordis.patch.yml`、端点/探针/装配门禁均不变），行为一致性由类型检查 + 既有回归网双保险。新增 `npm run typecheck`（CI 类型门禁）与统一产物新鲜度校验。

## [2.3.1] - 2026-09-01

修复批次（PR #13 贡献，macOS 实机验证；Windows 侧本仓库复核）。单测 285 → 290 例全绿（新增 `nextShadowPriority` 5 例）、`test:probe` 探针 29 → 31 例全绿（新增 `slots.entries` 双包探测）、`verify:host` 装配门禁全绿。

### 修复

- **兼容 dsh-turn-fold 的用户消息渲染槽位**：`conversation.chat.node` 的 `user` key 改为在槽位回调实际执行时读取已占用的 priority，并自动选择当前最低 priority 再低一级，避免与 `dsh-turn-fold` 等第三方插件固定占用 `-1` 时发生冲突；其他插件继续占用更低 priority 时也会自动避让。
- **POSIX diff awk 程序多余点号导致预览/回退失败**：`lib/scripts.posix.js` 的 `diffScript` 中 awk 程序起始引号后混入多余 `.`（v2.2.0 随 PF-1 引入），awk 报语法错误退出非零，macOS/Linux 上点「撤回」预览即失败、无法回退；Windows 走 pwsh 路径不受影响，故此前仅 Windows 冒烟未暴露。

### 变更

- **dsh 升级一键核验门禁 `npm run check:upgrade`**：串联 check:dsh + test:probe + verify:host 三层机器化核验，输出后提示在 compat-audit.md 头部追加核验记录——dsh 升级后一条命令替代手动三步。
- **compat 台账复查方式增强**：I1/I3/I4/I5/I7/I9/I12/I18/I20 复查动作补官方产物证据链（读哪个包哪个文件、断言什么），修正 I1 与 I29 的 priority 语义脱节（guard 强制分配 shadowing priority）、I5 补「context 键已评估无害」结论；I9/I15/I19/I22/I23 出处标注「非官方耦合」（项目内约定，dsh 升级复查不涉及）。
- **字段探针补盲 + 负向断言**：fork 探针改双包探测（原指向已删除的 dsh-client-runtime，alpha.3 下静默零断言）+ atSeq/increaseTitle 严格可选负向断言；settings RPC 探针迁至 dsh-api-settings-controller（原 dsh-host-apiproxy 已删除）；新增 I1/I3/I4/I7/I8/I9/I12/I18/I20 断言与 ConversationNode 节点集合断言——官方新增投影 kind / 收紧可选参数 / 删除字段即红。探针 29 例全绿。

## [2.3.0] - 2026-08-31

修复批次：适配 dsh 0.1.2 / 0.1.2-alpha.2（兼容 0.1.1-rc.2）。单测 285 例全绿，`verify:host` 装配门禁、`check:dsh` 巡检、`test:probe` 探针全绿；新旧两版 DSH（0.1.1-rc.2 / 0.1.2-alpha.2，Windows）实弹验证通过——撤回按钮、设置页「撤回插件」卡片、快照删除均正常。

### 修复

- **dsh 0.1.2-alpha 下撤回按钮与设置卡片全部消失（I29）**：0.1.2 的 client 服务层大迁移（`client/runtime` 包删除，slots 迁入 ui-renderer、sessions/workspaces 迁入新增的 api 包）后，插件 fiber 未声明 inject 时 `ctx.get('slots')` 解析不到服务、静默返回 undefined，apply 首行 `if (!slots) return` 即退出：CSS 不注入、slot 全部不注册、entry 仍 active（无任何报错，症状为「Host 活 Client 死」）。修复：client 插件对象声明 `inject: ['slots', 'sessions', 'workspaces', 'timer']` 并改为 `ctx.<name>` 属性访问（styles 服务 0.1.2 已移除，保留 `ctx.get` 可选探测 + `<style>` 降级）。**双版本兼容**（0.1.1-rc.2 ↔ 0.1.2，cordis 4.0.1 实测）：`conversation` 服务 0.1.2 才存在，静态声明会让 0.1.1-rc.2 上插件「声明未满足」静默不启动（UI 全灭），故不进 inject，统一走 `ctx.get('conversation')` 探测 + 降级（0.1.1-rc.2 上回填输入框功能本来就不存在；0.1.2 主流程不受影响）。同批受影响的第三方插件可参照 compat-audit I29 自查。
- **dsh 0.1.2-alpha.2 适配（I30 + 事件 ignorable 恢复）**：① Host settings 接入路径破坏性变更——独立函数 `installSettingsSection` 被官方移除、改为 `SettingsProvider.installSection` 方法（bash-local/pwsh-local 同款迁移），静态 import 会让插件 Host 半启动即崩（`SyntaxError: does not provide an export named 'installSettingsSection'`，`/api/recall/*` 全 404）。修复：`lib/index.js` 改命名空间导入 + 双版本兼容分支（旧版走独立函数、新版走 `ctx.inject(['settings'])` + `installSection`），verify-host 桩补 installSection。② 事件信封 `ignorable?: true` 在 alpha.2 恢复（alpha.1 曾移除改 fail-closed）——插件只扫 `user/message` + `turn/end`、不读 ignorable，无影响，契约文档同步。③ `conversation.chat.node` 声明包定位更新（ui-chat），`test:probe` 探针改双包探测，17/17 全绿。
- **快照管理「已删除但列表仍在」**：列表/批量删除按 index.json 条目里的 root 匹配，但历史坏数据曾在写入时丢失路径反斜杠（如 `D:workspacedsh-plugin冒烟测试工作区`，正确应为 `D:\workspace\dsh-plugin\冒烟测试工作区`）——store 目录 hash 按正确 root 计算，删除按坏 root 解析到**空 store**，真快照 tag 一个没删，却仍返回匹配条数的 deleted 计数（用户看到「已删除 N 条」但列表原样）。修复：列表构建与批量删除的 root 来源**优先取 root.txt 权威值**（`resolveStore` 每次写入），index.json 条目 root 仅作兜底——三处读取点（`buildListItems` / `collectAllSnapshotRecords` / `locateSnapshotOnDisk`）同步修正；磁盘上重复 hash 的坏 store 一并消失，实弹验证删除真正生效（33 条全删、列表从 36 降到 2）。
- **旧版 0.1.1-rc.2 下设置页不显示「撤回插件」卡片（撤回功能正常）**：插件 node_modules 里 `@deepseek-ai/dsh-settings` 固定为最新版（0.1.2-alpha.2，只有 `installSection` 方法），旧版 DSH 注入的却是旧版 settings 实例（仅 `register` 核心 API）——兼容分支只看静态导入包判断（`installSettingsSection` 不存在 → 走 `ctx.inject` + `installSection`），旧版实例无此方法抛 TypeError，被 catch 静默吞掉（仅进 `recordError` 环形缓冲，`/api/recall/status` 可查），namespace 从未注册、设置卡片缺失。修复：`lib/index.js` 设置接线改为**按运行时注入实例的实际 API 分派**——`installSection` 方法（0.1.2-alpha.2）或 `register` 核心 API（0.1.1-rc.2 及以前，手动复刻独立函数接线语义：注册 namespace、源指向 scope、卸载回退入口 config、watch 热更新）。旧版实弹验证：`/api/recall/status` errors 为空，设置页「插件配置」出现完整「撤回插件」卡片（快照开关/gc 阈值/文件上限/回填/归档/排除表/快照管理）。

## [2.2.1] - 2026-08-30

快照管理消息内容显示修复（patch）。单测 283 → 285 例全绿，`verify:host` 装配门禁、`check:dsh` 巡检全绿。

### 修复

- **冷会话快照只显示消息 ID（1.5.0 引入）**：manage list 的去重补全分支把 live 未命中的 `null` 写进 `messageText` 属性，client 凭「属性存在」判定「已查过」而跳过 messages 端点冷读——冷会话（已关闭、不在 live 注册表）的快照永远显示消息 ID 截断而非消息内容（同会话标题因 titles 链按 falsy 重查而不受影响）。改为 live 命中才写字段，与首次入库分支的既有纪律对齐；修复后冷会话快照的消息内容经 messages 端点冷读渐进补齐（同会话多条共享一次 readSession，确认无文本的缓存 null 不重复解压）。

## [2.2.0] - 2026-08-30

性能优化批次（[plan-performance.md](docs/plans/completed/plan-performance.md) PF-1〜PF-9 全项，2026-08-29 实施，同日实弹冒烟 9/9 通过）。API 形状与用户可见语义基本不变（PF-6 删除以「所见为准」、PF-1 校验更严两处行为变化见下）；单测 227 → 283 例全绿，`verify:host` 装配门禁、`check:dsh` 巡检、client 产物新鲜度全绿；合成基准同口径对比：快照管理首开 -70%、对话中二次打开免等待、同进程二次 init ≈0、单条删除 -21%、每条消息快照 -14%。

### 新增

- **预览指纹校验（PF-1，行为变化：校验更严）**：diff/快照脚本输出 `TREE <hash>`（add -A 后 index 树指纹），preview 随清单回传、client 确认时透传——execute 与安全快照指纹比对即知「预览后文件是否变化」，从「条目总数一致」升级为「内容一致」，且一次撤回少跑一整条重复 diff 进程（4 → 3 条重脚本）。老 client 的 `previewTotal` 条目数校验保留为兼容路径。
- **manage list stale 渐进刷新（PF-6，行为变化：删除以所见为准）**：每条消息快照不再清空列表缓存而是标 stale——对话中打开快照管理立即以旧列表应答（带 `stale` 字段），后台 dump 补新（in-flight 去重），client 静默二段刷新一次；批量删除在缓存非空时以「用户当前所见」的列表为准构造删除范围。

### 变更（性能等价重构，API 形状不变）

- **win32 文本写入改 stdin 单进程（PF-2）**：index.json/lineage.json/exclude.txt/root.txt 落盘从 base64 20000 字符分块（每块一条 PowerShell 进程，索引几百条时 saveIndex 6+ 条）改为 stdin 传全文 + 单进程；POSIX 的内联 cat 一并收进模板同名导出。读取手法由运行时探针钉死（`[Console]::OpenStandardInput()` 字节流——`Console.In` 在 PS 5.1 按输入代码页 GBK 解码 UTF-8 stdin 必挂，且本机 dsh 执行器实际解析到 PS 5.1：pwsh 别名 appexeclink 在 lstat 视角不存在）。
- **全量枚举换 .NET 手动栈遍历（PF-3）**：超大文件剔除（snapshot/diff/rollback 三脚本各一次，一次撤回共 4 次）与磁盘占用统计改 `Stack[string]` + 逐目录 `EnumerateFiles` + try/catch——.NET 4.x 的 `AllDirectories` 遇 ACL 异常目录中断整个枚举，手动栈才能与 `SilentlyContinue` 逐项容错对齐；几万文件的工作区从数秒级降亚秒。usage 端点多 store 并行（runLimited 4）+ 30s TTL 缓存（删除/gc 后失效）。
- **lineage 并入 storesDump（PF-4）**：`==DIR` 段内新增 `LINEAGEBEGIN/原文/LINEAGEEND`（与 INDEX 段同构，解析容错），manage lineage 从「每 root 串行一条进程（20 工作区 ≈ 10s）」降为零新增进程。
- **rebuildOrphans 四档守卫（PF-5）**：索引终态分级（healthy / empty / quarantined / truncated）——healthy 且非空、truncated 时整体跳过重建（顺带根治现有隐患：读截断后残缺内存视图会被无条件 rebuild 用孤儿集覆盖完好大索引）；`cleanupLegacy` 加内存标记（同 root 多次 init 只付一条进程）。init/预热常态每 root 省 1+N 条进程，同进程二次 init 零进程。
- **sweep 换 listSessions（PF-7）**：gc 前的已删会话扫描从逐会话 `readSession` 全日志解压（串行队列内，会话多时堵住快照/撤回）改为一次 `listSessions()` 目录枚举建 id 集合（I8：记录 id 在 header.id）；判定更保守（日志损坏但文件在的保留，purge 不可逆宁可少清）。titles 冷读维持现状——探针确认 `SessionHeader` 无 `title` 字段，titles 半项废弃（负向探针钉住，官方未来加 title 时提示可重启该优化）。
- **exclude-get 探测链合并（PF-8）**：全部 exclude 文件一条脚本 base64 读取（任意用户文本免疫定界混淆），首开进程链 4-6 条 → 2 条。
- **快照脚本瘦身（PF-9）**：exclude 同步条件化（内容未变跳过重写与清理循环，每条消息常态省 1 次 git 子进程 + 1 次盘写，改排除即时生效不变）；update-index 逐条调用合批（pwsh 100 条/批、POSIX xargs -0 自适应），大排除/多超大文件场景子进程 N → N/100。

### 修复

- **后台刷新与 dump 失败留痕**：`refreshListCacheInBackground` 与 `dumpStores` 失败原先被 silent catch 完全吞掉（实弹冒烟中曾出现一次约 29 分钟列表 stale 未自愈且零日志可查），现补 console 留痕——复发时看「recall list refresh failed」/「recall stores dump failed」即可定位。

## [2.1.1] - 2026-08-29

issue #12 换行符字节保真修复（patch）。单测 227 项全绿，双平台真实模板端到端复验（pwsh 全新/存量迁移 + POSIX 实弹）31 项全过，`check:dsh` 巡检一致。

### 修复

- **快照/回退换行符失真（issue #12）**：影子仓库固化为字节保真语义——`info/attributes`（`FIDELITY_ATTRS`）对全部路径关闭 EOL 转换、clean filter、`$Id$` 展开、`export-ignore`/`export-subst`、`working-tree-encoding`。根因是 `git archive`（回退恢复路径）与 `git add`（捕获路径）都会应用快照树里项目自己的 `.gitattributes`：`text=auto` + Windows 缺省 `core.eol=native` 会把 LF 转 CRLF，仓库级 `core.autocrlf=false` 挡不住（属性驱动的转换看 `core.eol`）。存量归一化索引经一次性 `git add --renormalize -- ':(top)'` 迁移（标记文件 `attrs-v1.stamp` 防重复，迁移失败不阻塞快照）。连带修复 `export-ignore` 声明让文件从回退归档中静默消失的同类缺口。注意：回退到本版之前的旧快照仍会还原归一化内容（旧 blob 信息已物理丢失），本版起的新快照字节保真。

## [2.1.0] - 2026-08-29

改进专项与审查修复、环境诊断批次（错误治理 / POSIX home 三档 / 并发治理）、双平台实弹冒烟（Windows + WSL2 Ubuntu 26.04）后发版。单测 224 项、官方 API 探针、`verify:host`、`check:dsh` 全绿。

### 新增

- **回退失败救援闭环**（H1）：rollback 未输出 ROLLBACK_OK（工作区可能半回退）时，用 execute 预先打下的安全快照（`snap-pre-rollback-<ts>`）自动 reset 回「回退前」状态，提示含「已自动恢复」；救援也失败时给出可直接复制执行的手动命令（空格路径已实弹验证）。
- **索引原子写与损坏隔离**（H2）：index.json/lineage.json 走 tmp+rename 原子写；内容损坏或形状非法时 fail-loud——坏文件改名 `.corrupt-<ts>` 保留现场并告警，孤儿从 tag 重建（时间从 tag creatordate 恢复），不再静默当空。
- **错误码单一事实源**（H3）：`lib/errors.js` 收敛 18 个错误码；无快照撤回 / STALE / AGENT_BUSY 三场景的 client 文案与按钮行为对齐。
- **client 多文件化**（R1）：`src/client/` 源码 + esbuild 打包（产物 `lib/client.js` 随源码提交），CI 钉产物新鲜度（F-G6）。
- **Host 路由域拆分**（R2）：routes-core / routes-manage / session-info 三域，全部 API 可直调。
- **fork lineage 持久化**（F1）：lineage.json 记录撤回链（childId↔parentId），快照管理按链分组展示。
- **verify-host 装配门禁**（E1）：复刻生产装配做结构断言（兄弟提供者桩 + agents 行为），装配回归发版前即可拦截。
- **环境错误分类与可行动提示**（M1）：快照失败按 git 缺失 / 磁盘满 / 无权限 / 锁冲突 / mkdir 冲突分类，toast 与设置页「最近错误」共用同一套可行动中文文案（≤140 字符、不含原始路径）；同一错误相邻重复合并 ×N 计数。
- **POSIX home 三档回退与旧容器迁移**（M2）：bash `$DSH_HOME` → Node `DSH_HOME` → `~/.dsh`（第三档补齐 `.dsh` 层，修复快照误落 `~/dsh-recall-snapshots` 的 I24 漂移）；旧根级容器首次启动整容器自动迁移（MIGRATE_OK / OLD_ABSENT / BOTH_PRESENT / MIGRATE_FAIL 四态，数据不丢永远优先于路径规范）。
- **并发治理**（M3）：store 心跳文件（宿主 PID + epoch 秒，随每次快照/建库刷新）；失败清扫三级让路——另一活实例使用中让路（`CLEANUP_OTHER_INSTANCE`，win32 `Get-Process` / POSIX `kill -0` 探活）→ 5 分钟内新锁让路（`CLEANUP_SKIPPED_FRESH_LOCK`）→ 照常清扫（`CLEANUP_DONE`），根治 issue #11 双实例互踩死循环。

### 修复

- **PS 5.1 降级环境读编码**：index.json/lineage.json 读取显式 `-Encoding UTF8`——pwsh-local 解析链降级到 PS 5.1 时按 ANSI 活动代码页解码无 BOM UTF-8，中文 root 乱码 → 好索引被误判 corrupt 隔离（双平台实弹复现）。
- **双实例并发写索引的 tmp-rename 竞态**：并发 saveIndex 时一方 rename 把 `.tmp` 消费掉，另一方报「No such file」刷错误——写侧完整成功后 rename 阶段的 ENOENT 视同成功（同伴已原子落盘），不再进用户错误列表（WSL2 双实例实弹复现）。
- **冷启动首消息快照丢失**（ensureGit init 竞态）：首条消息与启动预热并发时两个 `git init` 同跑，输家 `fatal: cannot mkdir: File exists`——POSIX 版改为 HEAD 复查放行同伴、真失败带诊断退出（WSL2 实弹复现）。
- **孤儿重建条目 time=0**：重建快照时间从 tag creatordate 恢复——此前重建后管理列表时间前缀缺失、retention/条数上限按「最旧」误清真实快照。
- **救援链路前缀契约**（F-S1，严重）：rescue tag 忘拼 `snap-` 前缀导致 reset 目标必然 unknown revision、救援 100% 走失败分支——修复后救援首次真正生效（实弹验证含空格路径）。
- 其余审查修复：rebuildOrphans 过滤安全 tag（F-G1）、POSIX rollback 删除侧 rm 失败响亮退出（F-G2）、loadIndex 读截断与损坏区分（F-G3）、errors 测试门禁补强（F-G4）、verify-host 复刻生产装配（F-G5）、产物新鲜度 CI 门禁（F-G6）及 A1-A8 改进项。

## [2.0.0] - 2026-08-26

P0 防线（撤回防护/时效校验）、P1 工程化（单测/探针/CI）、设置页体验改造与新增四项配置、转向指令消息撤回修复。发版前活体冒烟（浏览器自动化 + 真实 dsh web）通过。

### 新增

- **运行中撤回防护**（P0-1）：目标工作区 agent 正在运行时拒绝发起撤回（preview/execute 均拦截，同会话优先、快照存在时叠加跨会话同工作区检查）——避免用户确认时文件被 agent 改动，预览清单与实际回退内容脱节。拦截依赖 `inject` 声明 `agents` 服务（cordis 4 门禁，冒烟实证：漏声明时静默 fail-open，防护等于没有）。
- **回退时效校验**（P0-3）：preview 之后、execute 之前若该消息又出现了新快照（`previewTotal` 与当前 `recall.total` 不一致），强制重新预览并提示，防止按旧清单回退。
- **工程化基建**（P1）：vitest 单元测试（104 例）+ 官方 API 字段探针（`npm run test:probe`，dsh 升级后本地必跑）+ GitHub Actions CI；快照失败反馈持久化（设置页可回溯最近错误）；per-workspace 快照条数硬上限（超出先清最旧）。
- **新增配置项**：快照总开关 `snapshotEnabled`（关闭只冻结新建、存量快照仍可撤回）、撤回后归档开关 `archiveOriginal`（关闭时原会话保留在侧栏）、按时间保留 `retentionDays`（0 关闭；超期快照自动清理，与条数上限独立触发）。
- **配置一键恢复默认**：设置页「恢复默认」走官方 `settings.replace` reset 通道（`section: {}` 重置为组合默认并清 user 覆盖层），老版本服务无该 RPC 时降级写默认值。
- **设置页体验改造**：文件大小上限改 MB 单位输入；快照树「加载更多」与计数修复（缓存全量数组、按 limit 切片）；存储健康状态行；快照搜索框；危险操作分级（全删/清空折叠 + 二次确认）；操作成功即时反馈；空态引导；「最近错误」可一键清空。

### 修复

- **转向指令消息缺撤回按钮**：agent 运行中插入的用户输入在 UI 投影层为 `kind=steering`（存储层 `role` 恒 user、无差异），不命中 keyed `user` 渲染器而落到官方默认气泡——keyed 注册扩展为 `['user','steering']`（冒烟实测复现并验证）。

## [1.7.1] - 2026-08-25

### 修复

- 历史会话中用户消息图片从未渲染（[#9](https://github.com/limbo947/dsh-recall-plugin/issues/9)）：插件渲染器读取的 `props.loadImage` 在官方 `conversation.chat.node` slot 契约中**从不存在**（实际入口是 `props.renderMessageImages`），自研加载链在守卫处直接 return——v1.6.2 的重试链、v1.7.0 的失败按钮全部从未执行，图片永久无声空白（v1.6.2/#8 的修复因此「修了却无效」）。用户消息图片改走官方 `renderMessageImages` 管线（自带鉴权、缓存、失败重试与灯箱预览），布局对齐官方（图片在上、气泡在下）；自研 `ImageBox`/`useImageSrc` 及对应 CSS 作为死代码移除。

## [1.7.0] - 2026-08-25

### 新增

- 快照失败/跳过可见性（[#7](https://github.com/limbo947/dsh-recall-plugin/issues/7) 加固项 1）：快照失败或熔断时客户端 toast 提示（同一故障文本 10 分钟节流，避免持续故障期间刷屏），轮询到失败即终止、不再空等 20 次；熔断期间的新消息会收到「已暂停，N 分钟后自动重试」提示而非沉默。「按钮为什么消失了」的排障成本由此消掉。
- `git add --ignore-errors` fail-open 兜底（[#7](https://github.com/limbo947/dsh-recall-plugin/issues/7) 加固项 3）：无法索引的路径（无提交的嵌入式仓库、不可读文件等）以退出码 1 结束但索引照常落盘——快照缺个别路径可接受，好过整条快照 fatal。被跳过的路径以 SNAP_SKIP 行回传，客户端提示「快照已跳过未纳入的路径」（这些路径撤回时既不恢复也不会被删，与排除表语义一致）。
- 失败后孤儿进程清扫 + stale 锁清理（[#7](https://github.com/limbo947/dsh-recall-plugin/issues/7) 加固项 4）：runShell 失败路径按 `--git-dir=<本仓库>` 命令行标记定位漏网孤儿进程并终止（win: `taskkill /T /F`，POSIX: `pgrep`+`kill`），随后清理 index.lock 等残留锁——DSH subprocess 服务的树级终止有竞态窗口，且 git 被硬杀不做锁回收，残留的 index.lock 会让后续每条快照持续 fatal。

### 修复

- `git add` fatal 时脚本假成功（空树快照）：pwsh 对原生命令非零退出不抛错（ErrorActionPreference 不作用于 native），此前 add fatal 后脚本会带着未更新的旧索引继续走完 write-tree/commit/tag，产出空树 tag 且退出码 0——快照「成功」却什么都回退不了。现显式检查退出码（≥2 抛错终止），diff/rollback 的同款 add 一并修复。

## [1.6.2] - 2026-08-25

### 修复

- 用户消息图片显示空白且刷新无效（[#8](https://github.com/limbo947/dsh-recall-plugin/issues/8)）：会话回放时最早的图片消息先于会话 binding 就绪渲染，`loadImage` 以 unknown session 拒绝后被静默 `catch` 吞掉，图片永久空白。图片加载改为 400ms/1.5s/4s 三次退避自动重试（暂态失败自愈），耗尽后显示「图片加载失败，点击重试」失败态（对齐官方 MessageImage 语义）；顺带修复 attachment 切换时短暂残留上一张图的问题。
- 快照连续失败无限累积磁盘残骸（[#7](https://github.com/limbo947/dsh-recall-plugin/issues/7) 评论实测一个下午 127GB dangling 对象）：失败重试的每次 `git add` 都会写入无 tag 可达的残骸对象。两道防线——
  - **失败清理**：每次快照失败后执行 `git prune`（以 refs + 暂存 index 为根做可达性删除），只清当次残骸、不碰任何 tag 快照，已暂存对象保留以维持下次增量。
  - **熔断退避**：连续 3 次失败后按 5min 起步指数翻倍、60min 封顶的退避跳过快照，冷却期满自动重试，成功一次即全部复位；进入熔断只在跳变沿记一条最近错误。

## [1.6.1] - 2026-08-23

### 新增

- 快照管理新增带二次确认的「全部删除」按钮：一次清理所有工作区的快照（[PR #5](https://github.com/limbo947/dsh-recall-plugin/pull/5)，@CangWeiohh 贡献）。

### 修复

- 默认基础排除表补上 `dsh-recall-snapshots/`（home 存储目录名，无前导点）：工作区 root 恰为 HOME 时（如容器内 root=/root），home 存储落入工作区且不被旧默认 `.dsh-recall-snapshots/` 匹配，`git add -A` 把影子仓库自己吞进去导致快照全部失败、撤回按钮永不出现（[#6](https://github.com/limbo947/dsh-recall-plugin/issues/6)）。排除表在下一次快照/回退时重同步并清理已误跟踪条目，存量坏索引自愈；曾在设置卡片改过基础排除表的用户需手动补一行 `dsh-recall-snapshots/`。
- 全部删除直接枚举每个影子仓库的真实 `snap-*` git tag，而非仅依赖可能丢失或过期的 `index.json`；tag 每 100 个分批删除并回读校验，确认成功后才清空索引。即使 `index.json` 为空、`root.txt` 缺失或列表未显示残留快照，仍可清理。
- 仅创建但未初始化 git 的空 store 视为无快照，不会阻断其他 store 的全部删除；任一 store 删除失败会保留其索引并在页面显示可重试错误。

## [1.6.0] - 2026-08-22

### 新增

- 撤回后自动把被撤回的消息文本回填到输入框（方案取自 [#4](https://github.com/limbo947/dsh-recall-plugin/pull/4)，按主干结构重写）：走官方 `conversation` 服务的 `input.shell(id).actions.setDraft`（与输入框自身同一写入通道，draft 镜像同步），对话回退成功时填入新会话、回退失败时填入当前会话；8 次 × 150ms 有界重试覆盖 fork+open 后 shell 就绪竞态，拿不到服务时静默跳过。新增配置开关 `refillDraft`（默认开）。
- 设置入口迁入官方「插件配置」分区（[#2](https://github.com/limbo947/dsh-recall-plugin/issues/2)）：改用 `settings.plugin.item` keyed slot（按 namespace `dsh-recall` 分发），Host 端经官方 `installSettingsSection` 注册真 schema 的 settings namespace——`settings.describe` 命中后卡片出现。卡片内含插件配置表单（保存经 `dsh-settings` 持久化进用户层、watch 链路热生效无需重启）+ 排除配置编辑（折叠）+ 快照管理（折叠）。原「撤回设置」独立标签页移除。
- 导出 Schemastery `Config` schema（官方「插件配置」文档要求）：cordis 加载时校验入口配置并填充默认值，非法配置在插件加载时响亮失败；同时作为 settings namespace 的注册 schema，一式两用。
- 设置卡片「插件配置」表单：gc 触发条数/小时、文件大小上限、基础排除表、回填开关五个字段；显示「已覆盖」（用户层覆盖）与「环境变量锁定」（`DSH_RECALL_GC_SNAPS/GC_HOURS` 仍最高优先）标记；只提交修改过的字段，避免全量覆盖污染用户层。

### 变更

- 设置卡片默认收起、点卡片头展开，视觉规格对齐官方 PluginCard（bg-layer 底色、展开态边框/背景变化、标题 15px/600、内容区上边框分隔、箭头 14px 居右）。
- 快照管理的磁盘占用与「立即 gc」全局化：设置卡片无会话上下文，`usage` 汇总全部已知 store、`gc` 逐 store 执行（新增 `maintenance.runGcAll`）；带 sessionId 的旧调用语义保持不变。
- 配置热更新贯通：`gcSnaps/gcHours/maxFileBytes/baseExcludes` 改为调用时读取（原工厂创建时快照），settings 卡片保存后下一次快照/gc 即按新值执行，无需重启。
- `package.json` 新增 peerDependencies：`@deepseek-ai/dsh-settings`、`@deepseek-ai/schemastery`。

## [1.5.2] - 2026-08-22

### 修复

- 撤回按钮不自动出现、需手动刷新（[#3](https://github.com/limbo947/dsh-recall-plugin/issues/3)，修复方案取自 [#4](https://github.com/limbo947/dsh-recall-plugin/pull/4) 并按主干结构重写）：两处根因分别修复——
  - 快照捕获是异步的，客户端在消息节点挂载时只查一次 `snapshot-info`，先于捕获完成返回 `has:false` 则永不重试。改为有界轮询：近 5 分钟内的新消息最多 20 次 × 1s，`has:true` 即渲染按钮，捕获完成后自动出现；老消息不再空转请求。
  - 冷会话（未 live）根目录解析错误：`resolveRoot` 只认 live 注册表，冷启动时回退 `sandboxPolicy.workspaceRoot`（常为 harness 启动目录）导致查错 store，且错误根被永久缓存。改为先经 `sessionQuery.listSessions` 从持久化 header 解析真实 cwd；只有 live/持久化来源的权威结果才进缓存，回退的临时根不缓存。
- 启动预热读冷会话元数据时会话 id 误取 `record.id`（`listSessions` 记录的 id 在 `header.id`，顶层恒 undefined）：预热重建的孤儿快照 sessionId 记为空，树形管理里落入「已删除会话」节点。改读 `record.header.id`。

## [1.5.1] - 2026-08-18

### 修复

- 设置页冷启动优化：`exclude-get` 首次遍历工作区、逐文件 shell 读改为并行 + 30s 结果缓存（保存后失效），二次打开设置页不再重复付出首访代价。
- 冷会话标题/消息文本补齐引入通用并发限制器（`runLimited`，同时最多 4 个）：首次大量冷数据时 `readSession` 整日志解压不再全量并发压垮磁盘/CPU。
- 启动预热叠加 `sessionQuery.listSessions()` 冷元数据：冷启动时 `ctx.sessions.list()` 常为空（惰性载入），此前设置页首次打开仍要现场建 store，现开机即预热全部历史工作区。
- Client 侧 `usage`/`status` 补数据延后到 `list` 返回后异步执行：首屏先渲染树形内容，磁盘占用与错误日志随后补齐；`list` 失败时仍尝试补这两个数据，避免整卡全空。

### 变更

- Client 侧 `titles` 请求的 sessionIds 去重（`Set` 去重 + 过滤空值）。

## [1.5.0] - 2026-08-18

### 新增

- 快照管理改为**树形结构**展示：第一级工作区（文件夹名）→ 第二级会话（会话标题）→ 第三级快照（消息 ID/消息内容摘要）；工作区与会话支持展开/折叠。
- 树形每级右侧提供删除按钮：工作区删除该工作区全部快照，会话删除该工作区内该会话全部快照，叶子单条删除；均带行内二次确认。
- 快照叶子显示**对应消息内容摘要**（取 `user/message` 事件的 text 块），悬停显示完整内容；冷会话消息文本经新增 `manage/messages` 端点按会话分组异步补齐，避免为每条消息重复解压日志。
- Host `manage/delete` 扩展 `scope=workspace/session` 批量删除：内存 + 磁盘全量收集匹配快照，按 root 分组进串行队列，tag 分块（每 100 个）规避 Windows 命令行上限；单个 root 失败 best-effort 继续并进入错误缓冲。

### 变更

- `manage list` 同 id 去重由“首次命中即丢弃”改为字段补全（root/sessionId/time/标题/消息文本），避免磁盘先占位、内存后补全时树形节点落入「未知工作区」。
- 删除操作成功提示改为显示实际删除条数（`已删除 N 条快照`）。

### 兼容性

- 全部改动保持纯 JS 零构建；未改存储格式与脚本模板接口，旧索引/旧快照无需迁移。

## [1.4.0] - 2026-08-17

### 新增

- 官方插件配置机制：`cordis.patch.yml` 行声明默认值（`gcSnaps`/`gcHours`/`maxFileBytes`/`baseExcludes`），用户在 profile 的 `cordis.patch.yml` 按 `id: recall` 重述该行即可覆盖；`DSH_RECALL_GC_SNAPS/GC_HOURS` 环境变量保留为最高优先（向后兼容）。
- 回退前自动保存安全快照（`snap-pre-rollback-<时间戳>` tag，不进列表），误回退后可从该 tag 找回，堵住唯一的不可逆操作缺口；确认面板文案同步说明。
- 设置页「快照管理」卡片：快照列表（时间倒序，含工作区名/会话标题）、当前工作区磁盘占用、单条删除、「立即 gc」手动触发、最近错误展示（Host 侧失败原本只在宿主进程日志，页面不可见）。
- 快照列表跨工作区名称解析：`saveIndex` 条目持久化 `root`；store 目录新增 `root.txt` 元数据（旧 store 重新解析时自动补写）；工作区 cwd 全集取「live 注册表 + `sessionQuery.listSessions` 冷元数据」并集（冷启动注册表为空也能解析）。
- 快照管理性能优化：新增双平台 `storesDumpScript` 一条 shell 批量 dump 全部 store 元数据（旧实现每目录 2-3 条 shell 串行，冷列表 20 秒级）；列表 30 秒结果缓存（删除/新快照失效）；冷会话标题两段式——列表首屏只查 live/缓存（同步瞬时），冷标题（整日志解压 10 秒级）由客户端异步 `titles` 端点补齐、行内先显示「…」。实测冷列表 20s+ → 2.3s、缓存命中 8ms、删除 20s+ → 4.4s。
- Host 新增 `manage`（list/usage/delete/gc）与 `status`（最近错误环形缓冲）端点；`preview`/`execute` 与快照/gc 共用同一条串行队列，消除 git index 锁并发竞态。
- 变更清单截断保护：超过 500 条时面板显示「仅显示前 N 条」，总数仍准确；请求体 1MB 上限（`BODY_TOO_LARGE`）；启动时自检两套脚本模板的同名导出对齐。

### 修复

- 索引载入失败（如 shell 未就绪）后该工作区本次进程内被永久标记「已载入」、撤回按钮消失直到重启——改为读取链路全部走通后才标记，失败自然重试。
- 快照列表「未知工作区」与同快照重复行：旧列表只查内存 `state.snapshots` 且去重 key 带 root——冷启动注册表为空时全部落空。修复后磁盘来源三层解析 root、去重只按消息 ID。
- 管理页删除误报「该快照不存在」：列表来自磁盘全量而删除只查内存——修复为「内存 → 条目 root → 磁盘 index 反查（`locateSnapshotOnDisk`）」解析链；兜底删除前先 `loadIndex` 补齐内存视图，防止 `saveIndex` 用残缺内存覆盖 index.json 抹掉同 store 其余快照；`purgeSession` 对未缓存 root 现场解析 store（原先直接跳过导致该 root 清理永远 miss）。
- 事件重放/重发产生重复 messageId 时 `git tag` 重名 fatal 导致整条快照失败——改 `tag -f`（同一条消息重快照取最新状态）。
- A→B→A 切换会话后 A 复用 B 的 init promise——init 缓存改 `Map<会话, Promise>`。

### 变更

- 错误回包统一为 `{ok, code, message}`（业务失败与系统异常分离，文案与诊断解耦）。
- `saveIndex`/`writeExclude` 的 win32 base64 分块与 POSIX stdin 分叉合并为统一落盘原语 `writeTextViaShell`；脚本导出 `indexWriteCmd`/`excludeWriteCmd` 合并为 `fileWriteCmd`。
- `resolveHomeContainer` 改纯 JS 推导（容器 = home 目录父级），删除与 `homeDirScript` 重复的整条 `$h` shell 解析链（消除双链漂移风险）。
- `maintenance.js` 导出面收敛为 `maybeMaintain`/`runGc`；删除 `index.json` 的死字段 `count`；删除未使用的非 scoped `cordis` peerDependency。
- Host 端点分发重构为端点表 + 统一 try/catch；Client 侧 `kind` 语义（文案/徽章类名/汇总）合并为单表。

### 兼容性

- 全部改动经冒烟实测：临时中文+空格工作区上跑通真实 git 链路（建仓/快照/tag -f 幂等/diff 三类变更检出/回退恢复与删除/分块索引读写/tag 清理/gc/磁盘统计），Windows PowerShell 5.1 与 pwsh 7 双解释器通过。
- 评估阶段曾将 win32 回退改为 bsdtar 优先，冒烟实测否决：GBK 代码页机器上 bsdtar 把 tar 流里的 UTF-8 文件名按 ANSI 解码（中文文件名解包成乱码新文件），已回滚为 zip + Expand-Archive 链路（中文路径实测正确，mtime 语义天然安全）。

## [1.3.0] - 2026-08-17

### 新增

- 设置页「撤回设置」标签（设置 → 插件）：可视化编辑快照排除项——输入路径或模式回车即加、常用模式一键追加（`dist/`、`*.log`、`.env` 等）、放弃修改/保存与未保存状态提示，保存后下一次快照/预览/回退立即生效，无需重启。
- Host 端 `exclude-get` / `exclude-set` HTTP 端点：枚举并读写全部 exclude.txt（home 存储全局共享一份，降级工作区各自独立、分卡片展示）；写入走 base64 分块（win32）/ stdin（POSIX），任意长度配置不受命令行上限约束；写入路径经服务端白名单校验（仅接受枚举结果中的路径）。
- 冷启动兜底：会话注册表未载入时按磁盘 home 容器目录枚举 exclude.txt（`resolveHomeContainer`），设置页不再误报「尚未创建快照存储」。

### 兼容性

- 全部新增 shell 命令在 Windows PowerShell 5.1 与 WSL2 Ubuntu（bash）实测通过，覆盖中文/空格路径、CRLF、空文件、缺失文件等边界。

## [1.2.2] - 2026-08-15

### 修复

- 撤回出的新会话不再向标题追加递增数字：fork 不传 `increaseTitle`，原样继承原标题。

### 文档

- 新增英文 README（README.en.md，与中文版互链）与 AGENTS.md 项目速览。

## [1.2.1] - 2026-08-15

### 修复

- 修正 package.json 仓库地址（仓库改名后同步）；README 安装地址同步。

## [1.2.0] - 2026-08-15

### 新增

- Linux/macOS（bash）平台支持：与 Windows 版同名导出的脚本模板按 `process.platform` 单选；POSIX 侧 `DSH_HOME` 解析对齐执行器 env 洗刷语义（WSL2 实测）。
- 快照自动维护：定期 `git gc`（每 50 条快照或 24 小时先到先触发，`DSH_RECALL_GC_SNAPS` / `DSH_RECALL_GC_HOURS` 可调，`gc.stamp` 跨重启续存节流）。
- 会话删除联动清理：会话日志从磁盘消失后自动删除该会话全部快照 tag 并释放空间；归档不算删除，判断保守（冷会话不误清）。
- 用户自定义排除：home 下 `exclude.txt`（gitignore 语法）全局生效，下一次快照/回退即时应用。

### 变更

- Host 代码模块化拆分（index / store / snapshots / maintenance / scripts.*），零顶层副作用，全部副作用经 `ctx.on` / `ctx.effect`。

## [1.0.4] - 2026-08-15

### 修复

- 非 UTF-8 代码页（GBK）输出乱码、UNC home、非 Windows 平台的通用性问题。

## [1.0.3] - 2026-08-15

### 修复

- 跨机器通用性：git 多候选安装位置探测、索引 base64 分块写入（突破命令行 32767 上限）、目录扫描容错（杀软锁定/异常 ACL）、路径尾分隔符归一、`DSH_HOME` 回退链。

## [1.0.2] - 2026-08-15

### 新增

- 未装 git / home 不可写时页面顶部一次性降级提示（gitMissing / homeFallback）。

## [1.0.1] - 2026-08-15

### 变更

- shell 以宿主身份（`danger-full-access`）执行：受限会话（workspace-write / read-only）也能在 home 建影子仓库、照常快照与回退。

## [1.0.0] - 2026-08-15

### 初始发布

- 消息撤回：影子 git 仓库快照（tag 即快照，项目目录零污染）+ 官方 `sessions.fork` 对话整段回退，原会话归档可找回。
- 确认面板先展示变更文件清单（修改/恢复/删除）再执行；`.git`、`node_modules` 自动排除；超过 100MB 的大文件跳过。
- key 冲突递减重试的 user 槽位注册，Windows PowerShell 5.1 / 7 双版本兼容。
