# Changelog > 本文档收录 dsh-better-sidebar 的完整发布历史(最新版摘要见 [README](README_ZH.md);同步发布于 [GitHub Releases](https://github.com/omdsh-dev/DSH-better-sidebar/releases))。 ### v0.28.0 - **正式版支持线前移到 DSH 0.2.1-rc.1+**:13 条 DSH peer 下限统一为 `^0.2.1-rc.1`,开发依赖、CI 与安装脚本使用精确 RC 基线。不新增运行时兼容层、不改功能实现。 - **沿用已发布 alpha 的功能**:上游 alpha.5 → rc.1 完整源码树差异共 355 项,全部为 `package.json`,源码零变化;本次仅同步版本、依赖声明与文档。伴随依赖调整为 Cordis peer `^4.0.5` / dev `4.0.5`、group `1.0.5`、schemastery `~3.18.5`,include 保持 `1.0.10`。 - **发布渠道切换至 npm `latest`**:插件 v0.28.0 即将接管 `latest`,现有 workflow 按正式版本号自动选择该标签,无需修改。**宿主 npm `latest` 此时仍是 0.2.0-rc.2,不能安装本版**;该宿主必须固定插件 **v0.26.1**。DSH 0.2.1-alpha.5 用户固定 **v0.28.0-alpha.0**;0.2.0-rc.1、0.1.7 及更早支持线保持原指引。用户已明确接受两个包的 `latest` 不对齐风险。 - **RC 验证独立取证**:结果见 [DSH 0.2.1-rc.1 适配记录](docs/plans/2026-10-17-dsh-0.2.1-rc.1-adaptation.md);未完成的检查保持待验证,不沿用下方 alpha 发布时的测试数字。 ### v0.28.0-alpha.0(预发布) - **支持线前移到 DSH 0.2.1-alpha.5+**:13 条 DSH peer 使用 `^0.2.1-alpha.5`,开发依赖与 CI / 安装脚本钉精确基线。0.2.0-rc.2 用户请固定 npm 已发布的 **v0.26.1**(v0.27.0 尚未发布);本 alpha 使用 `dsh-better-sidebar@0.28.0-alpha.0`,不是 `latest`。 - **宿主 Connection 准入**:HTTP/WS 路由统一调用 `ctx.connection.requestRejection(req)`,不再依赖已删除的 `webRuntime` 或插件自建信任例外。 - **移除 runtime invariants 接入**:上游删除 `@deepseek-ai/dsh-invariants` 与 `/invariant` 子路径,本插件同步移除 companion、类型与发布入口;压缩种子测试继续使用公开的 surface fold 和 seed validator。 - **当前执行目录接入**:文件 / Git / 终端与新建 sidechat 跟随会话 `working_directory`;live 读 workingDirectory,cold 用 sessionQuery all projections 并释放 lease,linked worktree 保持 base-keyed,新 sidechat seed 尾部盖章当前 cwd。 - **宿主伴随 API / 依赖**:补齐 `TerminalBlockLabels.commandLine(line)` 与 21 份词典;同步 Cordis `4.0.5`、schemastery `~3.18.5-alpha.2`、group `1.0.5-alpha.2` 与 include `1.0.10`,不直接钉 loader 或设置 override。alpha.5 优先用 package 的 `engines.dsh` 判断兼容性,本插件不新增该字段,继续用 13 条 DSH peers。 - **验证记录**:[发布整合验证](docs/plans/2026-10-17-alpha-release-validation.md) 收录最新 dev 的 221 文件 / 2681 用例单 worker 通过、真实 alpha.5 挂载与 Context 三态联动验证;[初始适配记录](docs/plans/2026-10-16-dsh-0.2.1-alpha.5-adaptation.md) 保留适配阶段证据。 - **提交图使用完整可用高度**:移除 `min(60vh, 460px)` 高度限制,更改区下方到固定底栏之间的空间全部分配给图谱;打开差异预览或缩放窗口时自动调整。长历史仍在图谱内部滚动,待提交文件很多时保留原有外层滚动,避免把整个页面撑高。 - **dsh-context 可选联动**:Context 侧栏入口可用时,在 Agent 详情、后台任务输出窗和会话标题栏提供诊断入口;后台任务仅查看明确的真实属主,不改变任务输出观察或停止命令的会话语义。跨会话先定位目标再打开 Context,复用其他分栏已有的标签;用户继续导航时取消,等待最多 10 秒。未安装、禁用或仅显示会话标签页时隐藏入口,不修改上下文、不恢复 Agent 执行,也不修改 dsh-context 偏好。全部入口共用 Cordis 作用域内的可选适配器,空闲时不新增轮询。 ### v0.27.0 - 🌿 **Git 页面重做:VS Code Source Control 结构 + 只读提交图(同一页签)**([#941](https://github.com/omdsh-dev/DSH-better-sidebar/issues/941),贡献者 PR [#944](https://github.com/omdsh-dev/DSH-better-sidebar/pull/944) @hairyf + [#969](https://github.com/omdsh-dev/DSH-better-sidebar/pull/969) @vlln):「文件变动 > Git」的内容区改为 Source Control 结构——可折叠的 Changes(多行 Message + 仅填草稿的 Generate、Merge / Staged / Changes 三组各自独立折叠与计数、整行 hover)与 Graph 区、常驻底栏(可点击分支 + `*`/`+`/`!` 标记、upstream 的 `↓`/`↑` 与安全 Pull-then-Push);Push/Pull 与 worktree·子仓库选择移入 More Actions;提交只消费已暂存项,冲突 / 空消息 / 无暂存时禁止,草稿按会话·仓库·worktree 隔离。Graph 区由 **#969 的有界只读引擎**渲染(专用 `git.graph` 路由:子先于父的拓扑序、分支筛选、游标失效显式返回 `graph-stale`、每页 200 / 偏移 10000 / 分支 1000 / 进程 10 s / 输出 8 MiB 上限,超限报错不静默截断;泳道连线与引用徽章用主题状态色,hover 卡片与提交详情卡;**纯读取**——不 checkout / fetch / 改任何 ref)。底部预览面板改为固定三签 `提交详情 / 内容 / 差异`(有内容才有差异,读操作落在内容签)。**设计取舍与实施偏差见 [docs/plans/2026-10-16-git-page-vscode-scm-merge.md](docs/plans/2026-10-16-git-page-vscode-scm-merge.md)**(贡献者 PR [#939](https://github.com/omdsh-dev/DSH-better-sidebar/pull/939) @hairyf 的官方下划线 Tabs 镜片切换保留;第三个「提交图」镜头刻意未上线)。 - 🌿 **页签名「文件变动」→「Git」**(贡献者 PR [#968](https://github.com/omdsh-dev/DSH-better-sidebar/pull/968) @vlln):21 份词典、两份 README 与相关测试同步(页签内部镜头名保持现状)。 - 🐞 **变更面板两侧行数不再相加**([#908](https://github.com/omdsh-dev/DSH-better-sidebar/issues/908),贡献者 PR [#909](https://github.com/omdsh-dev/DSH-better-sidebar/pull/909) @UNscientific-9):`MM` 文件的两次读数各自成字段(`staged` / `unstaged`),每个分组只读自己那一侧,Merge 组不印行数——同一行被两次 diff 看见时不再印成 `+2 −2`。 - 🐞 **git 命令遇到 `dubious ownership` 不再整页失效**([#690](https://github.com/omdsh-dev/DSH-better-sidebar/issues/690),贡献者 PR [#827](https://github.com/omdsh-dev/DSH-better-sidebar/pull/827) @yanzhaohui1999):守卫按**单条命令**应答——git 自己指名被拒的仓库目录,插件只对那次调用注入 `-c safe.directory=
` 内)文件调用可见**([#913](https://github.com/omdsh-dev/DSH-better-sidebar/issues/913),PR [#921](https://github.com/omdsh-dev/DSH-better-sidebar/pull/921));操作改为一行一条**按时间倒序平铺**、不再按文件分组(PR [#940](https://github.com/omdsh-dev/DSH-better-sidebar/pull/940) @hairyf)。路径解析与 PTC 支持来自 @wang-kaopu,平铺操作列表来自 @hairyf。
- ⚡ **`changes.ops` 空窗不再重复整窗**([#870](https://github.com/omdsh-dev/DSH-better-sidebar/issues/870),贡献者 PR [#885](https://github.com/omdsh-dev/DSH-better-sidebar/pull/885) @wang-kaopu):游标改由客户端显式传递(不传 = 从最早一条起,含 seq 0),primed 之后请求带 `wait` 挂在宿主侧等下一个工具事件(25 s 上限)而不是每 2.5 s 重放同一窗;路由体移入 `src/changes-ops.ts`,并保留 PTC 四种事件类型。
- **修复选中行错位与异常边框**:整合提交图时误删了 Git 文件行与本轮操作行共用的选中声明,留下的选择器串到了缩进参考线规则,导致选中文件脱离分组。恢复原有主题背景与行内标记,不改变图谱高度或滚动结构;`tests/theme.spec.ts` 与真实挂载 `tests/e2e/git-layout.e2e.ts` 覆盖选中前后布局、暂存与未暂存同名文件、冲突行和窄侧栏。滚动状态保持测试默认使用独立临时仓库,避免复用旧目录。
- **侧边对话从最近一次 summarizing compaction 起种**(贡献者 PR [#964](https://github.com/omdsh-dev/DSH-better-sidebar/pull/964) @NolanHo):新子会话不再全量复制父日志,保留必要的事件结构与成组记录;新增真实宿主校验器回归。
- 🧪 测试:新增 `tests/git-scm.spec.tsx` / `git-graph-ui.spec.tsx` / `graph-layout.spec.ts` / `changes-ops-route.spec.ts` / `e2e/git-graph.e2e.ts` / `e2e-aggregate-auth.spec.ts`,改写 `changes-tab.spec.tsx`(底部面板三签、图谱区在 Git 镜头内)、`git.spec.ts`(porcelain v2 状态与发现根归一)、`git-view-worktree.spec.tsx`(图页竞态)、`unbounded-caches.spec.ts`(长轮询契约)等 20 余份既有 spec;删除随内联图退场的 `git-graph.spec.tsx`。
### v0.26.1
- ⚡ **稳态开销治理:0.25 引入的常驻轮询、每 tab 成本与 `keepMounted` 的无界复利**(社区反馈「0.24 没问题,之后就会卡」——侧栏在前台开久了卡、多轮对话后 DSH 卡、重装重启又暂时好;分界线经产物级 A/B 落在 v0.25.0,v0.26.0 的 git 轮询修复没触及这些;方法与全部实测数字见 [docs/plans/2026-10-15-perf-steady-state.md](docs/plans/2026-10-15-perf-steady-state.md)):
- **两个 500ms 表先治**(v0.25 引入,实测单 tick 0.5→9.7ms、最坏约 3 500 次强制布局读取/秒,且**无论是否可见都在跑**):宿主右侧面板展开态改**全模块一条共享表**(引用计数,隐藏即 `clearInterval`,恢复即补读);空白会话的兜底入口只在它**真的渲染**时建表(此前 interval 注册在 early return 之前,渲染 `null` 也照跑),让位走查 400→100 步(400px),隐藏期间不测量不写 state。同批:`settings-nav-icon` 的 `document.body` 观察者改 **rAF 合帧**并去掉 `characterData`(流式 token 改动文本节点不再逐次触发全文档查询)、`use-center-column` 的 1.5s 兜底表只在「一列都没有」时 armed(此前周期与复核窗口等长,注释声称的 query-free 快路径永不生效)、`session-phase` 隐藏时不请求且值未变不 `setState`(blank 会话不再每秒重渲染)。
- **「每 tab」收敛成「每会话」**:`use-session-root` 重写为按会话引用计数的共享 store——一条 5s 表,只在 ≥1 个**可见**订阅者时存在,页面隐藏停表、恢复/聚焦/取消 park 立刻补刷;native tab body 传自己的 `visible`(宿主对 parked body 报 false)。文件树目录 watch 与侧边聊推送同样按可见性门控(parked 不建 socket、不做重列、恢复即重连补刷)。**此前每访问一个页签就永久多一条「每 5s 一次 `git rev-parse` 子进程 + 4 000 条事件回扫」**——`cwdOf()` 被 12 条 `/sidebar/*` 路由共用,实测每次未命中 9.6–44.7ms。
- **`keepMounted` 保留语义、加上界**(切走不卸载仍然成立,草稿/滚动/展开态照旧):`git-status.slots` 释放归零即删并按 256 上限淘汰、`ops-stream.streams` LRU 32(保留 `seq` 游标,parked 页签回来仍折到错过的写操作)、`state.bySession` LRU 32 且 `getSessionStates()` 不再每次整份复制(`syncExpanded` 原先每次 store notify 都 O(会话数) 复制)、`previewScrollMemory` 64、`commitDrafts` 32、`pendingAnchors` 写入时按 TTL 清扫。
- **宿主侧**:`active-worktree` 加在飞去重(并发 8 次只 spawn 一次)、`ACTIVE_ROOT_TTL_MS` 3s→15s(与客户端 5s 轮询错配导致**每次都未命中**)、三个模块级缓存加 LRU 上界(512/512/256);`git.ts` 的两条 `--numstat` 加 pathspec(未忽略的大目录下从「两条全仓库 diff」变成零条,未跟踪路径不参与)并给 `runGit` 加 **8 MiB 输出上限**与产出侧行数上限;单层 `fs.tree` 补 `ProbeGate`、折叠探测每层封顶 32 且截断层不探测;`sidechat.events` 冷线程加 `(revision, size)` memo;`agent-opens` 每会话队列封顶 64;teardown 主动关闭三个 `noServer` WSS 的**已接受连接**(ws@8 的 `close()` 不会终止在连 socket,句柄会活到浏览器断开)。
- 🧪 测试:新增 `tests/steady-state.spec.tsx`(跨模块门禁:非 blank 会话只允许**一条** 500ms 表且**不得**有 5s 表、页面隐藏时零表、兜底入口在场但渲染 `null` 时命中测试必须为 0、`session.cwd` 请求必须为 0;末节是**客户端 `setInterval(` 站点清单**——新增未登记的周期任务即红,登记了门控词却删掉门控也红)、`tests/right-panel-open.spec.tsx`、`tests/session-phase.spec.tsx`、`tests/session-root-poll.spec.tsx`、`tests/unbounded-caches.spec.ts`;扩展 `dock-fallback` / `settings-nav-icon` / `center-column-locator` / `file-tree-dir-watch` / `use-sidechat-push` / `state` / `active-worktree` / `git-worktree` / `fs-tree-probe-gate` / `sidechat-routes` / `smoke` / `agent-opens` / `git-status-poll` / `subagent-live-polling` / `subagent-jobs-view` 共 15 份既有 spec;判别性由逐条「临时回退 → 对应用例变红 → 还原」实测。`tests/e2e/perf.e2e.ts` 新增 `steady-state` 指标与两条硬断言(兜底入口不渲染即不得命中测试;`session.cwd` 必须按会话而非按访问过的页签计数)。
- **明确未做**:关闭 `keepMounted`(用户选定保留语义 + 加上界)、文件树/侧聊列表虚拟化、CSS `:has` 回潮、任何宿主侧改动、给 perf lane 加耗时阈值门禁。**无公开接入 API 变化**(`ctx.betterSidebar` / TabDescriptor / FileViewerDescriptor / localStorage 格式均未动)。
### v0.26.0
- ⚡ **git 状态轮询不再拖慢界面**([#212](https://github.com/omdsh-dev/DSH-better-sidebar/issues/212) 后续;设计与实测见 [docs/plans/2026-10-13-git-poll-ladder-and-idle-notify.md](docs/plans/2026-10-13-git-poll-ladder-and-idle-notify.md)):一次抓取是 `git status --untracked-files=all` 加两条全仓库 `--numstat`(4 个进程),在**未忽略的大目录**(嵌套 `node_modules`、pnpm store,就是「根锚定 `/node_modules` 盖不住子目录」那种仓库)上实测 **59,631 条 / 9.24 MB / 0.131 s**,而截断上限是在整份输出产出并解析成五万多个对象**之后**才生效的。三处一起收:
- **编辑器 gutter 不再持有轮询**——它此前把**功能开关**当成了页签可见性传进共享 store,而那个参数正是启动定时器的开关;加上原生页签 `keepMounted`(切走不卸载),**只要打开过任何一个文件,整页生命周期就每 2.5 s 跑一次那个扫描**,哪怕你正在打字。现在它只读共享答案、只在**换文件或保存**时各重读一次;页签不可见时连订阅都不建立。
- **节奏降到 30 s 起,并按实测自动退档**——往返超 750 ms / 2 s / 5 s 依次退到 60 s / 180 s / **停止自动轮询**(快响应自动回落;暂存、提交、丢弃、保存、手动刷新这类事件驱动的刷新永远即时,也是恢复通道)。文件列表与染色条目的语义零变化,只是后台不再常驻 git 进程。
- **空闲轮询不再触发重渲染**——store 的 `notify()` 原先每 tick 无条件自增 `version`,而它就是 `useSyncExternalStore` 的 snapshot,于是**每个消费者每 2.5 s 整体重渲染一次**(文件树那一列逐行重算 git 状态也在其中),即便数据一字未变;现在只有真的变了才通知。
- **linked-checkout 清单的计数改单命令**——变动页的 worktree 选择器每 30 s 为**每个** checkout 跑一次完整 `status()` 只为读条目数,现改为一条轻量 `status --untracked-files=normal`。**一处刻意的语义变化**:未跟踪**目录**计 1(此前展开计入每个文件,而且数字还被 2000 上限悄悄截断)。文件树与变动页的逐文件行不受影响。
- 🧪 测试:新增 `tests/git-status-poll.spec.tsx`(7 例:非轮询消费者不得启动定时器、非轮询先到不阻塞后来的轮询者而轮询者离开即停表、空闲同结果不通知、30 s 基线、慢往返退档与快响应回落、极慢往返停止自动轮询且事件刷新可恢复、不可见不订阅);`tests/editor-git-gutter.spec.tsx` +3(在屏编辑器推进 120 s 仍只读一次、不可见完全不订阅、换文件与保存各只重读一次而纯重渲染不重读);`tests/git-worktree.spec.ts` +1(未跟踪目录计 1,完整 `status()` 仍展开)。判别性:分别回退空闲守卫、`poll` 归属与 gutter 的 `poll: false`,对应用例当场红(已逐条实测)。同批还把 `.delta/` 加进 `vitest.config.ts` 的 `exclude` 与 `eslint.config.js` 的 `ignores`——那是 DSH worktree 通道留下的陈旧检出,`.git/info/exclude` 忽略它,但这两个工具只读各自的配置,不排除时本地 `pnpm test` 会报 6 文件 / 7 用例**假红**、`pnpm lint` 报 1115 errors(`eslint` 的「点开头目录默认也不进」那句注释同时订正)。
- ⚡ **文件树两处性能放大收敛:折叠链预加载 + 宿主逐子目录探测**(逐条核查结论与「按用户决定不做」的清单见 [docs/plans/2026-10-13-file-tree-perf-fold-preload-and-probe-budget.md](docs/plans/2026-10-13-file-tree-perf-fold-preload-and-probe-budget.md)):① **折叠链的预加载与渲染现在共用同一个 32 链上限**——`compactLoadTargets` 此前扫描**所有已加载层**,而渲染侧 `compactChain` 在 `MAX_COMPACT_DEPTH` 就停手,于是渲染收在 32 节、预加载仍继续请求第 33、34… 节(每节一次串行 `fs.trees` POST 加一次全树 render,标签却不会再变长);现改为从渲染根(`cwd` + 展开集)沿**同一条** `compactChain` 下行,上限自然继承。顺带订正了「安全上限,健康缓存下永不触及」这处不准确注释:合法的深单子目录链(`src/main/java/com/...` 这类)真会触及,循环守卫是 `compactChain` 的 `visited` 而不是这个常量。② **`fs.trees` 整批共享一个探测预算**——新增 `PROBE_BUDGET` / `ProbeGate` / `createProbeGate`,`listDirectory` 增加可选 `gate` 参数(只在真实文件系统探测外层取槽,**缓存命中的层一次都不花**),路由每请求建一个 gate 并传给它的所有层:此前一个批次最多 64 条路径 × 每层 32 路探测可**同开约 2048 个 `opendir`**,冷挂载一棵宽目录树就是一次 syscall 风暴。单层与 `fs.tree` 单路径行为逐字不变(gate 缺省即原并发)。**明确未做**:文件树**不引入虚拟化**(memo 边界 + 每层 1000 行上限 + `truncated` 提示已够,用户判断当前性能无问题)、**watch 上限不加用户提示也不加全局句柄**(无法 watch 的机器本就存在,VS Code 同样不提示)、不改 `DirRow` 的 chain identity。
- 🧪 测试:新增 `tests/fs-tree-probe-gate.spec.ts`(gate 在飞上界、排队任务抛错后释放且保持 FIFO、`limit<1` 夹紧;`listDirectory` 每个目录探测恰花一个槽位、缓存命中零开销;三层共享一个 gate 合计 9 次);`tests/file-tree-compact.spec.ts` 与 `tests/file-tree-compact-fold.spec.tsx` 新增「到渲染上限即停」(把实现回退到旧扫描,两条当场红:预加载会一路问到链尾);`tests/smoke.spec.ts` 新增路由级断言「一次 `fs.trees` 只建一个 gate」(`vi.mock` 包一层计数,其余导出保持真实)。
- 🐞 **文件树的「加载中」占位不再把子树永久卡死**:`loadLevels` 对非强制加载会先写一个空 `{}` 占位(渲染即 Loading 行),而应答被世代守卫**丢弃**时(刷新 tick / 排除列表变化 / 卸载都会 bump 世代)那个占位没人回收——之后任何加载都把「缓存里有条目」当成已加载(空占位也算命中,注释里写明是为了避免重复请求),于是这一层再也不会被重新请求,**子树停在 Loading 行直到下一次强制刷新或重挂**。现在占位的**所有权跟着请求走**(`pendingRef`:path → 写下它的世代):应答被丢弃时只回收**本次调用仍持有、且仍是空占位**的层(已有列表或错误的一律不动——丢弃的应答不得清空屏上的行),并**补发一次**该批请求,让丢弃自愈而不是等下一个 tick。
- 🧪 测试:`tests/editor-host.spec.tsx` 新增「a listing dropped by a refresh tick does not strand the level on Loading…」(首答延迟 300 ms、tick 在飞行中使其失效、tick 触发的强制重列人为挂死);去掉本修复该用例当场红并打印现场。
- 🖥️ **底部工作台可以同时开多个终端 Tab([#927](https://github.com/omdsh-dev/DSH-better-sidebar/issues/927),PR [#926](https://github.com/omdsh-dev/DSH-better-sidebar/pull/926))**:`terminal-bottom` 去掉 `single: true` 改为提供 `createTab`——每开一个 mint 一个 `terminal-bottom:` 的 id,并在 `meta.terminalInstance` 里记下自己的实例;宿主侧的出现键与内容身份都带上它(`dsh-better-sidebar:terminal-bottom:[#run]`),于是两个 tab **各自一个 shell**、刷新后各回各的进程,关掉一个只结束它自己那条。**旧 tab(`meta` 里没有 `terminalInstance`)读出来仍是 `undefined`,身份与改造前逐字节相同**,恢复绑定不丢。**维护者收尾**:`nextTerminalMeta()` 必须把实例一起带过去——`patchTab` 是**整份替换 `meta`**,只回 `terminalRun` 会让「新建终端」之后的 tab 掉回旧命名空间,于是两个都换过进程的 tab 撞在同一个 `…terminal-bottom#1` 上、被宿主(按 `(sessionId, key)` 记忆一个 model)发成**同一个 shell**。设计文档 `docs/plans/2026-10-05-bottom-terminal-tab.md` 的「一个终端」限制随之标注放开。
- 🧪 测试:`tests/terminal-tab.spec.tsx` +3(实例随「新建终端」保留、旧 tab 不长出实例;两个实例各自 `#1` 不撞、且都不等于旧的裸 `#1`;两个实例渲染出两个宿主终端与两条不同的 `(session,key,contentId)`),描述符关闭钩子补一条「带实例的 tab 只结束自己的终端」。判别性:把 `nextTerminalMeta` 退回只回 `terminalRun`,前两条当场红。(贡献者 PR [#926](https://github.com/omdsh-dev/DSH-better-sidebar/pull/926) @eleven-lings)
- 💬 **侧边对话补齐到接近原生:实时推送 + 排队可见 + per-thread 模型选择 + 底栏重构**(PR [#925](https://github.com/omdsh-dev/DSH-better-sidebar/pull/925);设计、真机证据与「哪些做不到」的清单见 [docs/plans/2026-10-12-sidechat-native-surface.md](docs/plans/2026-10-12-sidechat-native-surface.md)):
- **实时推送**:新增 `/sidebar/ws/sidechat`(`src/sidechat-push.ts`,客户端 `src/client/use-sidechat-push.ts`)。宿主把 `session/event`(60 ms 去抖)与 `agent/assistant-stream`(250 ms 去抖)压成**纯失效帧** `{threadId}`——不带事件体、客户端收到才带 `afterSeq` 增量重拉,协议面最小、单一事实源、断线自愈;每 socket 最多观察 64 个线程,空闲时**零帧**,重连指数退避 1→15 s,轮询降级为 15 s 兜底。
- **排队可见**:`sidechat.events` 增返 `pending`(`agent.inbox.nextStep/nextTurn` 投影,按 claim 顺序),队列在底栏可见;`sidechat.dequeue` 可按 id 撤一条、不带 id 清空整队。注入门控同时看 `boundaryDelivered` 与 `boundaryPending`——否则「首问还没被 claim 就连发第二条」会注入第二对 boundary + question。
- **per-thread 模型选择**:宿主对 `origin:'subagent'` 会话**显式禁用**自带选择器(`modelDirectories.assertAvailable`),`sessionController.selectModel` 也被 subagent fence 挡住,所以插件自建两条路由——`sidechat.models`(直通 `sessionController.modelCatalog()`,缺服务即 `available:false`,此时 UI 只提供「继承主对话」)与 `sidechat.model`(`ctx.get('llm').resolveCallConfig` 归一化后**同时**落 `session.append('model/selection')` 与 `installModelSelection(agent.ctx, ref)`,否则控制器缓存的 per-agent selection 永不回读投影;resume 后按日志里的有效选择重装)。
- **幽灵输入改为精确清扫**:冷恢复(`sidechat.prompt` → `agents.resume`)此前没有 create 路径那道 `inbox.clear()` 围栏。现在改为 `dropInheritedGhosts`——只折**继承前缀**得到幽灵 id 集合,再逐个 `agent.inbox.remove(id)`,精确且幂等,不会误杀「用户在线程里排队、宿主重启后尚未领取」的合法输入。同时确认幽灵的来源比原先记录的更宽:父会话 mid-turn 且工具有未返回调用时,种子被截断在 `turn/start` **之前**,而 inbox 的领取 splice 发生在 `turn/start` **之后**,所以父会话本轮开头已领取的输入在种子折叠里仍是 pending。
- 🧪 测试:新增 `tests/sidechat-push.spec.ts`、`tests/use-sidechat-push.spec.ts`、`tests/sidechat-transcript.spec.ts`;`tests/sidechat-core.spec.ts` / `tests/sidechat-routes.spec.ts` / `tests/sidechat-view.spec.tsx` 大幅增补(含真实 `Session.create` + 真实 `SessionProjectionRegistry` 的种子验证);挂载 lane 的 sidechat 宿主路由烟测同步。
- 🛡 **三处静默数据问题**([#898](https://github.com/omdsh-dev/DSH-better-sidebar/issues/898) / [#899](https://github.com/omdsh-dev/DSH-better-sidebar/issues/899) / [#900](https://github.com/omdsh-dev/DSH-better-sidebar/issues/900)):① 编辑器草稿守卫的记账键从**裸 tab id** 改成 **`会话::tab id`**——原生右栏每个会话的 id 都从 `tab1` 重新计数、底部工作台对同一路径在各会话复用 `editor:`,两套身份原先挤在同一个键上,**另一个会话打开编辑器就会把本会话的草稿记录一并删掉**(刷新时静默丢失);② 面包屑折叠行(`a/b/c` 压成一行)的重命名此前提交的是**链头**目录、与预填名不一致——不改字直接回车等于把父目录改名,现在落到链尾;③ 无 BOM 的 16 位小端数据(低幅采样表 / 未压缩音频数据段这类)不再被判成 UTF-16 文本打开、保存后整份改写(奇数长度还会丢尾字节):新的文本闸门要求 ≥90% 的单元是合法文本单元。同批还带了 dev 新增代码的一轮去重(内部导出 `runGitRaw` / `bottomTerminalKey` / `formatModelRoute` / `dirtyCountForSession` 收敛,仓内调用点同批改完)。(贡献者 PR [#901](https://github.com/omdsh-dev/DSH-better-sidebar/pull/901) @UNscientific-9)
- 🧪 测试:`tests/editor-unsaved-guard.spec.tsx`(跨会话草稿:第二个会话挂同一个原生 id,既不能清掉、也不能确认掉第一个会话的草稿)、`tests/file-tree-compact-fold.spec.tsx`(折叠行重命名断言落到链尾:`/tmp/a/b/c` → `renamed`)、`tests/text-encoding.spec.ts`(uint16LE 采样表 / WAV `data` 段 / 奇数长度判二进制,真 UTF-16LE/BE 仍是文本)。
- 🧷 **文件的重命名 / 删除现在作用于两个承载面的页签([#915](https://github.com/omdsh-dev/DSH-better-sidebar/issues/915))**:改名 / 删除后的页签调解此前**只遍历插件自己的底部工作台**(`pathTabsOf` 走 `state.bottomSplits`),而当前架构下文件**默认开在 DSH 原生右侧栏**——原生承载面一挂上,`service.openTab` 就直接走 native、**从不写 store**,那些页签因此从不参与调解。后果是数据面的:把开在右侧栏的文件改名,页签仍绑着旧路径,**下一次保存用 `mkdir -p` 把改名掉的文件重建出来**(磁盘上新旧两份、新名字那份是旧内容,界面还报「已保存」);删掉文件,页签仍在、保存把它复活,而且**连「有未保存改动,确定关吗」都不弹**。现在两个页签空间一起遍历:原生一侧由 `src/client/native/tab-adapter.tsx` 的 `NativeTabRecords.pathTabs()` 按路径枚举(原生 id **每会话各自计数**、多会话同号并存,所以每条都带上**自己的 seat 会话**与内容会话的 `cwd`),经 `SidebarSurface.pathTabs?()` 由承载面交回 service——与 `tabTypeOf?` 同一条缝,插件不再另持一份承载面句柄。策略同时补齐三处:**目录改名按子树搬移**(此前只做精确匹配,改了目录名之后 `dir/a.ts` 整条漏掉,而删除那条早就是子树口径,两条现在统一走 `isWithinWorkspace` + `relativeTo`)、**改名不再重载文档**(`path` 变化原本会触发编辑器重载,而改名并没有改字节——重载等于白丢只活在编辑器实例里的草稿;现在这次移动经 `markRetargetedPath` / `consumeRetargetedPath` 交给编辑器**认领**,文档与草稿都留着、下一次保存直接落到新名字,没有这个标记的 `path` 变化(真实切换文件)照旧加载;认领检查排在清空工具栏状态**之前**,否则会把「有未保存改动」的标记以及关闭前的确认一起清掉)、**删除照旧先走草稿确认**,拒绝即保留,关闭时带上该页签自己的会话。另有一处 **导航门控**:原生承载面每次渲染都会**重投同一次导航**,原来无条件按地址改 `path`,于是插件侧的改址会被下一次渲染当渲染垃圾丢掉——现在 `path` 与 `meta` 同闸门,**只被「尚未采纳的导航修订」覆盖**,真实导航(新修订)仍按地址改回。**聊天链接打开的页签也覆盖**:走地址的页签在工作区内是**相对拼写**(`src/a.ts`),而文件树给的是**绝对路径**,按原样比较会被整条判成「无关」——比较统一在按会话 `cwd` **解析后的绝对拼写**上进行,写回记录时写的是调用方给的新名(绝对拼写,即文件树那一侧的拼写),而改名标记里另存该页签自己的旧拼写、由它自己认领(`movedPath` 的相等判断交给 `relativeTo` 的 `.`,原先的字符串相等在 `E:/a` 与 `E:\a` 混拼时会漏出一个 `新名/.`)。**改名后的关闭确认按内容会话记账**:草稿注册表的键是 `sessionId::tabId`,而原生页签的座位会话与内容会话可达地不同(编辑器由文件地址解析出的 scope 驱动),所以调解把**内容会话**(`scopeSessionId`)而不是座位 id 传给 `confirmDiscardDraft`——传座位时只要两者不同就会查不到草稿、静默放行、把未保存改动丢掉(这正是 #901 修的那一类)。净效果:改动的 5 个源文件是 `src/client/tree-mutations.ts`(新增 `pathTabKey` / `consumeRetargetedPath` / `clearRetargetedPath`)、`src/client/EditorHost.tsx`(认领这次移动)、`src/client/native/tab-adapter.tsx`(`pathTabs()` 枚举 + 导航门控)、`src/client/service.ts`(`NativePathTab` / `SidebarSurface.pathTabs?()`,以及**只在内部使用的 `BetterSidebarService.nativePathTabs()`**——原生枚举**没有**当成消费 API 宣传:它带 `@internal`,而 `SidebarSurface` 的成员本就不在接入指南的公开方法表里)、`src/client/native/surface.ts`(把枚举转交给 service)。(贡献者 PR [#912](https://github.com/omdsh-dev/DSH-better-sidebar/pull/912) @UNscientific-9)
- 🧪 测试:`tests/editor-unsaved-guard.spec.tsx` 新增 `tree rename/delete reconciliation` 一组 7 例(真实 service + 真实原生承载面 + 记录仪控制器:文件改名改址 / **聊天链接的相对拼写**也改址 / **两个 seat 同号 id 的改名标记互不干扰** / **改名不重读文件且草稿仍在**(断言 `fsRead` 调用数不变 + 脏标记仍在)/ **目录改名按子树改道两个空间** / 删除文件时脏页签**先问** / 目录删除连子树一起关);`tests/native-surface.spec.ts` 新增 `native records: path enumeration and the navigation gate` 一组 2 例(枚举只含带路径的记录并跨会话 / **插件侧改址扛得住同一导航修订的重投**,新修订仍按地址改回)。(不再包含已废弃的 `tests/tree-mutations.spec.ts`——那是被取代的 PR #917 的新增面,#912 把用例并进了上面两个既有文件。)
- 🔁 **引用行号的四处收尾([#826](https://github.com/omdsh-dev/DSH-better-sidebar/issues/826) 后续)**:上一批把行号接进了编辑器,真机手测与代码审计在这条路上又抓到四类问题,一并收掉。
- **跳转后回不到预览**:`line` 的「已消费」记忆原本在 **viewer 组件实例**里,而 `EditorHost` 每次 reload 都用 loading 行把 viewer 整个重建——重建出的实例 `mode` 回到 `preview`、记忆为空,于是同一条行号再次强制切编辑;**而「预览」按钮本身就会 reload**(#167 part B 的编辑→预览重载),所以读者点下去必然被切回去,页头刷新 / #855 自动刷新 / 预览态保存后的刷新同理。记忆改由**宿主**持有(它比 viewer 活得久):viewer 落地后回调 `onLineConsumed()`,`EditorHost` 记住已消费的 `${path}#${line}` 并停止再下发(换文件清空、换行号自然重置)。公开面纯增量:`FileViewerProps.onLineConsumed?: () => void`。
- **插件自绘的预览里点 `#L131` 根本不跳行**:`MarkdownDelegate.openFile(path, { line })` 收到的行号只被当成「这是行号不是标题锚点」的判据,随后在 `use-markdown-surface` → `openSidebarFile` → `openTab` 三跳里被丢掉(种子没有 `line` 字段、原生分支的 `surface.openResource` 也没传,而末跳 `src/client/native/surface.ts` 本来就支持)。现在补齐三跳,并新增 `lineOfHostFragment` 兜住只有 destination 的调用方(区间取起始行);四处插件自绘 markdown(编辑器预览 / 侧边对话转录 / 文件变动阅读窗 / 任务窗口)从此与聊天正文行为一致:**跳到那一行 + 离开预览**。
- **换文件会继承上一个文件的行号**:`updateTab` 的补丁只有 title/path/meta,合并模式在页头换文件、以及文件树里重命名打开的文件,都把上一个文件的行号留在记录里——新文件跳到旧行号,markdown 还被拉进编辑态(与 reload 那条同症状,触发点不同)。规则统一成「**改 path 就是一次新的打开**」,两个记录层(`patchTab` 与原生 `records.update`)都清掉瞬态行号;只改 title / meta 的补丁不动它。
- **落地没有光标,还会被预览的阅读位置覆盖**:落地只 `dispatch({ selection })`,而 CodeMirror **只在获焦的编辑器里画光标**、零长度选择本身什么都不画——文件短时一页装得下又没有光标,读者无从判断落在哪。且「进入编辑态按预览滚动位置恢复」那条副作用**声明在落地之后**,同一次提交里会把选择与滚动重新写回读者刚才预览的位置:先在预览里滚动、再点带行号的引用,跳转被静默抹掉。现在行号引用这条路上 `view.focus()`(普通打开不动读者焦点,他可能正从聊天输入框点链接),并在落地前清空 `previewSyncRef`。
- 🧪 测试:新增 `tests/editor-line-nav-mode.spec.tsx`(4 例,**真 `TextEditor` 过真 `EditorHost`**:落地后能切回预览 / 页头刷新不回弹 / 同文件新行号仍落地 / 换文件不继承行号),`tests/text-editor-line-jump.spec.tsx` +6(落地获焦 / 普通打开不抢焦点 / markdown 翻转后仍获焦 / 消费契约两例 / 预览位置覆盖),`tests/markdown-surface.spec.tsx` +2(真实宿主 `MarkdownText` 下点 `#L42` 与 `#L20-L25` 进记录、标题锚点仍只走 parked 片段),`tests/native-tab-line.spec.ts` +2(换 path 清行号 / 只改 title 保留),`tests/markdown-navigation.spec.ts` +1(`lineOfHostFragment`),`tests/file-open-claim.spec.ts` +1(种子透传与「不传就不带这个键」)。判别性:去掉 `previewSyncRef` 清空 → 预览覆盖用例红(期望第 3 行的 offset 8、实得按比例算出的 14);去掉两个记录层的清行号 → 对应两条红。(回归报告 [#914](https://github.com/omdsh-dev/DSH-better-sidebar/issues/914);贡献者 PR [#916](https://github.com/omdsh-dev/DSH-better-sidebar/pull/916) @yanzhaohui1999)
- 💾 **带基线保存时目标已不存在判冲突([#911](https://github.com/omdsh-dev/DSH-better-sidebar/pull/911))**:文件读进来之后被删掉或改名走,此前会当成「没有基线可比」直接写回,`mkdir -p` 于是把用户刚删掉的文件(连同父目录)**无提示重建出来**,界面还报「已保存」。数字基线只可能来自一次成功的读取,所以「读不到」是冲突而不是新建意图——现在同样回 409 `fs-conflict`,文案分成「已不存在」与「已在磁盘上被改过」两种;新建走 `fs.createFile`、上传走上传路径,都不受影响。(贡献者 PR [#911](https://github.com/omdsh-dev/DSH-better-sidebar/pull/911) @UNscientific-9)
- 🧪 测试:`tests/fs-write-route.spec.ts` +2(删文件后保存 → 409 且文件不复活、无临时残留;父目录被删 → 409 且目录不重建)。
- 🧭 **改动块内以 `---` / `+++` 开头的正文不再被当成文件头([#910](https://github.com/omdsh-dev/DSH-better-sidebar/pull/910))**:删掉一行以 `-- ` 开头的源码(SQL / Lua / Haskell 注释、文档里的命令行参数行)在 git diff 里就是 `--- …`,与文件头分隔行**逐字节同形**——解析器据此把该文件**从这一行往后的改动整段丢掉**,而且不报错。现在两条头部分支只在**尚未进入 hunk** 时生效,并补了「真文件头跟在 hunk 之后仍能解析」的反证用例。(贡献者 PR [#910](https://github.com/omdsh-dev/DSH-better-sidebar/pull/910) @UNscientific-9)
- 🧪 测试:`tests/diff-rows.spec.ts` 3 例(删除的 `-- foo` 留在 hunk 内、新增的 `++ i;` 不改文件名、真文件头仍能开场)。
- 📜 **超大文件的预览改成 VS Code 那种追加式滚动**:第一版做的是「上一页/下一页」的显式翻页(新页替换当前页),现在改为**滚到底自动取下一页并拼接**——读者读过的内容不会被抽走,行号跨页连续,「加载全部」就是一路追加到 EOF(必须串行:每一页的 `offset` 就是上一页响应的 `nextOffset`)。超限提示条最右侧新增「加载全部」按钮,加载中显示已读字节进度,到 EOF 后自动置灰。
配套的**行数上限**(`MAX_PREVIEW_ROWS = 50_000`)是这里唯一 VS Code 不需要、而本插件必须有的东西:它的 `TextModel` 与视图都做了虚拟化,所以模型里塞进一个 GB、DOM 仍然很小;本插件渲染的是真实元素。到顶后停止追加并明确提示改用下载——看得见的天花板胜过卡死的标签页,且这个数远高于任何人在侧栏里真会读的日志(6 MB 参考日志约 3.5 万条记录)。上限由读取 hook 持有而不是视图按**已渲染**行数判断:`loadAll` 在 `await` 循环里推进时 React 尚未重渲染,读渲染态的数字会滞后好几页把上限冲过去(新 spec 直接抓到:worker 十秒内 OOM)。
> 📦 **上传、下载、HTML 预览与打包全部改为流式传输,不再有大小限制。** 三个部署限额键 `readLimit` / `mediaLimit` / `uploadLimit` 随之删除(旧 profile 里残留的值会被当作未知键静默忽略,无需迁移);`listLimit` 保留。
- ⚡ **下载不再有 20 MB 上限,宿主内存不再随文件增长**:`/sidebar/file` 与 `/sidebar/html` 从「整份 `readFile` → `res.end(buffer)`」改为 `createReadStream` + `pipeline`。关键在**写响应头的时机**——先等流的 `open` 事件再 `writeHead`,所以文件读不到(ENOENT/EACCES/目录)仍然回插件的 JSON 错误信封,而不是一个 200 加空体;提交之后再失败(文件被换掉、客户端断开)就 `destroy()` 掉连接,并给 `writeJson` 加了 `headersSent` 早退守卫,避免路由的 catch 块在已提交的响应上抛 `ERR_HTTP_HEADERS_SENT` 把「下载被截断」升级成「请求崩溃」。判别性用例见 `tests/file-stream-route.spec.ts`(21 MiB 真 `node:http` 下发、中途断开存活、不可读路径仍是 4xx 信封)。
- ⚡ **上传的两道 128 MB 闸门删除**:上传本就已经端到端流式(`for await (chunk of req)` → 临时文件 → 原子 rename),挡路的只是客户端的 `MAX_UPLOAD_BYTES` 预检与宿主的逐块 `limit` 检查,两者一并去掉;流式核心、失败清理、不留残file的保证一字未改。
- ⚡ **打包改由系统命令完成,不再自己写 ZIP**:删除手写的 300 行 ZIP writer(`src/zip.ts`,连同 `ZIP_MAX_BYTES` / `ZIP_MAX_ENTRIES` 两道上限),改为探测并调用系统的 `zip`(恒带 `-y`,否则会**跟随**符号链接、一个 `a -> .` 的自指环会一直递归)或 `tar`(Windows 10 1803+ 自带 `bsdtar` 而没有 `zip`)。压缩包的 stdout 直接写进 OS 临时目录,**从不整包进内存**,下载时流式下发、流结束后删除。`archive.status` 的 `done/total` 逐条目进度随之消失(子进程不报),状态收缩为 `{ state, bytes }`;工具都不存在时 `archive.build` 立刻回新的 wire 错误码 **`unavailable`**,界面显示一句可操作的提示。客户端的下载也从 `fetch → blob` 改回普通 `` 直链——Blob 会把整包放进标签页内存,正好抵消宿主侧的改造。
- ⚡ **编辑器保存拆掉 1 MiB 的墙**:`fs.write` 原走 JSON,而 `/sidebar/api` 的请求体上限是 1 MiB,所以「能打开的文件存不回去」。保存改为 `POST /sidebar/write` 的 raw 路由(**文档即请求体**),编码与行尾还原逻辑(#523 / #871 的两条保证)原样保留并由 `tests/fs-write-route.spec.ts` 继续钉住,另加一条「远超旧上限的一次保存」。
- 🧾 **超大文件从「截断成只读」改为「只读分页预览」**:`fs.read` 从「全读或截断」变成三态(`text` / `binary` / **`too-large`**)。超过编辑上限(5 MiB)的文本文件不再回半份文档,而是只带 4 KiB 嗅探头的 `too-large`,客户端据此渲染**分页只读预览**(等宽行列表,日志查看器形态)并提供下载出口;超过该大小的**二进制**走原来的 media 路径,行为完全不变。`FileViewerProps.truncated` 与 `truncation` 文案键一并删除——「拿到半份文档但看起来能编辑」这个状态本身就是 #732 的成因。新 `fs.readPage` 按字节窗口分页,窗口回退到最后一个换行,所以一页是整数行;超长单行原样返回而不是空页(否则「下一页」会停在一个永不前进的偏移上)。
- 🔧 **`.jsonl` 预览不再谎报「文件过大」**:预览态的 JSONL(小于编辑上限)走的是同一套分页记录视图,但顶部那段「文件过大,仅提供分页预览」对它是一句谎话——**阈值决定提示,不是组件决定**。`LargeFileView` 现在自己按 `MAX_EDITABLE_BYTES` 判断,一个 2 KB 的记录文件不再顶着超限提示条。
- 🆕 **`.jsonl` / `.ndjson` 有原生支持了**:此前它们既没有语法高亮(`lang.ts` 无映射 → 纯白文本),也不能简单地映射到 `json()`——`@lezer/json` 的文法只接受一个顶层值,实测 400 条记录的文件会解析出 **402 个错误节点**。现在有一个手写的逐行 JSON 词法分析器(一行一次、不携带跨行状态、未闭合字符串直接标错),新增第 4 个内置 viewer `jsonl`:**预览**是每条记录一张可折叠卡片(默认折叠成一行摘要,点开用宿主的 `JsonTree`),**编辑**是原样可保存的 CodeMirror,由预览/编辑开关切换。行引用(`a.jsonl#L131`)仍会先切到编辑档再落行。
- 🎨 **修掉「错误标记看不见」**:`tags.invalid` 在深浅两种配色下都是 `#ffffff` —— 在浅色主题里就是白底白字。它是 JSON/JSONL 语法错误落到的 token,读者本该能看出哪段没解析;现改为红色波浪下划线。
- 🔧 **`stat` 失败从 500 变 400**:两条字节路由原先让 `stat` 的原始 rejection 逃成「internal」500,读起来像插件故障;不可读的用户路径是 400。
- 🔧 **分页读取的编码一致性(一处只有真机才会发现的回归)**:第一版锁死了第一页嗅探到的编码,于是「长 ASCII 前缀 + GBK 正文」的文件第一页判成 UTF-8 后,后半段 GBK 字节被按 UTF-8 解成乱码——而一次性全读是能正确解出来的。现在**只锁宽字符编码**(UTF-16/UTF-32 是文件属性,一页恰好没有 NUL 不能重新裁决),字节序编码逐页各自嗅探(纯 ASCII 页在两种编码下解出来是同一个字符串)。这在分页按行对齐的前提下是安全的:`0x0A` 既不能是 UTF-8 的续字节,也不能是 GBK 的尾字节。双向用例见 `tests/fs-read-page.spec.ts`。
- 🧪 新守护:`tests/file-stream-route.spec.ts`、`tests/fs-read-page.spec.ts`、`tests/jsonl.spec.ts`、`tests/jsonl-language.spec.ts`、`tests/archive-tools.spec.ts`、`tests/archive.spec.ts`、`tests/editor-large-file.spec.tsx`(超限只读预览 + 可编辑反证)。`tests/zip.spec.ts` 随 writer 一起重写为 `tests/archive.spec.ts`;唯一一条真二进制用例自探测、缺席即跳过(CI 上没有 `zip` 的 Windows runner 不会红)。
- 📖 设计过程、与 VS Code Web 的对照(含「VS Code 并不分页,它的模型是阈值 + 确认 + 关闭 tokenization」这一条纠错)以及实施偏差记录见 [docs/plans/2026-10-11-streaming-transfer-jsonl-preview.md](docs/plans/2026-10-11-streaming-transfer-jsonl-preview.md)。
- 🃏 **任务卡小条只说原生状态**:运行中小条不再是「● 运行中 正在读取文件」这样把状态念两遍,而是**活动行取代状态词**——`正在分析请求` / `正在读取文件 · main.ts` / `正在准备读取文件` / `正在运行命令 · npm run build` 等措辞全部来自宿主 `chat` 命名空间,与主对话逐字一致;已结算小条同理(只留 `已读取文件、执行了命令`,不重复「已完成」)。**`空闲` 与 `出错` 保留状态词**(「待命」「失败」不被任何活动措辞蕴含),**完全没有活动数据时回退状态词**,小条永不空着。状态点、执行扫光与折叠 chevron 不变,小条仍是 22px——画布几何与卡片高度预算零改动。
- 🧩 **主代理卡片接上自己的实时数据**:`subagents.live` 路由一直把拓扑根的 fold 结果放在 `live[rootSessionId]`,而模型从未把它挂到根节点上——于是**主代理卡片的状态文本恒为 thinking 兜底**(「正在分析请求」),与主会话在做什么无关。现在根卡显示真实状态,树模式根行的实时行也从恒定的「思考中…」变成真实活动。
- 🔎 **节点详情浮窗的活动行移到标题下方**:那行原来在键值格里(`最近活动 | 值`),右对齐的值列在 280px 里被省略,标签又占掉三分之一宽;现在它是标题下的一行 10px 小字(宿主措辞),实时文本尾行紧接其下并夹到两行。**没有活动数据就不渲染这一块**——此前每个节点浮窗都会显示「最近活动:正在分析请求」,已结算节点也不例外。词条 `tasksNodeActivity` 随这一行从 20 份词典删除。
- 🗂 **聚合卡改为「按组展开」**:点一个聚合卡只展开**该组**(`fold:` / `fold::idle`),兄弟组、页级折叠与手工折叠都不受影响——此前一次点击会清空手工折叠集并关掉全局规则,等于**点一个全开**。控制条按钮仍是页级展开/折叠,且**收起时清掉按组展开**(全局收起必须真的收起);树里的聚合行同一语义,行内副文案固定为「展开」(该行只在折叠态存在)。
- 🔢 **聚合卡不再两处写计数**:待命组的徽标此前印的是**字面量字母 N**(`tasksFoldIdleGroup` 在 20 份词典里都是 `'N waiting' / 'N 个待命'`,没有 `{count}` 插值)——现在插值出真实计数,小条只保留`出错 N` 尾注与展开箭头;完整 tally 仍是卡片的 `aria-label` 与 tooltip。
- 🎨 **树模式实时行的图标按活动类别取**:`tool-icons.tsx`(工具名映射,只有一处写死的调用)换成 `activity-icons.tsx` 的 `activityGlyph(kind)`——读文件 / 搜索 / 编辑 / 命令 / 网页 / 子代理 / 计划 / 提问 / 通用工具各用自己的宿主图标,尚未派发时是思考图标;`Record` 让上游新增类别编译失败而不是静默兜底。
- 🧪 测试:`tests/tasks-page.spec.tsx` 新增一个 `FakeChatLocale`(挂宿主 `chat` 命名空间,否则只能断言到 fallback 的 detail)并补 5 条(原生状态取代状态词、无数据回退、根卡真实状态、浮窗活动块位置与「无数据不渲染」、一次只开一组+全局收起折回、徽标真实计数);`tests/tasks-model.spec.ts` 补 4 条(按组展开的双向隔离、组 id、`expandedGroups` 盖过残留手工折叠、根节点带 live);新增 `tests/activity-icons.spec.ts`(类别全覆盖 + 读/命令/搜索/网页互不同型 + `undefined` 走思考图标)。[设计文档](docs/plans/2026-10-11-tasks-status-and-group-fold.md)。
- 🔌 **预览插件目录收录音频预览插件**:[`@huanlin/dsh-plugin-better-sidebar-plugin-audio`](https://github.com/HuanLinOTO/dsh-plugin-better-sidebar-plugin-audio)——设置页「添加插件」弹窗的预览插件目录里可一键复制安装命令(20 份词典同步 `pluginAudioName` / `pluginAudioDesc`,条目写进 `tests/plugin-list.spec.ts` 的目录断言,README 的预览插件表格 3 → 4)。(贡献者 PR [#881](https://github.com/omdsh-dev/DSH-better-sidebar/pull/881) @HuanLinOTO)
### v0.25.0
> 📦 **支持线前移:仅适配 DSH `0.2.0-rc.2+`**(peer 下限 `^0.2.0-rc.2`,CI 与挂载 lane 钉 `@deepseek-ai/dsh@0.2.0-rc.2`)。DSH 的 npm `latest` 现在正是 **0.2.0-rc.2**(不再停在 0.1.7-rc.2),本版就钉在它上面。**0.2.0-rc.1 及更早(含 0.1.7 线)请固定 v0.24.1**——`^0.2.0-rc.2` 对 rc.1 判定为 false,会被宿主的启动兼容性预检整行静默禁用;0.1.7 线的最后支持版是 v0.22.1。本次前移是一次纯声明与钉版变更:0.2.0-rc.2 对本插件消费的宿主面仍是纯增量,运行时零改动。
> 🙏 **本版包含以下贡献者的工作**(按合并先后;括号内为其 PR 带来的能力):
> [@NolanHo](https://github.com/NolanHo)(#880 搜索框粘贴路径直开 + 噪声目录名单、#884 子进程输出偶发清空)、
> [@yanzhaohui1999](https://github.com/yanzhaohui1999)(#865 文件引用落到那一行、#828 行号后缀切分)、
> [@finley08](https://github.com/finley08)(#890 编辑器显现后重新测量、#859 桌面壳 HTTP 走页面基址、#832 新建文件)、
> [@ZinChl](https://github.com/ZinChl)(#806 目录命中点击、#809 打包状态按会话、#811 树批量拆分、#813 重启后的子代理、#814 任务折叠守卫)、
> [@graydovee](https://github.com/graydovee)(#807 空白会话的底部入口)、
> [@btsd321](https://github.com/btsd321)(#818 tab 条右侧动作)、
> [@ZhangFengshun](https://github.com/ZhangFengshun)(#641 宿主加载不带会话日志偏移)、
> [@PerryLink](https://github.com/PerryLink)(#843 目录条目清理)、
> [@UNscientific-9](https://github.com/UNscientific-9)(#842 checkout 的 `--end-of-options`、#645 引用胶囊)、
> [@lpf20200901](https://github.com/lpf20200901)(#821 插件目录 memory-delta)、
> [@dat-lequoc](https://github.com/dat-lequoc)(#837 编辑器可见性重测量)、
> [@lhh010](https://github.com/lhh010)(#823 插件目录 file-trace)、
> [@MengJiapeng](https://github.com/MengJiapeng)(#803 多仓库 git 历史)、
> [@zhanghunuaa](https://github.com/zhanghunuaa)(#802 文件树跟随活动 worktree)、
> [@mengge237](https://github.com/mengge237)(#817 自动激活保留已开页)、
> [@Waser750](https://github.com/Waser750)(#840 侧边对话归档线程)、
> [@suiyideali](https://github.com/suiyideali)(#587 编辑器内查找)、
> [@wang-kaopu](https://github.com/wang-kaopu)(#612 WSL 外部打开、#523 非 UTF-8 编码、#566/#565/#521 Windows 路径)、
> [@nyantused-cpun](https://github.com/nyantused-cpun)(#600 编辑器草稿保护)、
> [@bulingbuling688](https://github.com/bulingbuling688)(#574 工作区提及)、
> [@ingeb0rga](https://github.com/ingeb0rga)(#845 IME 229 守卫)、
> [@shyuan-hub](https://github.com/shyuan-hub)(#264 面包屑折叠与排除模式,方向)、
> [@rijiu915](https://github.com/rijiu915)(#363 Markdown 跨文件锚点,方向)。
> 谢谢每一位!
- 🧷 **右侧面板展开时不再显示底部入口**:宿主右上角在那种状态下是它自己的一簇控件(Split / Fullscreen / Collapse right sidebar),插件的「展开底部面板」挤进去只会跟它们打架 —— 现在右侧栏一展开,头部入口与空白会话的备用入口都退场,收起后重新出现。这条规则把「展开态该把入口摆在哪儿」整个问题去掉了,让位逻辑只需服务收起态(那里宿主的角落只有一个按钮)。**判据必须按「可见」而不是「在不在 DOM 里」**:实测宿主把两个角落控件都留在文档里(落屏时 `data-sidebar-right-expand` 与 `data-sidebar-right-toggle` 各 1 个、而面板已经展开),只查属性会一直读成「没展开」、按钮照旧冒出来。判别性用例见 `tests/dock-fallback.spec.tsx`(造一个可见的 Collapse 控件,还原门即红)。
- 🧷 **再修一处同源的碰撞:右侧栏打开时,空白会话的底部入口压在宿主「收起右侧栏」上**。备用入口是按宿主**角落控件**的 rect 左移 8+28px 定位的,但那只认控件**关闭形态**的属性 `data-sidebar-right-expand`——右侧栏**打开**时这个属性整个消失、同一个座位换成 `data-sidebar-right-toggle`,于是定位落到视口兜底坐标(`innerWidth - 44`),而那里正是宿主自己的角落按钮群。实测 2200px 视口、右侧栏打开时:入口落在 `2156..2184`,与宿主「Collapse right sidebar」(`2166..2194`)**重叠 18px**、并切到「Fullscreen」2px——与用户截图逐字一致。现在锚点同时认两种形态,**并且让位不再是一个固定偏移**——宿主的角落控件是肩并肩一簇(打开态实测 `Split@2094 / Fullscreen@2130 / Collapse@2166`,各 28px、间隔 8px),只往左挪 36px 会精确落在**下一个**按钮上(实测压住 Fullscreen 28x28px),所以改为逐个槽位左移直到与**任何**宿主按钮都不相交。真机复测(2200px 视口):关闭态入口 `2124..2152`、宿主 `Open right sidebar@2160`,打开态入口让到 `2058..2086`、整簇从 `2094` 起——**两种状态都零重叠**。**占位判定改用命中测试**:早先按 `button, [role="button"]` 筛「谁占着这一格」,而右侧栏的**页签芯片**不是这两者——页签一多,让位就停在芯片上(用户报的「tab 多的时候位置也错误」)。现在用 `document.elementsFromPoint` 直接问页面「这一格下面有什么」:**不透明的小控件**(≤300px 宽,含页签芯片)算占位,整行的背景板(会话头 titleRow、面板 tabStrip、面板本体)不算;并且沿水平方向采 **5 个点**——只测中心点会漏掉「中心空着、右缘却压着邻居」的情况(实测这样压住 Split/Fullscreen)。让位步长同时收到 4px。判别性用例见 `tests/dock-fallback.spec.tsx`(还原锚点即红 `expected '980px' to be '464px'`;关掉让位步进即红 `expected '464px' to be '428px'`)。
- 🧷 **修掉「展开底部面板」按钮在会话头与备用入口之间叠成两个**:blank 会话里宿主不渲染会话头,插件的会话头入口随之不可达,于是空白会话专用入口以 `position: fixed` 贴到宿主「Open right sidebar」按钮左侧(#698 / #623)。但**相位判定不足以互斥**——相位是每 1s 轮询来的,而会话头入口由宿主自行决定何时渲染,两者之间必然存在窗口(相位读取失败时还会按「非 blank」降级,一个从未成功读到的会话可能一直停在 blank);而两套入口在版面上**就是同一个位置**,同时在场即精确重叠成一团。现在除相位外再加一条与相位无关的兜底:**会话头那套入口只要真的在 DOM 里,备用入口就让位**,判别性用例见 `tests/dock-fallback.spec.tsx`(造出「相位说 blank + 会话头入口在场」的不一致状态,还原守卫即红)。最多 500ms 的让位延迟(与位置对齐同一节拍)。
- 🎯 **文件引用终于落到那一行([#826](https://github.com/omdsh-dev/DSH-better-sidebar/issues/826) 第二半)**:第一半把 `path:131` 的行号从地址上切了下来、文件能打开了,但行号**被解析出来就扔掉**——点开停在第 1 行。现在两种写法合流到同一个字段:`#L131` 由宿主解析成 `navigation.params.line`,`path:131` 由插件在 `src/client/path-line.ts` 切出,都落到 `SidebarTab.line`,再经 `FileViewerProps.line` 交给 viewer;文本编辑器在**到达时定位一次**并给编辑器焦点(CodeMirror 只在获焦时画光标),越界行号钳到最后一行,区间(`:起-止`)只取起始行、列号丢弃(编辑器只有一处光标)。定位直接写 `view.scrollDOM.scrollTop` 而不是 `scrollIntoView`——后者会走遍每一层可滚动祖先,浏览器缩放时连 window 都算上、把整个侧栏拖上去。**`line` 是瞬态**:`sanitizePersistedTab` 的白名单不含它(与 `revealed` 同处理),链接可能是几天前写的,刷新后不该把读者拽回那一行。**markdown / html 收到行号会先强制切到编辑态再定位**——预览里没有源码行号可放,而它自己挂载时的滚动恢复(`useLayoutEffect`)会和跳转抢同一个 `scrollTop`;引用要的是源码,所以让源码先可见。**公开面纯增量**:`FileViewerProps.line?: number`(§5 已同步),第三方 viewer 忽略它即可。原地把 `a.ts:131` 切到 `a.ts` 时行号会被**清掉**(更新路径比的是当前值而不是「有没有传」),否则每次通知都会把读者弹回去;同一引用重复投递不重复跳,同一文件的新行号照跳。
- 🧪 测试:`tests/native-tab-line.spec.ts`(6 例:记录铸造 / 宿主 `#L` 片段 / 普通打开不种子 / 新导航改行 / **裸路径清行** / 重复投递保住记录身份)、`tests/text-editor-line-jump.spec.tsx`(6 例:落到指定行 / 越界钳位 / 普通打开不跳 / **markdown 强制切出预览** / 无行号留在预览 / 只落一次),`tests/native-surface.spec.ts` 的 `fileParamsOf` 断言补上 `line` 并加区间与 `行:列` 两例。scroll 那一步刻意不断言(骑在两层嵌套 rAF 之后、且 jsdom 不排版),断言的是同一次 dispatch 的 selection anchor。(贡献者 PR [#865](https://github.com/omdsh-dev/DSH-better-sidebar/pull/865) @yanzhaohui1999)
- 🖥️ **桌面壳里插件路由不再满屏「Failed to fetch」**:HTTP 与 WebSocket 需要**不同**基址,而壳正是把两者拉开的地方——页面在 `dsh-app://app/`,注入的 `__DSH_TRANSPORT__.streamBaseUrl` 却是宿主的 `http://127.0.0.1:`(另一个 origin)。壳的 `protocol.handle` 会把非静态的 `dsh-app://app/*` 路径**同源转发**给宿主,所以 HTTP 必须骑**页面基址**(`hostHttpBase()` → `document.baseURI`,反代目录前缀也带在里面,一套规则覆盖两种部署);发到注入 origin 就是跨源,而插件自己的 `/sidebar/*` 路由不带 `Access-Control-Allow-*`,浏览器直接拒(文件树 / git / 编辑器全部请求失败)。WebSocket 反过来,必须用注入 origin:自定义 scheme 承载不了 `ws:`,而 `dsh-app://app` 的 host 是字面量 `app`,DNS 永远不解析。`tests/base-path.spec.ts` 钉住三种场景(壳内页面基址 + 注入 origin 的 socket、无页面基址时回退注入基址、普通 http 页面同源不变)。
- 👁️ **被 park 过的编辑器不再是一片空白**:原生页签在宿主里靠 `keepMounted` 存活,宿主把不活跃的 body **移动**进隐藏 seat、需要时再移回来——而移动一个节点**不改变它的 border box**,所以只看尺寸变化的 `ResizeObserver` 一次都不触发,一个在隐藏宿主里被打开的编辑器会把视口量成 0 并把这个空的虚拟视口一直留着(症状很有辨识度:**行号槽照常画,正文空白**)。现在每次「显现」都重新测量:`IntersectionObserver` 报显现本身(补上移动这条 ResizeObserver 看不见的路径),`requestAnimationFrame` 兜住「显现与挂载同帧」的那一次(此时 CodeMirror 的首次测量仍可能读到 0),另接 `visibilitychange` 与 `window` resize;四种观察器都可缺省、逐个 dispose,旧浏览器/测试环境不受影响。
- 🛡️ **会话切换不再把上一个项目的写操作带进新项目([#848](https://github.com/omdsh-dev/DSH-better-sidebar/issues/848))**:右侧栏的每个 page 都按「会话 + 页签 id」**复用同一个组件实例**,而页签 id 每会话重新计数——换会话(或换 cwd)时组件是**原地重渲染**而不是重挂,于是上一个项目里选中的行、打开的行菜单、内联重命名/新建编辑器、以及**已经弹出来的删除确认框**都会活到下一个项目里。最危险的是选中集:「删除选中」会把**新会话的 scope 配上一个会话的绝对路径**发给宿主,而 v0.23.0 起包含性检查是刻意移除的,宿主对绝对路径照单执行——**它会删掉另一个项目的文件**。现在这些状态随会话与 cwd 一起失效(选中集、锚点、行菜单、内联编辑器、确认框、拖拽落点、busy 标志、错误条),批量删除与打包构建按 generation 捕获自己的 scope、并把**结算**(pruneTree / 选中集 / busy / 任务条)在换代后退役(它们结算的是「在屏项目」的状态,已经不一定是自己开始的那个项目了)。
- **一并修掉两处「靠 passive effect 兜底」的窗口**:换会话的清理写在 `useEffect` 里,而点击可以落在「swap 已提交、effect 尚未 flush」之间,此时 props 已经是新会话。单行删除的确认框与拖拽上传因此改为**在调用点复核自己 armed 时的 scope**(`sameScope`),不一致就整条请求不发(fail closed);请求本身用 armed scope,scope 与路径永远同源。实测还原守卫即红:那个窗口里会发出 `fsRemove({sessionId:'s2',cwd:'/projects/beta'}, '/projects/alpha/a.ts')`。
- 🧪 测试:新增 `tests/file-tree-session-scope.spec.tsx`(6 条:选中集/确认框随换代清除、同会话重渲染**不**清除、批量走的在途结算不写进新会话、上面那条窗口的判别性用例、以及一条钉住「请求里的 scope 与 path 必须同源」的正向对照——只数调用次数抓不到 scope 被换掉的回归)、`tests/task-window-delete-arm.spec.tsx`(3 条:任务窗口的两步删除按钮不跨任务武装)、`fs-operations` / `git-view-worktree` / `file-tree-archive` 各补相关用例。**已知未修(另立跟进)**:宿主侧 `fs.remove/rename/mkdir` 没有项目归属校验,客户端仍是唯一守卫;`isWorkspaceRoot` 的 `catch` 会吞掉 EACCES/EPERM 而回退到拼写比较。
- 🪟 **保存不再把 CRLF 文件写成 LF([#871](https://github.com/omdsh-dev/DSH-better-sidebar/issues/871))**:上一批只修了读取侧,写入侧仍有洞——编辑器传送的永远是 LF 文档(CodeMirror 没有别的行模型),而 `fs.write` 把它原样落盘,于是在 Windows 检出上**改一行 = 整文件 diff**。现在保存前用**同一趟** 64 KiB sniff 读取同时取出编码与行尾(`text-encoding.ts` 的 `encodingOfFile` 扩成 `fileFormatOf`,新增 `detectEol` / `restoreEol`),落盘改为 `encodeText(restoreEol(content, eol), encoding)`:CRLF 文件保持 CRLF、LF 文件不被引入 `\r`、已含 `\r\n` 的内容不会被写成 `\r\r\n`。口径与宿主自己的 `edit` 工具逐字一致(前 4096 字符多数票;孤立 `\r` 既不计票也不改写),所以插件保存与模型 `edit` 对同一个文件得到同一个结论——顺带核实:宿主的 `write` 工具**不做**这件事(`writeText` 原样写模型文本),模型整份覆盖时文件仍会变成 LF,那是宿主写入、插件无权干预,已在 issue 里留结论。**客户端零改动**,保存入口(含快捷键与草稿边沿)自动全覆盖。新文件仍是 UTF-8 + LF:「项目级行尾约定」不在本批范围。
- 🧪 测试:`tests/text-encoding.spec.ts` 补 3 条新用例(CRLF/LF 检测、多数票与孤立 `\r`、`restoreEol` 不双写)并把既有的编码三例扩成 `fileFormatOf` 断言;`tests/fs-write-route.spec.ts` 补 3 条端到端(CRLF 文件写 LF 文档后除被编辑行外全 CRLF 且无裸 `\n`、LF 文件不引入 `\r`、GBK + CRLF 组合下两个 restore 同时开火)。判别性:把 `restoreEol(...)` 换回 `content` → 2 条端到端红,其余仍绿。
- 🔎 **搜索框支持直接粘贴路径:指名路径 stat 直开、相对片段按路径匹配**([#879](https://github.com/omdsh-dev/DSH-better-sidebar/issues/879)):`fs.search` 之前把整个输入当作文件名子串在整个 cwd 树里递归匹配——文件名永不包含分隔符,所以粘贴任何带 `/` 的路径都是**结构性零命中**,却仍烧满 10 万条访问预算(实测含 worktree 森林的 Rust 仓库上 1.9s 返回 `{matches:[],truncated:true}`,工作区外文件如 `/tmp` 下的实验产物图因此在右侧无从预览)。现在 `searchFiles` 按查询形态分流:**指名单一目标的 query(绝对路径、`~/…`、`./x`、`../x`)经 `resolveTarget`——与 fs.read 同一解析原语(`~` 展开 #713、会话 cwd 拼接、win32 WSL/dsh-remote 命名空间改写、词法 resolve)——stat 直取**,命中返回**解析后的绝对路径**(resolve 剥掉 shell 补全带的尾分隔符;客户端对匹配行与树行做精确字符串比对,客户端 `resolveSidebarPath` 对绝对路径透传,TreePanel 现有点击链路零改动即可打开),目录目标按 #801 契约同列 `dirs`(行内树导航而非按文件打开;**工作区外的目录命中行只清空查询**——树以 cwd 为根,没有可导航的行,客户端守卫同时避免了 `ancestorDirs` 为外部路径合成垃圾 `expanded` 条目),目标缺失立即返回空结果(不再烧预算换一个注定为空的遍历);**含分隔符的相对片段按根相对路径匹配**(大小写不敏感,Windows 两种分隔符都收;同名文件消歧正是 #306 的诉求)。引擎不参与路径分支——fd 的 `--fixed-strings` 不带 `--full-path`、rg 的三个 glob 以单路径段为匹配单元,路径语义由遍历独占,引擎 argv 构造零改动(新 skip 名仍由 `SKIP_DIR_NAMES` 派生进 fd `--exclude` / rg glob,`check:engines` 门禁不受影响)。用户 exclude 列表不作用于直开分支:粘贴路径是显式意图,与点击后的 `fs.read` 行为一致(fs.read 同样不应用 exclude)。POSIX 上 `\` 仍是文件名合法字符而非分隔符(`~\foo` 形态仅在 win32 计入路径手势);单独的 `~` 按客户端 `isAbsolutePath` 的口径视为指名 home 目录,其余不带分隔符的 `~`/点前缀拼写(`util.ts~`、`~$doc.docx`、`.env`)仍是名字查询,引擎资格不受影响。
- 🧹 **噪声目录名单补 worktree 森林与语言环境**:`SKIP_DIR_NAMES` 增加 `.worktrees` / `.worktree`(两种 git worktree 布局拼写,`.worktrees/` 正是 DeepSeek Harness 自己的约定)、`target`(Rust / Maven 构建产物)、`venv` / `.venv` / `__pycache__`(Python 环境与字节码缓存)——此前两片 worktree 森林实测可烧掉 100k 预算的 97k,真实目录从未被遍历、`truncated` 恒真。walk 与两条引擎 argv 共用同一名单(fd `--exclude` 与 rg glob 对自动派生),引擎与遍历的跳过行为保持一致。
- 🧪 测试:`tests/fs-search.spec.ts` 新增 9 条——路径片段匹配(深度无关 / 大小写 / 目录行的 dirs 拆分 / 噪声围栏照常生效)、直开分支(文件与目录目标、目标缺失即空且 `maxVisited:1` 下仍 `truncated:false` 钉住「未走遍历」、尾分隔符 / `./` / `../` / `~` 经 `resolveTarget` 解析后返回解析路径)、`probed===false` 证明路径分支不触引擎、不带分隔符的 `~`/点前缀拼写仍是名字查询(引擎资格保持)、分隔符 query 不进引擎(假引擎返回污染行判别)、POSIX 反斜杠名字(探针式 skipIf 只在名字可存在的平台运行)、worktree/语言环境森林不进入;`tests/smoke.spec.ts` 新增 1 条路由级用例(绝对命中 / 立即空结果 / 相对片段 / `./` 锚定解析四态穿真实 mount);`tests/file-tree-search-persist.spec.tsx` 新增 1 条(工作区外目录命中行只清空查询、不触发任何 toggle)。判别性:还原 `fs-search.ts` 的分支即红(引擎污染行会浮出来);还原 skip 名单即红(森林内的 guide.md 命中);拿掉客户端守卫即红(外部路径的垃圾 toggle 浮出)。
- 🐞 **修掉 `streamLines` 以 `exit` 结算子进程输出、负载下 fd 目录结果偶发静默清空**([#883](https://github.com/omdsh-dev/DSH-better-sidebar/issues/883)):Node 的 `exit` 在进程死亡**瞬间**触发,stdout 管道可能还有未排空的数据;正确信号是 **`close`**(进程结束**且 stdio 全部关闭**,stdout 读到 EOF、readline 的全部 `line` 事件已发出)。宿主负载高时 `exit` 抢在管道排空前到达,`lines` 数组还是空就 resolve——fd 双调用的第二路(`--type d`)最常受害,签名恒为 **`matches` 完好、`dirs: []`**,目录命中行退化为按文件打开(报 `is a directory`,正是 #801 修的错误面被偶发化)。实证(装 fd 的 Linux 宿主,`tests/search-engines.spec.ts` 单文件连跑):干净 dev 基线 **7/30 复现**、签名恒一;`exit`→`close` 一行后 **0/30**。截断 / 超时 / spawn-error 三条既有结算路径换事件后语义不变(`finish` 幂等守卫 + `if (truncated)` 分支照旧);探测函数的 `--version` 调用是 `stdio: 'ignore'`、只读退出码,`exit` 即正确信号,不改。上游 CI 从未暴露的原因:ubuntu lane 无 fd(真引擎用例自跳过,双 spawn 路径不存在)、ci-windows 串行跑撞不进竞态窗口——#872 记录的「慢机/负载才暴露」类隐患。
- 🧹 **`runChild` 的 fd 分支不再吞掉第二路的失败**:删除 `streamLines(dirChild, cap).catch(() => ({ lines: [], truncated: false }))`——rejection 现在流进 `runEngine` 既有的 broken 标记 + 本次回退 JS 遍历(与主路对称;旧 catch 把失败降级成「目录恰好为零」,在所有环境都不可见)。
- 🧪 测试:`setEngineHooks` 新增 `spawner` 缝隙(类型收窄为 `EngineSpawner`),两条**确定性**用例用脚本化 PassThrough 子进程直接钉住契约——「`exit` 先于 stdout 排空时零行丢失」(复放竞态的真实次序:先 exit、数据在途、close 后至;exit 版代码在「close 前仍未结算」断言即红)与「fd 目录路失败整路 reject 并标记 broken」(swallow-catch 版代码红在「以空 dirs resolve 而非 reject」)。判别性均已临时还原对应实现验证:还原 `exit` → 用例 1 红;还原 catch → 用例 2 红。真引擎用例('matches literals…')此前正是该竞态的偶发受害者,负载机器上 ~23% 概率红。
- 🪟 **修掉 CRLF 行尾文件在预览里的三层 `\r` 误判与「预览渲染两遍」(ci-windows 上暴露)**:Windows 检出下 README 的锚点用例全红,根因不是 slug 而是① `markdown-html.ts` 的 `isBlank`、`mermaid-blocks.ts` 的 `CLOSE_FENCE_RE`、`markdown-code.ts` 的 `closesFence` 都把行尾的 `\r` 当内容——于是 **HTML 段永不终止**(实测 `splitHtmlBlocks(README.md)`:LF 36 段 vs CRLF **4 段**,`# b` 被当原始 HTML 文字渲染)、**mermaid 围栏吞掉文件余下部分**、**链接改写从第一个围栏起全部失效**(首个分歧在第 16883 字节);② `TextEditor` 的 draft 快照把 CodeMirror 归一化后的 LF 文档在挂载后一个 commit 发布出去,`previewMdText` 从 CRLF 翻成 LF,宿主 `MarkdownText` 于是重建了文本变化的标题节点——挂载那一趟写入的 **26/26 个 id 被丢弃**(断言时只剩 7/26、13/15 个 TOC 锚点 `id=missing`),MutationObserver 一个微任务后才自愈。现已把 draft 定义成「编辑」:与已加载正文逐字节相等(或行尾归一后相等)时不发布。
- 🧪 测试:新增 `tests/markdown-crlf.spec.ts`(7 条 **CRLF ≡ LF** 不变式,覆盖分段器 / mask / 两个改写器 / mermaid 分段器)与两条真实渲染器用例(含把 `README.md` **在内存里**转成 CRLF,让 ubuntu / macOS lane 也跑 Windows 的拼写);测试脚手架 `scanDocumentAnchors` 改为 CRLF 安全(实测 CRLF 标题 0 → 26)。判别性:还原三个扫描文件 → 9 条红;还原 `TextEditor` → CRLF 检出下 2 条红(即 CI 原症状)。
- 📝 **已知未修(另立 [#871](https://github.com/omdsh-dev/DSH-better-sidebar/issues/871))**:同一归一化意味着**保存会把 CRLF 文件写成 LF**(一次内容修改变成整文件 diff),需要产品取舍;本轮只修读取侧。
- 🔄 **模型改完文件后,正在看的预览自己刷新**([#855](https://github.com/omdsh-dev/DSH-better-sidebar/issues/855)):此前模型在会话里 `write` / `edit` 一个你正开着的文件,侧栏里仍是旧内容,只能手点刷新(或切走再切回、关掉重开)。现在**命中即静默重载**:走页头刷新按钮那条完全相同的加载路径(同样的 `fsRead` / `load()`),因此手动刷新入口原样保留。信号来自**插件自有的会话事件增量流** `changes.ops`(不是新的文件系统观察者):宿主按 `seq > afterSeq` 增量下发 `tool/call` + `tool/result`,客户端折成文件操作后只认**结算成功**的 `write` / `edit`(`running === false && !isError`)——半写内容与失败的调用都不触发重载。**每会话一个共享轮询器**(`src/client/ops-stream.ts` 的成员选举,2.5s、self-scheduling,与文件变动页的会话透镜同一节奏、同一路由):一个会话开 N 个编辑器 tab 每 tick 仍只有 **1 个** `changes.ops` 请求,页签全部离开即停;**没有 fs.watch,也没有任何周期性的 `fsRead` 指纹轮询**。**叠加口径**:文件变动页的**会话透镜保留它自己的轮询器**(两条流的可见性条件不同),所以同一会话**同时**开着会话透镜与编辑器时,该会话每 tick 是 **2 个** `changes.ops` 请求——「1 个」说的是编辑器这一份,不是全会话总数(把两者合并会改动会话透镜那条已被既有 spec 钉住的「只在透镜在屏时轮询」语义,见[设计文档 §4.1](docs/plans/2026-10-06-editor-auto-refresh.md))。
- **只刷新命中的那一个**:命中判定用 `resolveSidebarPath` 把模型写的相对路径按会话 cwd 归一(含 `.` / `..` 词法折叠、Windows 盘符与 UNC 大小写折叠),未命中的文件**零重读**、不重挂、不动滚动位置。
- **脏态优先**:编辑器上报 `dirty` 时自动刷新**直接跳过**——草稿只活在编辑器实例里,重载会重挂它,用户没保存的输入永远不会被磁盘内容覆盖;处于**编辑态**(`mode === 'edit'`)同样跳过,免得光标跳走。**跳过不等于死路**:未 dirty 时既有的「编辑 → 预览」边沿照旧重载并拿到新内容(实测 `fsRead` 1→2,`tests/editor-refresh.spec.tsx` 的 B 例);**dirty 时这条边沿自身也带 dirty 守卫**(`EditorHost.tsx` 的边沿判定要求 `dirty !== true`;`tests/editor-refresh.spec.tsx` 的 B 例两半都钉住了它——干净时 `fsRead` 1→2,dirty 时仍是 1)因而被抑制,此时的两条出口是**页头刷新按钮**(`window.confirm` 后重载,draft 换成新内容)与**「保存 → `fs-conflict` 横幅的 reload」**(`TextEditor.tsx` 的保存带 `mtimeMs` 基线,模型写过就会被拒成 `fs-conflict` 而不会静默覆盖模型字节,横幅里的 reload 就是新内容入口)。一句话:**dirty 期间保持旧内容直到用户显式动作,不存在看不到新内容的死路**。
- **不误报历史**:流的第一帧是**基线**——打开文件之前会话里已经结算的写入一条都不会触发(该文件刚刚才被读过),只有**打开之后**才结算的动作会刷新。会话作用域按会话 id 分账,别的会话写同一个路径不会串台。
- 🧪 测试:`tests/editor-auto-refresh.spec.tsx`(14 例,jsdom + 假计时器),每条都做了**还原源码即红**的证伪:拿掉重载调用 → 「命中即重读重渲染」与「相对路径命中」红;拿掉路径命中判定 → 「未命中零重读」红;拿掉 dirty 判定 → 「脏草稿不被覆盖」红;把每会话一条流改成全局共享 → 「另一会话写本文件不刷新」红(并带正向对照,证明两个编辑器确实都在轮询);拿掉成员选举 → 「N 个 tab 每 tick 恰 1 个请求」红(3 个 tab 变 3 个请求);拿掉编辑态判定 → 编辑态用例红;拿掉基线 → 「打开前写过的文件不被重读」红;放行 running / err 的调用 → 「结算成功才刷新」红;**放行 `kind === 'read'`** → 「模型 read 本文件零重读」红;**放松 `EditorHost` 的 `opBaseline` 守卫**(只留 `null` 判断、不再按路径比对基线)→ 「重新定向到刚被 touch 的路径不重复读」红(既有 12 例仍全绿);**塞进一个周期性 `fsRead`** → 「无事件不轮询 fsRead」红。`tests/editor-refresh.spec.tsx` 的既有 4 条(#167)未改动、仍绿。
- ⚠️ **滚动位置与「真实浏览器」边界(如实记录)**:重载走的是既有那条加载路径(与 #167 的保存后刷新、页头刷新按钮逐字相同),所以 markdown / html **预览态**的滚动位置由 `TextEditor` 既有的模块级 per-file 滚动记忆恢复(`previewScrollMemory` + 布局 effect;jsdom 量不出 `scrollHeight`,这一层是**读源码得出、未在真机浏览器验证**);`code` 这类「编辑器就是唯一视图」的文件(`toolbar.mode` 恒为 `preview`)**无法保留光标/滚动**——重挂会回到文档开头,但**内容不会丢**(dirty 时根本不刷新)。真实浏览器里「预览是否真的刷到了新内容」这一层本轮**只在 jsdom 验证**,没有做真机点击验证。[设计文档](docs/plans/2026-10-06-editor-auto-refresh.md)
- 🔗 **Markdown 里的本地 `.md` 链接改在侧栏打开,标题锚点可跳转**([#363](https://github.com/omdsh-dev/DSH-better-sidebar/issues/363)):插件自绘的四处 markdown(编辑器预览 / 侧边对话转录 / 文件变动阅读视图 / 任务笔记)原先把 `[x](./other.md)` 渲染成**不可点的纯文本**——宿主渲染器只有在 `MarkdownDelegateProvider` 提供 `openFile` 时才把本地目标变成可点元素,且它自己的 `parseFileLink` **只认行号 fragment**(`#L24`),带标题 fragment 会让整个目标判为非法,于是「点了没反应」。现在链接被判为「同文档 fragment」或「要打开的文件」:同文档的就地滚动预览**自己的滚动容器**(不用 `window.scrollTo`,也不用 `scrollIntoView`——后者会连整条侧栏一起滚),跨文件的把 fragment 以 `%23` 编码带过宿主解析器、按「会话 + 绝对路径」暂存,目标文件渲染出来后落到对应标题。
- 🔗 **Markdown 里的本地 `.md` 链接改在侧栏打开,标题锚点可跳转**(贡献者 PR [#363](https://github.com/omdsh-dev/DSH-better-sidebar/pull/363)(@rijiu915)提出方向;因宿主渲染器的两条硬约束——本地链接无官方 delegate 时退化为纯文本、`#标题` 片段被 `parseFileLink` 判非法——维护者以**官方 `MarkdownDelegateProvider` + `%23` 片段载体**重新实现,该 PR 已带署名关闭):插件自绘的四处 markdown(编辑器预览 / 侧边对话转录 / 文件变动阅读视图 / 任务笔记)原先把 `[x](./other.md)` 渲染成**不可点的纯文本**——宿主渲染器只有在 `MarkdownDelegateProvider` 提供 `openFile` 时才把本地目标变成可点元素,且它自己的 `parseFileLink` **只认行号 fragment**(`#L24`),带标题 fragment 会让整个目标判为非法,于是「点了没反应」。现在链接被判为「同文档 fragment」或「要打开的文件」:同文档的就地滚动预览**自己的滚动容器**(不用 `window.scrollTo`,也不用 `scrollIntoView`——后者会连整条侧栏一起滚),跨文件的把 fragment 以 `%23` 编码带过宿主解析器、按「会话 + 绝对路径」暂存,目标文件渲染出来后落到对应标题。
- **标题 id 现在与 GitHub 逐字一致**:`githubHeadingSlug` 改成 GitHub `github-slugger` 的真实规则——**不 trim、不折叠连续空白**(每个空格各转一个 `-`)、删 emoji 与 `\uFE0F` 等 variation selector、删标点与符号、保留任意文字系统的字母/数字/组合标记、重复标题 `-1`/`-2`,文档里已存在的显式 id 先占用。**这是本批最有用户价值的一处**:仓库自己的两份 README 目录锚点因此从 **0/15 全部失效**(点击被认领成按钮但 `scrollTop` 纹丝不动,静默 no-op)变为 **15/15 全部可用**(真实 `TextEditor` + 真实宿主 `MarkdownText` 渲染后逐个锚点点击实测)。旧实现错在两处:先 `trim` 再把空白折叠成一个 `-`(GitHub 两者都不做,`## 🛠️ Development & Build` 的真实 id 是 `-development--build`),以及保留 `\p{M}` 而把 VS16 留在 id 里(`🖼️ 特性巡礼` 曾得到含不可见字符的 `"\uFE0F-特性巡礼"`)。
- 🧹 `` 这类空显式锚点(GitHub README 常用)被标为 `hidden`——不再占版面,id 仍在,`#x` 深链照旧。
- **范围决定**:非 markdown 的本地链接(`./notes.txt`、`./pic.png`、`./docs`)**维持纯文本不变**——本批只认领 `.md` / `.markdown`,目的是不把用户交到插件不打算服务的动作上。`#L24` 行号目标仍交给宿主自己的语法,**本批不做「打开后跳到第 24 行」**。
- 🧪 测试:`tests/markdown-navigation.spec.ts`(认领判定 / `%23` 载体 / 幂等 / 跨文件 fragment 表)、`tests/markdown-slug.spec.ts`(**以真实 `github-slugger` 正则做对照**的 slug 判定 + 两份 README 的锚点解析率断言)、`tests/markdown-surface.spec.tsx`(真实渲染下的 host delegate 点击路径、祖先滚动容器、跨文件落点)。
- 🐞 **修掉 `~~~` 代码围栏内的链接与图片被改写**:两份源码改写器(`markdown-images.ts` / `markdown-navigation.ts`)各自内联的围栏 mask 只认三个反引号,于是 `~~~` 围栏里当作**示例**展示的 `[y](./b.md#u)` 会被写成 `[y](./b.md%23u)`、`` 会被写成 `/sidebar/file` URL(与 58d7f71 基线逐字对比,这是本批唯一的渲染差异)。现在抽出一个共享 mask(`src/client/markdown-code.ts`,CommonMark 规则:**3 个及以上反引号或 3 个及以上波浪号**开栏、闭合不短于开栏、未闭合则延伸到文末),两个 pass 共用。**图片那条盲区是既有缺陷而非本批引入**(58d7f71 上 `rewriteLocalImageUrls('~~~\n\n~~~')` 已经会改写),一并修掉。
- 📝 如实记录的窄边(不修、无损坏):目的地含**转义括号**(`[x](./a\(1\).md)`)或**单引号 title**(`[x](./a.md 'title')`)的链接不会被认领,回退成纯文本——与本批之前的渲染逐字一致。
- 💻 **底部工作台也有终端了**([#774](https://github.com/omdsh-dev/DSH-better-sidebar/issues/774)):`+` 菜单新增内置类型 **`terminal-bottom`**——页面是插件自绘的 xterm,**进程由宿主的会话级终端服务 `ctx.webTerminals` 供给**(`@deepseek-ai/dsh-api-terminal-controller` 的 client 半边),插件不持有 PTY,也与右侧栏那条 **互不干扰**(右列仍是 `ui-sidebar-terminal` 的地盘)。**恢复语义**:页面每次都用**同一份 `(sessionId, key, contentId)`** 调宿主的 `webTerminals.view()`,宿主持久化在 localStorage 的 `(会话, contentId) → 终端 id` 绑定因此能在刷新后认回**同一个 shell**;`terminalId` **不写进 `tab.meta`**(`view()` 显式传 id 会关掉宿主的「缺了就建」,一个过期 id 会把页签永久卡在 `missingTerminal`),DSH 重启后 PTY 已消失而绑定还在,所以状态面板的「**新建终端**」按钮把 `tab.meta.terminalRun` +1 换一代 `contentId` 另铸进程。**关页签**才结束进程(`onClose` → `webTerminals.close()`),卸载**不**关——宿主的契约是「视图跨 DOM 卸载存活」,这正是刷新能认回同一个 shell 的原因。宿主没挂终端控制器时该行置灰、页面显示「宿主未提供终端能力」,**不**把整行拖成 `pending`(`webTerminals` 走结构化探测,没有写进 `dsh.client.inject`)。
- 🧩 **新 API:`TabDescriptor.bottomOnly`**(#774)——只投插件自己的底部工作台、**永不注册原生右侧栏承载面**的类型:`openTab` 落点强制 `'bottom'`(`target: 'side'` 也被改道),原生 tab 类型与 guide 条目都不注册。它是 `terminal-bottom` 不变成「宿主右栏里第二个终端胶囊」的全部机制(挂载 lane 钉住宿主 `terminal` guide 条目恰好 1 条),也**不是** `hidden` 的替代品——后者会连 `+` 菜单一起摘掉。字段说明与承载面差异已进 [接入指南](docs/external-plugin-guide.md),内置清单从 5 变 6。
- 📦 **新依赖 `@xterm/xterm@^6.0.0` + `@xterm/addon-fit@^0.11.0`**(与宿主自用版本一致,两者安装期零脚本),**只进新 chunk `lib/client-terminal.js`**:核心包 `lib/client.js` 里连一处 `@xterm/xterm` / `xterm-char-measure-element` 都没有(chunk **748,716** 字节,xterm 与它的样式表整份在内;实测于合并 dev 之后的树,该 chunk 还内嵌 zh/en 词典,所以数字随词典增删浮动——#774 分支单独构建时是 749,897 字节),并已写进 `package.json#files` 与 registry 清单——chunk 名镜像点同步到 8 处。
- 🧪 测试:`tests/terminal-tab.spec.tsx`(15 条,jsdom + `vi.mock` 掉 xterm/addon-fit,钉住 `view()` 调用形状与 contentId 恒定性、mount/detach、`write`/`resize` 转发与上限夹紧、snapshot 重放 + `acknowledge`、无服务降级、「新建终端」走真实 `updateTab` 路由、`onClose` → `close()`);`tests/builtins.spec.ts` 内置清单 5 → 6 并新增「`terminal-bottom` 是 `bottomOnly`,其余内置都不是」;`tests/native-registration.spec.ts` 新增「`bottomOnly` 类型一个原生 tab 类型、一个 slot、一条 guide 条目都不产生」(**删掉 `sync()` 里那行 skip 即红**——此前这半边只有挂载 lane 抓得住);`tests/chunk-artifact.spec.ts` 新增「chunk 里必须有 xterm、核心包里一处都不许有」的产物断言;`tests/e2e/mount.e2e.ts` 新增第三条真实浏览器用例(展开底部 → **空面板里的「Terminal」类型卡片** → 断言 `.xterm` 挂载、`/sidebar/bundle/terminal.js` 有响应、无 pageerror,且宿主 `terminal` guide 条目**仍恰好 1 条**、`terminal-bottom` 的 guide 条目**为 0**;`+` 菜单那条点击路径 lane 里没有覆盖,两条入口共用同一份 `buildNewTabOptions`)。
- 📄 词条 `terminalNew` / `terminalUnavailable` / `terminalGone` 进 `locales.ts` 的 zh/en 与 19 份第三语言词典。[设计文档](docs/plans/2026-10-05-bottom-terminal-tab.md)。
- 🧹 **移除「位置兼容模式 / 标题栏条带」整套机制与设置**([#430](https://github.com/omdsh-dev/DSH-better-sidebar/issues/430),细节与消费者普查见 [移除记录](docs/plans/2026-10-05-remove-titlebar-compat-strip.md)):这套设置想让人手动声明「我的桌面壳在顶部占了 N 像素」、好让插件把内容下移,但**插件内部早已没有消费者**——唯一的三条读 `--dsh-title-bar-strip` 的 CSS 规则随「顶部自绘 chrome 交还宿主」在 v0.19.0(`223b254`)删除,而用户期望被移动的「侧边栏按钮」是**宿主原生控件**(插件没有它的位置 API),宿主也不读这条契约(DSH 源码树对该契约与 `desktopWindow` 全 0 命中)。于是改设置只会写一个没人读的变量、**什么都不动**。本版把整套机制删除:`titlebar-strip.ts` / `wco.ts` / `shell-presets.ts` 三个模块与各自 spec、`titleBarScheme` / `titleBarPresetId` / `titleBarCompat` / `titleBarStripPx` 四个偏好键与设置界面、12 个词典 key × 21 份词典、挂载 lane 里两条 WCO / preset 场景。**保留**:`desktop-env.ts` 的 `hostTransportBase()`(反向代理前缀与 `dsh-app://` 解析基准,另有 3 个消费者)与「**自定义 CSS**」逃生口(改为独立分组、**非空即生效**)。旧偏好数据**安全忽略**(宿主 schema 为 OPEN 不报错、客户端 `parsePrefs` 只按声明字段构造、一次性 `settings.yaml` 回迁丢弃未声明键),三层各有测试与变异证伪。
- 🧪 测试:新增「旧数据被安全忽略」三层断言(`tests/prefs.spec.ts` / `tests/plugin-shape.spec.ts` / `tests/smoke.spec.ts`),挂载 lane 改为**负向断言**(戳记 URL 下 `body[data-dsh-title-bar-compat]` 必须缺席、`--dsh-title-bar-strip` 必须为空)并保留「自定义 CSS 实时注入」用例;删除 5 个只服务该机制的 spec。
- 🗂️ **资源管理器补「新建文件」**([#132](https://github.com/omdsh-dev/DSH-better-sidebar/issues/132),移植贡献者 PR [#832](https://github.com/omdsh-dev/DSH-better-sidebar/pull/832) 的交互语义):目录行右键「新建文件夹」旁新增「**新建文件**」,在该层顶部插入同款行内输入(Enter 提交 / Escape 取消 / 失焦提交 / IME 保护,图标跟随输入的文件名),与「新建文件夹」**互斥**(开一个即关另一个)。与 PR 的差别是**写路径落在服务端**:新增路由 `fs.createFile` → `fs-operations.ts` 的 `createWorkspaceFile`,复用既有 rename/mkdir 的单段名与「已存在即拒」规则(`fs-error` 409,同一句 `"x" already exists`),并额外拒绝 Windows 非法字符(`< > : " | ? *` 与控制字符)与保留设备名(CON / PRN / AUX / NUL / COM1-9 / LPT1-9,带不带扩展名都拒);落盘用 `wx`,**同名文件绝不被截断**;客户端在已加载的那一层先做一次大小写不敏感的占用检查(`newFileExists`),服务端仍是最终裁决。词条 `newFile` / `newFilePlaceholder` / `newFileInvalid` / `newFileExists` 同批进入 `locales.ts` 的 zh/en 与 19 份第三语言词典。
- ↕️ **资源管理器排序三档**([#132](https://github.com/omdsh-dev/DSH-better-sidebar/issues/132)):文件树头部工具区(刷新 / 上传按钮旁)新增排序菜单——**按名称 / 按类型**二选一(打勾)+ **文件夹优先**独立开关。排序作用于 `level.entries` 的**副本**,不改服务端、不原地重排共享的目录缓存;**默认(名称 + 文件夹优先)与宿主原有顺序逐元素一致**(`tests/file-tree-sort.spec.tsx` 直接拿 `fs-tree.ts` 的 `compareEntries` 当基准)。「按修改时间」不在本批范围(维护者另立 issue)。
- 🧪 测试:`tests/fs-create-file.spec.ts`(11 例:成功 / 根目录为父 / 空名·`.`·`..`·分隔符·`../escape` 路径穿越 / 全部 Windows 非法字符 / 保留名与近似名 / 已存在且内容不被截断 / 同名为目录 / 不可写父目录 / 工作区外父目录 / 路由 200·409·400 三态),`tests/file-tree-new-file.spec.tsx`(11 例:菜单只在目录行、根层与子层提交、Escape、非法名、占用名、服务端拒绝进错误条、失焦提交、两种创建器互斥),`tests/file-tree-sort.spec.tsx`(12 例:三档顺序、文件夹优先两种取值、默认等于宿主顺序、不原地排序、`typeOf`,以及 FileTree 渲染与 TreePanel 头部控件端到端切档)。
- 📑 **Tab 右键菜单补两项**([#109](https://github.com/omdsh-dev/DSH-better-sidebar/issues/109)):「**关闭全部页签**」与「**在资源管理器中定位**」。前者作用域与相邻三项一致——**右键所在的那个标签组**(分栏各管各的),含右键的那个页签本身(这是它与「关闭其他页签」的区别),逐条走既有单页签关闭路径(descriptor `onClose` 生命周期 + 终端 pty 释放 + 会话作用域),页签在滚动视口外也能关;菜单只从页签上打开,所以该项恒可用。后者对页签关联的文件(`tab.path`,即编辑器/文件夹窗口)调用宿主 **open-in-app** 的 reveal——与资源管理器行右键「在文件管理器中显示」**同一个适配器**(`createOpenInApp` → `ctx.remote.session.openWorkspacePath({ action: 'reveal' })`),不新增任何宿主调用;变更 / 任务 / 侧边对话 / diff 页签没有可定位的文件(diff 的 ref 是仓库相对路径,不是宿主能定位的路径),该项置灰,无 resolver 时同样置灰。词条 `closeAllTabs` / `revealTabInFileManager` 同批进入 `locales.ts` 的 zh/en 与 19 份第三语言词典。
- 🧪 测试:`tests/tab-bar-context-menu.spec.tsx` 把菜单整份钉到 **6 项**(标签序 + 每行 disabled 数组),新增「关闭全部」「定位」「无关联文件置灰」「无 resolver 置灰」用例,并新增一条 Workbench 用例钉住 reveal resolver 穿到**分栏后每个 pane 的** tab 条(回退任一层传递即红)。
- 🔍 **文件搜索优先走本机原生引擎(fd / ripgrep),缺失或失败自动回退 JS 遍历**([#203](https://github.com/omdsh-dev/DSH-better-sidebar/issues/203),PR [#303](https://github.com/omdsh-dev/DSH-better-sidebar/pull/303) 重写落地):`fs.search` 之前是纯 JS 递归遍历,十万级目录秒级、10 万条访问预算一顶就静默截断。现在启动后惰性探测一次(每个候选二进制 `--version` 500ms 内验证通过才用,进程内缓存),探到就用引擎,探不到/运行失败回退原遍历;rg 优先用 DSH 自己捆绑的 `@vscode/ripgrep`(agent 侧搜索工具同一个二进制,从 CLI 入口的依赖树推导候选路径),fd 走 `--fixed-strings` 字面量匹配。噪声目录排除名单单源化到 `SKIP_DIR_NAMES`(18 项,walk 与两条 argv 共用),引擎输出统一归一化成 walk 契约(根相对、`/` 分隔、大小写不敏感),**路由与客户端零改动**。**目录命中按 #801 的契约同时产出 `dirs`**:fd 用 `--type d` 直接列目录,rg 由文件路径倒推匹配的目录段(`deriveRgMatches`)——唯一残留差异是**空目录在 rg 下不可见**(已记录)。运行失败禁用该引擎(坏二进制不该拖慢后续搜索),**超时只降级本次**(树太大不等于二进制坏)。调试插桩 `DSH_SEARCH_DEBUG=1` 写 `$DSH_HOME/search-debug.log`,默认完全静默。Windows 侧由 `ci-windows` lane 承载:`choco install fd ripgrep` + `pnpm check:engines` 对插件自己的 argv 做字节级断言(不得含 `\`/CR、非 ASCII 文件名必须命中)。
- 🧪 测试:`tests/search-engines.spec.ts`(探测缓存 / 失败禁用 / 超时不禁用 / argv 对称性 / `deriveRgMatches` / 捆绑 rg 候选路径推导 / 真引擎在场时的端到端),`tests/fs-search.spec.ts` 的 dispatch 组(引擎优先、`dirs` 透传、预算透传、运行失败回退并禁用)。
### v0.24.1
> 🎁 **本版包含从 v0.22.1 起的全部内容**(v0.23.0 的文件页 / 文件变动页 UI/UX 重构与性能打磨、v0.24.0 的 DSH 0.2.0-rc.1 支持线前移,以及本节的修复)。
>
> 🐞 **修复版**:文件树「一操作就整体刷新」的两处根因。① 原生承载面(`src/client/native/index.ts` 的 `sync()`)把 `files` 接管项当成多余注册——它按 KIND 记账、永远不在 `wanted` 里,于是**每次会话状态写入**都被 dispose + 重建;替换 slot 注册等于替换宿主的 slot entry,**整个插件 tab body 被卸载重挂**(展开/收起、切 tab、拖底部工作台都会触发)→ 文件树丢失层缓存、滚动位置与目录 watcher,并重新 `fs.trees([root, ...expanded])` 重列整棵可见树。② 目录实时刷新(`FileTree.tsx` 的 `retryDir`)**先把该层缓存删掉**再请求,行被占位替换后重建(构建 / 格式化 / 模型跑 bash 时整层闪空)。
- 🧪 判别性证据:真实挂载 lane 新增 `tests/e2e/zz-expand-refresh.e2e.ts`(展开只请求该层、tab body 存活、行数不归零;回退任一修复即红),配套两个 jsdom 单测(回退后 `registered` 6 → 12、`disposed` 全是 `dsh-better-sidebar:files`;另一条断言重列期间旧行仍在屏)
- 📉 修复后实测(40 子项目录):展开 `fs.trees` payload 由 `[root, big]`(空缓存特征)变为 **`[big]`**,新开 WebSocket 1 → **0**,被删行 45 → **0**,外部写入由「删 40 建 40」变为 **只增 1 行**
- 🔧 `src/fs-watch.ts` / `use-dir-watch.ts` / `TreePanel.tsx` / `tab-adapter.tsx` 零改动(`fs.watch` 新建自身不投递事件,已实测证伪)
### v0.24.0
> 📦 **支持线前移**:仅适配 DSH **0.2.0-rc.1+**(peer 下限 `^0.2.0-rc.1`,CI 钉 `@deepseek-ai/dsh@0.2.0-rc.1`)。**0.1.7 线(含 npm `latest` 的 0.1.7-rc.2)请固定 v0.22.1(0.1.7 线最后发布的版本)**——宿主 0.2.0 的启动兼容性预检按 `semver.satisfies(宿主版本, peer范围, {includePrerelease:true})` 判定,caret 范围跨 minor 不成立(实测 `^0.1.7-rc.1` 对 `0.2.0-rc.1` 为 **false**,整行会被静默禁用),因此本版不保留任何 0.1.7 兼容分支。
- 📦 **基线抬到 `0.2.0-rc.1`**:14 条 DSH peer + 27 个 `@deepseek-ai/*` devDependencies 同步钉版;`dsh.plugin.json` 的 `engines.dsh` 随之前移;挂载 lane(`scripts/e2e-common.sh`)与 CI 钉版同步。
- 🔍 **适配前的实测结论(纯增量,故无运行时改动)**:本插件消费的 19 个宿主包里**零个值导出被删除**;类型面仅 `dsh-client-ui-primitives`(`DisclosureRow`/`TextShimmer`/`Tooltip` 新增可选 prop、overlay 顶部内距)、`dsh-session`(新增 `ToolCallRecovery`)、`dsh-api-remotes`(新增 product-analytics remote)有变化;会话格式仍 **v4**(无迁移包)、`SUBAGENT_DESCRIPTOR_VERSION` 仍 **3**、`dsh/lib/bin.js`(`dsh plugin` / `dsh web` / 启动行 / `--version`)与 `dsh-client-modules` 运行时**逐字节相同**、web profile 的客户端 inject 列表相同、`ui-primitives` 的裸 import 集合相同(无需新增测试期依赖)。
- 🧩 **宿主 web profile 组成变化与本插件无关但已核对**:新增 desktop-only 的 `product-analytics` / `desktop-product-telemetry`(web profile 上 `disabled`);原先就 `disabled` 的 `time-context` / `schedule` / `ui-schedule` 三行移入可选的 `dsh-experimental-schedule-bundle`。「宿主 tab 只有 terminal、browser 缺席」的挂载断言不受影响。
- 🧪 **测试与规则**:`tests/market-manifest.spec.ts` 的 peer 形状断言改为钉当前基线 tuple,并把「caret 跨 minor 必失效」写进注释(这条与 §3.4 第 12 条里「includePrerelease 更宽松」的经验不冲突:宽松只发生在同一 minor 内)。
- ⚠️ **唯一残留**:可选集成 `@huanlin/dsh-plugin-better-locale`(未被任何源码/测试 import,仅结构化运行时探测)的 peer 钉在 `^0.1.0-rc.8`,其最新 0.4.3 同样钉 `^0.1.5-rc.2`——两者在 0.2.0 上都会被宿主预检禁用,故 `pnpm peers check` 会报它的 5 条 unmet peer。这是上游未适配 0.2.0 的结果,与本插件自身 14 条 peer 无关。
### v0.23.0
> 🧭 **开发线版本(从未发布到 npm,全部内容随 v0.24.1 一并发布)**:文件页与文件变动页的整体 UI/UX 重构,加上一轮性能与交互打磨。文件页新增 **Ctrl/Cmd 与 Shift 多选**(VS Code 语义)、批量操作条、**Git 变更着色**(与 VS Code 同色的新增/修改/删除/冲突,目录带汇总点)、**新建文件夹**、拖拽上传体验重做;**多选可右键「压缩并下载」**,打包在宿主侧成为带进度的任务(`archive.build` / `archive.status`,逐条目回调,4 并发 + 5 分钟 TTL + sessionId 绑定)。「打开方式」改为**双源并存**:宿主探测到的本机关联应用(资源管理器 / VS Code / Cursor / Zed / 自定义编辑器,含 SSH 远端)与插件自研目标,可用设置开关 `openWithPluginTargets` 强制并存;右键菜单收敛层级(一级只留默认动作,应用列表进子菜单)。文件变动页**重构为层级树**,Git 视角(工作区 / 暂存 / 历史)与 Agent 视角(会话内改动)各自保留,目录行可暂存/取消暂存。交互与性能:`/sidebar/api/fs.tree` 实测 **22.8ms → 3.8ms**(10k 条目,readdir 一次取回 + 只为 cap 建行 + 小写比较排序 + 1.5s 目录级缓存,由写操作与 fs-watch 失效),新增批量路由 `fs.trees`(挂载/刷新的 N+1 请求变 1 个),右键菜单打开不再重列目录、行不再随 git 轮询重渲染。修掉「在侧边打开」没有效果(原来只写未展开的底部工作台,现按承载面分流到宿主 `openResource(preferNewPane)` 真正分栏)。
- ⚠️ **行为变更(安全相关)**:**删除工作区路径检测**——插件 fs 路由(tree/read/write/rename/remove/mkdir/媒体/HTML/upload/archive)不再做包含性检查,可读写宿主用户能访问的任意路径(仅受 OS 权限约束),`workspaceFence` 设置与围栏提示一并移除。
- 🧩 抽出可复用的全局组件与共享 store(`IconButton` / `Chip` / `SectionHeader` / `Notice` / `ConfirmDialog` / `StatusBadge` + 共享 git 状态 store),皮肤契约(`tests/theme.spec.ts`)继续守护;ZIP 打包器自研(无第三方依赖)并修掉非 latin1 文件名的下载头 500(RFC 5987)。
- 🐞 同时修掉:关闭文件后返回文件树时展开状态被重置(原生承载面的展开集合原先按 tab 记录,现改为**每会话状态**的投影)。
### v0.22.1
> 📦 **正式版**(npm `latest`):仅支持 **DSH 0.1.7-rc.1+**(peer 下限 `^0.1.7-rc.1`,CI 钉 `@deepseek-ai/dsh@0.1.7-rc.1`)——**支持线不变**,0.21.1 / 0.22.0 的用户直接升级即可;**DSH 0.1.6-alpha.2 及更早请继续固定 v0.19.1**。这是一次补丁版:两个缺陷在真机上可稳定复现,而当时的单元测试**全绿**。
- 🐛 **`files` 接管 id 被孤儿化 → `native register files error: … already registered` 刷屏 + 文件树落到宿主空态(需刷新页面才恢复)**(社区 issue #770 / #771 / #766)。根因链由官方桌面壳日志逐字确认(`desktop.frontdesk.log` 2026-09-27 15:51:39.338):客户端条目替换(插件市场更新 / Plugins 页禁用→启用 / HMR 重打)期间,插件 teardown 逐个注销内置描述符 → `service.notify()`(同步内联)驱动 `sync()` 在**已经 inactive** 的插件上下文上运行;清理循环把**不是描述符**的 `files` 接管释放后又**在同一轮里重建**它——`tabs.register` 建在**宿主**上下文上(宿主上下文仍活着)因此照样取走 id,紧随的 `ctx.slots.inject` 建在插件上下文上抛 `cannot create effect on inactive context`,`live.set(FILES_KIND, …)` 永不执行,disposer 丢失 → 该 id 在**整个页面生命周期内不可再注册**。修复(社区 PR #777,@yanzhaohui1999):① 清理循环**跳过 `FILES_KIND`**——接管的寿命只由编辑器类型的 `wantsFiles/hasFiles` 开关与 seat disposer 决定;② **任何在宿主取走 id 之后失败的注册都回滚释放**该 type 与已建好的槽位,失败只留一个「下次通知可重试」的状态,绝不留下无处释放的 id;③ teardown / 清理循环逐个安全释放(一个释放抛错不再中断其余)。
- 🐛 **macOS 桌面版窗口拖拽「拖一次就失效」并且双击标题栏缩放也失效**(issue #772)。插件宿主是直挂 `body` 的子元素,宿主的 `html[data-platform=darwin] body > :not(#root) { -webkit-app-region: no-drag }`(选择器含 id,只有 `!important` 能压过)命中它,而 app-region **无视 `pointer-events`**——铺满视口的面板层把下面每条 `[data-window-drag]` 拖拽带一起抵消。修复(社区 PR #773 合并;随后 #786 补齐):`[data-dsh-better-sidebar]`、`[data-dsh-panel-host]`(`> *` 保持 `no-drag`)与放大视图 `.mermaidModal` 用**中性值** `-webkit-app-region: initial !important` 退出计算——`initial` 的计算值 `none` **不**扣减拖拽区,而字面量 `none` 不是中性值(它计算成 `no-drag`);交互弹层(`.selectionPopup`、`FloatingWindow`、`AnchoredPopover`)**不**加 `initial`,否则按下会变成拖窗(重演 #103/#111)。
- ✅ **守护**:`tests/native-registration.spec.ts` 把两条不变量钉在注册表的**事件日志**上(在未修复代码上 4/4 红,且「失败后仍能重新注册」不再是非判别断言);槽位失败判据改为 `name::key`,让 `registerSlots` 的**部分回滚**释放路径真正被执行;新增**部署级回归门** `tests/e2e/native-reload.e2e.ts`(`utimesSync` 已安装的 `lib/client.js` → 页面内条目替换 → 断言无 `native register … error`、无诊断条、`files` 接管重新可用;npm 0.22.0 上连续 3 次运行全红、修复版连续 3 次全绿(该用例只有 1 个 test,重复跑三次))。拖拽契约由 `tests/panel-host-css.spec.ts` 在 Linux 的 `pnpm test` 里钉形状(无 macOS runner),并由挂载 lane 的**真实级联探针**按宿主规则读计算值(面板宿主/放大视图在未修复产物上都判红)。
- 📐 **基线不变**:`@deepseek-ai/dsh-*` 仍钉 `0.1.7-rc.1`,peer 下限 `^0.1.7-rc.1`;无公共 API / 契约 / 词典 / chunk 改动。事故记录与「为什么否决 #766 的吸收方案」见 [docs/plans/2026-09-28-native-files-takeover-reload-leak.md](./docs/plans/2026-09-28-native-files-takeover-reload-leak.md)。
- 🧪 **验证**:`pnpm test` 122 files / 1293 passed / 9 skipped;`pnpm test:mount` 与 `DSH_CMD=… pnpm test:mount:aggregate` 绿;打包产物里 `app-region:initial!important` ×2、`no-drag` ×2。
### v0.22.0
> 📦 **正式版**(npm `latest`):仅支持 **DSH 0.1.7-rc.1+**(peer 下限 `^0.1.7-rc.1`,CI 钉 `@deepseek-ai/dsh@0.1.7-rc.1`)——本版没有动支持线,0.21.1 的用户直接升级即可。**DSH 0.1.6-alpha.2 及更早仍请固定 v0.19.1**。主内容是把任务管理页从「子代理拓扑」重做成**工作流图**,并在随后几轮里按真机反馈打磨;期间 DSH 0.1.7 删掉了 Agent Teams 的 Remote 方法,团队与后台任务两个数据面随之改写。
- 🧩 **任务管理页 = 工作流图(全新主显示模式)**:会话树渲染为分层节点 + 贝塞尔连线的画布——拖拽平移、滚轮缩放到光标、内容包围盒居中适配(双击背景重新适配)、右下角控制条(图/树切换 + 折叠开关 + 缩放 + 适配)。**经典缩进树保留**(键盘可导航),两种模式共享同一个视图模型,折叠状态与团队富化不会视觉漂移;默认视图走设置里的偏好。
- 🃏 **双段式节点卡**:上段 = 类型徽章(主代理 / 子代理 / 成员 / 工作流 / 已完成聚合)+ 相位徽标 + 名称 + 元信息;下段小条 = 状态点 + 状态词 + **主 Agent 同款合并活动行**(并发工具按类别归并 + 计数 + 最新在跑那条的细节,措辞直接取宿主 `chat` 命名空间的词条)+ 已完成节点的折叠 chevron。运行中小条从左到右**完整扫过**(1.5s,令牌渐变,`prefers-reduced-motion` 下关闭)。卡片 8px 圆角、只用上段极淡底色表达层级、当前会话用加粗 accent 描边。
- 🔀 **工作流 run 入图**:宿主侧从 `tool-workflow/*` 会话事件折叠出 run(与官方 workflow-run 面板同一批事件),run 挂在发起代理下、成员 agent 重新挂到 run 节点下并按相位分框、同色相位徽标;catalog 里没有的成员用 run 自身数据合成占位节点,所以跑完的 run 仍能看到它的成员。
- 🗂 **折叠分两组,各自说清自己是什么**:`✓ N 已完成`(已完成 + 出错,失败会单独报 `出错 N`)与 `N 个待命`(跑完一轮、随时可被叫起来的 teammate)是**两行**;**手动 chevron 永远有效**(不设阈值),**自动聚合**只在「待命成员 ≥3」时收空闲成员——1~2 个待命成员就是这个团队的工作集,而且它们的卡上会挂任务行。聚合卡名字行写「前两个名字 + `+N`」,点聚合全部展开。
- 🪟 **两个常驻浮动窗**(抽出可复用的 `FloatingWindow`):后台任务输出与共享任务详情/编辑都在其中——可拖拽、可四边拉伸、内容区自滚动、只靠关闭按钮或 Escape 结束时消失(外部点击、失焦、锚点离屏都不关)。任务窗把窗口余量交给描述区(编辑态交给多行输入框),拉大窗口是给内容更多空间而不是留白;动作行固定在 footer,长描述不会把按钮推出视野。
- 👥 **Agent Teams 任务板(实验层)**:团队成员富化到对应节点上、常驻任务条列出成员与共享任务;任务状态机跟随宿主(待办 → 认领 → 进行中 → 完成 → 重开)+ 改派 / 编辑 / 两击删除,CAS 过期修订单独提示(别人改过 → 已刷新)。**成员活动由 `subagents.live` 的 running 叠加**,不是在客户端猜。
- 🔄 **后台任务改读宿主客户端服务**:0.1.7 的 web profile 挂载客户端 `ctx.jobs`(推送 roster + 非消费输出流 + kill),插件因此删掉自建的 `jobs.list` / `jobs.output` / `jobs.kill` 三条路由与事件回放镜像,也彻底不碰模型 `job_output` 游标(宿主把这层约束变成了能力)。输出改在常驻浮动窗里流式显示并尾随,代理数 ≥ 8 时抽屉自动折叠。
- 🛠 **DSH 0.1.7 的两处数据面重写**:① 团队读——0.1.6 的 `agentTeams.remoteView` 三个 Remote 方法被上游删除,插件改为读 Lead Session 的 **`agentTeam` Session projection**(与 `subagentCatalog` 同源、推送式),删掉 `teams.view` 路由与 5 秒轮询;写路径保留两条路由(`createTask` / `updateTask`),拒绝从「返回联合」变为「抛 `TeamError`」,`TEAM_TASK_STALE_REVISION` 映射成 409 `team-conflict`。② 后台任务——见上一条。**修掉的真实故障**:0.1.7 上团队条从来不渲染(路由报 `remoteView is not a function`,页面静默无提示)。
- 🐛 **真机抓到的四个缺陷**(单测都绿的):
- 逐节点折叠按钮点了没反应——它被**自动**折叠的守卫卡住了(`isAutoFoldable` 要求非成员且是叶子),而按钮是**手动**触发,现在两种触发各有各的守卫;
- 「待命」的卡片**从来不画**折叠按钮(chevron 的渲染条件写死成「已完成/出错」),现在「不在执行」即提供;
- 认领任务后标签变「阻塞」——`ready` 的语义只是「待办且无未完成 blocker 可认领」,对所有非 pending 任务都是 false,现在收敛成唯一规则 `taskBlocked/taskStatusLabel/taskTone/taskDotState`;
- 任务窗「完成」在队列中的任务上必失败——宿主状态机要求先认领,动作行现在跟随状态。
- 🎨 **窄屏与手机设置**:卡片/行距/字号按原生右侧栏的窄宽重新定档(内容包围盒居中、缩放夹取、平移边界、相位框配色、连线 stub 加粗、卡片高度预算重算);设置页新增**手机**分组——窄屏(≤768px)时新任务页不再自动弹出、任务管理页默认走树状图。
- 📐 **基线**:`@deepseek-ai/dsh-*` 钉版与支持线**不变**(0.1.7-rc.1,peer `^0.1.7-rc.1`)。新增词条若干(含 `teamTaskClaim` / `tasksFoldIdle` / `taskWindowCreate` / `tasksStateProvisioning` 等)×20 份词典;设计文档见 [docs/plans/2026-09-14-tasks-graph-workflow-teams-design.md](docs/plans/2026-09-14-tasks-graph-workflow-teams-design.md)、[2026-09-27-tasks-graph-polish.md](docs/plans/2026-09-27-tasks-graph-polish.md)、[2026-09-27-floating-window-mobile-settings.md](docs/plans/2026-09-27-floating-window-mobile-settings.md)、[2026-09-27-agent-teams-dsh-0.1.7-adaptation.md](docs/plans/2026-09-27-agent-teams-dsh-0.1.7-adaptation.md)(含实施偏差与已知限制)。
### v0.20.0(开发线,**从未发布**)
> 🚫 **这一版没有发布到 npm**(npm 上 `latest` 仍是 v0.19.1):它原本是 0.1.6-alpha.2 线的正式版,开发完成后被 0.21 线直接取代,所以下面这些变更实际随 **v0.21.1** 一起发布。原本的定位:仅支持 **DSH 0.1.6-alpha.2+**(peer 下限 `^0.1.6-alpha.2`,CI 钉 `@deepseek-ai/dsh@0.1.6-alpha.2`)。**这是一次破坏性变更版**:0.1.5-rc.* 用户请固定 `dsh-better-sidebar@0.19.1`。适配记录见 [docs/plans/2026-09-21-dsh-0.1.6-alpha.2-adaptation.md](docs/plans/2026-09-21-dsh-0.1.6-alpha.2-adaptation.md)。
- 🖥️ **终端交给 DSH 内置**:DSH 0.1.6 自带右列终端(`ui-sidebar-terminal`),所以插件删掉了整套自研终端——`pty-manager` / `agent-pty` / `pty-deps` / 8 个 `terminal_*` 工具 / xterm 视图与字体链接 / 跨会话固定终端,以及 `node-pty` 依赖、它的 `allowBuilds` 放行与安装脚本的 `-Repair` 模式。**注意**:模型因而失去跨调用持久的终端(只剩一次性 `bash` / `pwsh`);上游 `@deepseek-ai/dsh-tool-terminal` 未被内置 bundle 挂载,需要时请自行在 profile 里启用。
- 🌐 **浏览器视图让给内置**:插件删掉 `BrowserView` / 沙箱状态条 / 嵌入性探测(含宿主 `browser.probe` 路由)与两个只配置自家 iframe 的设置项;**保留**聊天与界面的外链接管(按协议分流是宿主没有的能力),目标改为 DSH 内置的 `browser` tab 类型。
- 🧩 **产物行交回宿主**:DSH 0.1.6 把 `conversation.chat.turnTail` 从 chain 改成 list(上游有意让多个插件**追加**而非互相替换),插件原来的接管只能与内置产物卡重复,因此整体删除;点击行为不变(内置走宿主 `openFile` → 本插件编辑器)。代价是失去轮尾那一个「在文件树中显示」入口(树内右键仍在)。
- 🐛 **修复两个静默破坏**:`SidebarRightGuideEntry.id` 变成必填后,缺 id 会让**整个原生承载面静默空掉**(抛在 `ctx.inject` 回调里被吞);`turnTail` 改 list 后原注册直接抛错、产物行失效。两处都已修好并加测试守护,同时补了注册失败的上报口。
- 🎨 **空白面板卡片对齐内置卡风格**:`.paneCard` 改用 DSH 指南胶囊那套配方(0.5px l4 描边 / 24px 圆角 / layer-1 填充 / 56px 下限 / 14×20 内边距),并加测试守护其几何、令牌化配色,以及不复制上游那套**未定义**的 `--dsw-alias-bg-l1/-l2` 拼写。
- 📐 **基线**:`@deepseek-ai/dsh-*` 全部钉 `0.1.6-alpha.2`,`dsh-code-runtime` 整包消失已移除,`diff` / `simple-icons` 作为 primitives 的新裸 import 提升进 devDependencies;`ui-primitives` 本版三处破坏(`IconSendOutline16` 下线、`TerminalBlockLabels.noExitCode` 新增、`ConnectionIndicator.reconnectLabel` 移除)同步适配。
### v0.19.1
> 📌 **正式版**(npm `latest`,无 prerelease 后缀):钉版推进到 **DSH 0.1.5-rc.2**(npm `next`),**peer 下限仍是 `^0.1.5-rc.1`**——rc.2 的上游 delta 里没有任何触及本插件的面(零 `packages/api|host|session|agent` 变更,真实代码改动只有消息反馈弹窗、产物卡片 CSS 与 `CodeFileIcon` 的 SVG 数据拆分),因此 rc.1 用户无需升级 DSH 即可用本版。DSH 0.1.5-alpha.2 用户继续用 **v0.19.0-alpha.1**;0.1.2-rc.1 稳定线继续用 **v0.18.1**。
- 🎯 **适配 DSH 0.1.5-rc.2**:devDependency 钉版、CI 挂载车道与 `SIDEBAR_SERVICE_VERSION` 同步到 rc.2;插件侧**零代码改动**(上游 delta 未触及本插件,逐文件核对见 [docs/plans/2026-09-10-dsh-0.1.5-rc.2-adaptation.md](docs/plans/2026-09-10-dsh-0.1.5-rc.2-adaptation.md)——300 个变更文件里绝大多数只是各包 `package.json` 的版本号单行)。
- 🎨 **文件图标(#611)与内置 Tab 图标改为彩色**(#531 + #594 合并,实现按 rc.2 重做):
- **文件/文件夹图标走 DSH 官方图形**:回退链末端是宿主 `ui-primitives` 的 `FileTypeIcon`(48 类官方全彩代码/配置图形 + markdown/图片/PDF/Office/视频/文件夹的类目色板),插件**不再自带扩展名表,也不再需要图标懒加载 chunk**——`#429` 那份 563 条彩色数据与 `lib/client-file-icons.js`(255 kB)连同 `fileIconTheme` 设置开关一并删除,彩色就是唯一形态(核心 bundle 因此只增 10 kB)。
- **新增对外 `registerFileIcon` API**(能力 `'fileIcons'`):按扩展名(`exts`)、精确文件名(`names`)、目录名(`folderNames`)注册自己的图标,优先级降序、同级按注册序;**注意**:宿主分类器覆盖任意路径,所以注册 `exts: []` 的 catch-all 会接管所有未具体命中的行。
- **内置 tab 图标彩色**:文件 / 文件变动 / 任务管理 / 侧边对话 / 终端 / 浏览器 六个类型与 diff 视图的 glyph 换成彩色版本(颜色全部来自 `--dsw-alias-*` 令牌,皮肤照旧全覆盖)。
- **原生右侧栏的 tab 芯片也带图标**:宿主的 tab 定义没有 icon 字段,但 `sidebar.right.pane.tab.title` 槽就是芯片内容——插件在该槽渲染「图标 + 标题」(编辑器 tab 带路径时显示该文件的图标),glyph 为 `aria-hidden`,芯片可访问名不变。
- 🛠 **CI 修复一:Windows lane 的真实超时**。`ci-windows` 在 2026-09-09/10 窗口内红了 8 次,其中 6 次是真起进程的用例撞上 vitest 默认 5000ms:`tests/agent-pty.spec.ts`(真起 PowerShell + ConPTY)与 `tests/install-powershell.spec.ts`(冷启 `powershell.exe` 实测 12.1s)现在各自声明 30s 预算,且 `vitest.config.ts` 的全局 `testTimeout` 从默认 5000ms 提到 **15s**(第一次真实 Windows 跑又在**第三个**文件上翻车:`tests/git.spec.ts:85` 耗时 9607ms——三个文件同一个根因,逐文件加超时是打地鼠);`waitForTranscript` 的内层轮询预算从 5000ms 降到 15s,**内层预算必须小于外层**(原先是同一个 5000ms,结构性必然超时)。`ci-windows` 的 `Test` 改跑 `pnpm test:windows`(`--maxWorkers=2`),在 2 核 runner 上不再让真起进程的 spec 互相抢占。
- 🛠 **CI 修复二:挂载车道的 npm 安装**。`plugin-mount` 的 `npm install -g @deepseek-ai/dsh@` 曾 4 次失败(2 次 `ETARGET`、2 次 `JavaScript heap out of memory` exit 134):钉版自身的传递依赖是浮动 `^` 范围,上游**分阶段发布**预发布版时(rc.2 于 09-10 的 14:43–14:57 逐包上线)npm 会组出 rc.1/rc.2 混合 peer 图(3062 条 ERESOLVE)。现在钉一个**已完整发布**的 rc.2(修 ETARGET)并加 `NODE_OPTIONS=--max-old-space-size=4096`(修 OOM)。中途试过 `--legacy-peer-deps`,被真实 CI 否掉:它跳过的正是全局安装必须提供的 peer,`dsh-app-boot` 在 boot 时 require 的 `@deepseek-ai/cordis-plugin-group` 是 peer 而非 dependency,加了它 CLI 直接 `ERR_MODULE_NOT_FOUND`。
### v0.19.0
> 📌 **正式版**(npm `latest`,无 prerelease 后缀):本版仅支持 **DSH 0.1.5-rc.1+**(peer 下限 `^0.1.5-rc.1`,CI 钉 `@deepseek-ai/dsh@0.1.5-rc.1`)。DSH 0.1.5-alpha.2 用户请继续用 **v0.19.0-alpha.1**(npm `alpha` 标签仍指向它);0.1.2-rc.1 稳定线继续用 **v0.18.1**。
**✨ 新功能**
- 📝 **新建标签页列表恢复可选说明**(#613):DSH 0.1.5-rc.1 让 `SidebarRightGuideEntry.description` 回归(可选),插件随之恢复 `TabDescriptor.description`,六个内置类型各写回一条说明(原生指南的 文件 / 文件变动 / 任务管理 / 侧边对话 / 终端 / 浏览器 六行都带上它),`guideDesc*` 词条回到 20 份词典。**宿主的原生指南只在列出的条目 ≤ 4 条时渲染说明**(更长的列表是整列丢弃,不是截断),而插件默认贡献 6 个 guide 条目——因此默认组合下说明不渲染,只有在插件设置页关掉足够多的 tab 类型、把 guide 压到 ≤ 4 条时才会出现。未声明说明的条目仍是「图标 + 标题」单行(插件不补通用兜底句)。
**🐛 修复**
- 无。rc.1 相对 alpha.2 的 delta 很小(373 个变更文件,绝大多数是上游各包版本号、原生右侧栏预览 UI 打磨与测试快照),除上述说明字段回归(并附带上游 `files` 类型改用自己的彩色文件夹图标作指南字形)外,没有需要插件适配的变更。
**🧰 CI 与内部**
- 基线推进到 **DSH 0.1.5-rc.1+**(#613):peer 下限、devDependency 钉版、CI 挂载车道与 `dsh.plugin.json` 的 `engines.dsh` 同步(rc.1 在 npm 上同时是 `latest` 与 `next`)。
- 明确未变、不必再核:全局主面板模型(`main` 槽 / `sidebar.panellist` / `ctx.layout` / 根级 `rightbar` + `rightbar.session`,插件仍不接入)、文件地址语法(`packages/util/workspace-path` 只动了版本号)、原生 tab 体宿主契约(`.paneBody` 仍是有确定高度的块级滚动容器)、core / agent / session / subagent 宿主 API 与 `ui-primitives` 导出面(仅 CodeBlock 渲染变化)。
- `@deepseek-ai/dsh-client-ui-primitives@0.1.5-rc.1` 仍不声明 `dependencies` 而 bundle 仍裸 import `anser` / `shiki` / `@shikijs/langs/*` / `mdast-util-*` / `micromark-*` / `katex`——上一版提升进 devDependencies 的那组包因此保留,不得回退。
- 真机验证(DSH 0.1.5-rc.1 + 插件 0.19.0):门禁 `typecheck` / `lint` / `check:consumer-types` 全绿,单测 **124 files · 1296 passed · 9 skipped**,`pnpm peers check` 干净;挂载冒烟对真实 rc.1 **7 passed**(含 tab 体填充断言,以及新增的「指南 ≤4 条时说明才渲染」断言);本地 3080 实测:guide 六行仍是「图标 + 标题」(6 > 4,说明按上游规则不渲染;上游把胶囊 `min-height` 从 48px 调到 56px),文件树点击 `AGENTS.md` 落到插件编辑器(CodeMirror 就绪),sidechat 输入框贴底(宿主盒 962px == 面板体 962px,composer 底边距 8px),底部工作台与中心列左右边完全重合,`pageerror` 0(控制台仅有第三方 `dsh-tauri-worktree` 的 `/api/dsh-worktree/attach` 500,与本次改动无关)。
### v0.19.0-alpha.1
> 🧪 **alpha 通道**(npm dist-tag `alpha`,安装 `dsh-better-sidebar@latest`):本版仅支持 **DSH 0.1.5-alpha.2+**(peer 下限 `^0.1.5-alpha.2`,CI 钉 `@deepseek-ai/dsh@0.1.5-alpha.2`)。0.1.5-alpha.1 请继续用 **v0.19.0-alpha.0**;0.1.2-rc.1 稳定线用 **v0.18.1**(npm `latest`)。
**✨ 新功能**
- 🪟 **每个 tab 体都填满面板**(#609):原生右侧栏的 tab 体宿主是「有确定高度的块级滚动容器」而非 flex 容器,此前插件各 tab 的根只写 `flex: 1`,在块容器里塌成内容高度——侧边对话的输入框因此贴不到面板底(转录一长就被推出可视区)。现在 native 适配层统一给每个 tab 体包一层 `height: 100%` 的列 flex 宿主,插件全部 tab(含第三方 `registerTab` 注册的 descriptor)恢复与底部工作台一致的填满语义。
**🐛 修复**
- 🧭 **跟随 DSH 0.1.5-alpha.2 的文件地址语法**:`fileAddressFor` 一律产出 session 作用域地址、绝对路径保留前导 `/`;`parseFileAddress` 改为前缀解析并忽略 `?`/`#` 后缀。
- 🪟 **底部工作台的中心列定位跟随 alpha.2 全局面板改动**:`conversation` 槽改为根级 `main` keyed 槽下的 `main.conversation`,定位器改认新 key 并跳过 `display: contents` 槽宿主(alpha.1 的旧 key 仍兼容)。
**🧰 CI 与内部**
- 基线推进到 DSH 0.1.5-alpha.2(#609):peer 下限、22 个 devDependency 钉版、CI 挂载车道与 `dsh.plugin.json` engines 同步;`pnpm peers check` 干净(按 §3-9 补提 `dsh-session-persistence` 传递 peer)。
- **移除 `TabDescriptor.description`**:alpha.2 的原生指南条目不再渲染第二行(改为「图标+标题」胶囊),该字段与 6 个 `guideDesc*` 词条(20 份词典)一并下线;新建标签页的默认页改由注册表选(恰好 1 个指南条目则直接打开它)。
### v0.19.0-alpha.0
> 🧪 **alpha 通道**(npm dist-tag `alpha`,安装 `dsh-better-sidebar@latest`):本版仅支持 **DSH 0.1.5-alpha.1+**(peer 下限 `^0.1.5-alpha.1`,CI 钉 `@deepseek-ai/dsh@0.1.5-alpha.1`)。0.1.2-rc.1 稳定线请继续用 **v0.18.1**(npm `latest`)。
**✨ 新功能**
- 🖥️ **接入 DSH 原生右侧栏**(#604):右列改为 DSH 自己的右侧栏——插件的 7 个 tab 类型全部注册成原生 tab 类型 + 原生 tab 体;聊天里的文件打开统一走 `ctx.sidebarRight.openResource(dsh-resource://file/…)`;`editor` 类型以 `extension` 优先级认领文件资源(压过内置文本预览)并接管内置「文件」页 kind(注销即复位);跨会话打开在目标会话未上屏时排队重放。
- 🧩 **退役插件自绘右侧面板与自由窗口**(#605):右列归 DSH 后,插件只保留底部工作台(单分栏树、随会话持久化),开合按钮注册进 DSH 会话头 utilities 槽;浮窗 API(`floats` / `floatTab` / `dockFloat` / `raiseFloat` / 右键「移动到自由窗口」)、`features` 里的 `'floatWindows'`,以及 `openByDefault` / `defaultWidthPercent` / `changesDiffFloat` 三个设置项一并删除(旧持久化文档里的 `floats` 字段被忽略,不影响加载)。
- 🔗 **适配 DSH 0.1.5 宿主契约**(#603):`assistant/chunk` 事件删除 → 实时增量改由 `agent/assistant-stream` 帧折叠(侧边对话转录的 `live` 字段);`sessionPersistence.inspect` 删除 → 冷会话读取改走 `open(id,'read')`;会话头 `version` 用 `SESSION_FORMAT_VERSION`。
**🐛 修复**
- 侧边对话转录 / `jobs.output` 回放 / fork 继承等 8 处会话事件读取跟随 0.1.5 契约;自定义种子补齐 fork 标记对,避免继承父会话未领取的 inbox 输入。
**🧰 CI 与内部**
- 真机挂载冒烟门禁钉 0.1.5-alpha.1,e2e 增加「展开原生栏 → 经引导页逐个打开插件 tab 类型」的巡检;typecheck / lint / 单测 / 挂载车道全绿。
### v0.19.0(未发布)
- 🎨 **可选彩色图标主题**:设置页「文件 → 文件图标」可在**内置单色**与**彩色品牌图标**之间切换——彩色主题含 563 条规则(218 扩展名 + 197 精确文件名 + 148 目录名,如 `.tsx → React`、`package.json → npm`、`node_modules` 着色),数据在**懒加载 chunk**(`lib/client-file-icons.js`)里,只有选中时才下载,默认关闭时启动零开销。数据源自 [#429](https://github.com/omdsh-dev/DSH-better-sidebar/pull/429)(@fenter)。
- 🎨 **文件图标多样化 + 对外图标注册 API([file-icons.tsx](./src/client/file-icons.tsx))**:文件树与编辑器文件 tab 不再是单一 `VscFile`——markdown / 图片媒体 / PDF / JSON / 40+ 代码扩展 / 配置 / 数据库 / lock / 压缩包各有专属 glyph(VSCodicons 单色 `currentColor`,遵循皮肤契约),未知扩展回退通用图标。`ctx.betterSidebar` 新增 `registerFileIcon`(`features` 含 `'fileIcons'`):按扩展名 / 精确文件名(`names`)/ 目录名(`folderNames`)注册自定义图标(彩色 ReactNode 亦可,颜色责任在注册方),`exts: []` 为全局默认(只兜内置 glyph 没认领的扩展,不吞掉内置多样性),保留扩展名 `'folder'` / `'folder-open'` 可换目录行图标;`fileIcon` / `folderIcon` 为权威解析器(完整回退链 + 逐工厂崩溃隔离),注册/注销即时生效。接入示例见[外部插件指南 §7](./docs/external-plugin-guide.md)。
### v0.18.1
> 📌 **正式版**(npm `latest`):DSH 基线不变(**0.1.2-rc.1+**,peer 下限 `^0.1.2-rc.1`)——本版是 v0.18.0 之后的增量发布:变更面板预览能力增强、文件树可写,以及五项修复。
**✨ 新功能**
- 📄 **变更面板操作预览增强**(#499):`.md` 阅读模式(含 mermaid 渲染)、`.html` 与 `.pdf` 内嵌渲染预览;diff 语法高亮扩展到 mjs/cjs/mts/cts、CSS/SCSS/Less、HTML/XML/SVG/Vue、GraphQL、JSONC/JSON5;新增**密钥脱敏**层(预览默认开启,面板头部可切换)
- 🗂️ **文件树重命名 / 删除**(#550):行内重命名 + 确认式删除,右键菜单减重与子菜单视口钳制
- 🧩 **插件目录名与 shell 预设文案词典化**(#535):跟随宿主语言
**🐛 修复**
- 🔀 **git diff 折叠上下文真实展开**(#576,修复 #577):折叠行此前显示「n 行…点击展开」却点不动(`-U3` 裁剪使 gap 段没有行文本);现按需经 `git.show` 拉取两侧完整内容切片填充,带加载 / 失败降级三态与请求去重;顺带修复该路由的 `rev:path` 寻址(此前恒返空)
- 💬 **侧边对话种子不再继承父会话未领取的 inbox 消息**(#562):补 fork 标记对,消除「上下文很长时侧边对话先把之前的 User 消息发出去」的幽灵消息
- 🖼️ **Markdown 分栏渲染器内的本地图片**(#569):改写为可访问 URL,不再 404
**🧰 CI 与内部**
- ESLint flat config 接入 CI 与 Makefile(#536)、Makefile 命令面规范化(#526)、e2e 脚本加固(#527)、共享组件测试工具收敛样板(#524)
- 重构:Sidebar.tsx 按关注点拆分(#542)、四处轮询习语收敛到 `use-polling`(#541)、删除 rc.7 宿主的 `__DSH_MODULES__` 回退路径(#540)、One Dark/Light 语法色板单源化(#534)、重复实现收敛与死代码清理(#525)
### v0.18.0
> 📌 **正式版**(npm `latest`):本版仅支持 **DSH 0.1.2-rc.1+**(peer 下限 `^0.1.2-rc.1`);不再支持 0.1.0-rc.8 ~ 0.1.1-rc.2——DSH stable 用户请固定安装 `dsh-better-sidebar@0.17.1`,停留在 0.1.2-alpha.x 的宿主继续用 `dsh-better-sidebar@latest`(v0.18.0-alpha.0)。
**✨ 新功能**
- 🌿 **「文件变动」统一 tab**(#475):Git 视角(真 diff / 历史 / 暂存·提交·还原 / worktree·子仓库选择)与本轮文件视角(模型读/写/编辑实时追踪)双视角合一;统一 diff 渲染(改蓝配对 + 行内字符级高亮 + 语法着色 + 上下文折叠)、底部可拖拽预览面板、一键展开独立 diff tab
- 💬 **侧边对话渲染升级**(#486):主对话级 Blocks 结构、turn 用量尾标、断线重连横幅
- ⚙️ **工作区路径围栏开关**(#458):新增 `workspaceFence` 声明式设置键,工作区外路径 403 时错误面一键关闭并指引
**⚡ 性能**
- 🚀 **核心包 -45%**(#489):19 个非中英词典 chunk 懒加载、渲染稳定化(转录行复用 / tree Sets / 批量 drag)、启动与轮询开销削减(单次 settings fetch、每 tick 单 git 进程);新增 perf 测量 lane;顺带修复底栏拖拽把面板宽度泄漏进宿主布局、原生左栏突跳的 bug
- 📉 会话列重复 DOM 查询削减(#456)
**🐛 修复(节选)**
- ✏️ 编辑器 / Markdown:SSH 远程链接客户端打开(#522)、预览/编辑切换保持阅读位置(#467)、预览隐藏 YAML frontmatter(#394)、TOC 外点关闭与层级修复(#461)、@-引用保留 basename(#417)
- 💻 终端 / 平台:Windows 自定义 shell 解析(#503)、resize 失败容错(#428)、字体解析兜底等宽(#366)、WSL 会话 Linux 绝对路径(#455)、Windows Explorer reveal 保留选中(#508)
- 🗂️ 布局 / 状态 / 文件树 / 对话:桌面 shell 布局与侧卡片共存(#398)、窄屏自动激活不强制抽屉(#373)、自由窗口 id 恢复冲突(#385)、窗口聚焦自动刷新文件树(#469)、引用提示固定行尾(#509)、划选弹窗关闭与草稿插入锚定(#427)、折叠态开关组对齐(#361)、旧引擎滚动跳变兼容(#448)、旧版 Git worktree 列表(#454)
**🧰 CI 与内部**
- Windows CI 车道(#520)、Makefile 命令面规范化(#526)、e2e 脚本加固 + 聚合双挂载回归(#527)、共享组件测试工具收敛样板(#524)、重复实现收敛与死代码清理(#525)
**🌐 生态收录**
- 新收录 10+ 插件:dsh-better-sidebar-icons(#441)、dsh-sidenote(#451,原 dsh-sidechat #470)、dsh-github-workbench(#410)、dsh-bilingual-reader(#379)、dsh-server-deck(#413)、dsh-md-export(#405)、dsh-code-nav(#404)、dsh-suhuang-scroll(#392)、dsh-better-overleaf(#370)等(均含 18+ 语言 i18n 补齐)
历史版本(v0.12.0 – v0.15.2)
### v0.19.0-alpha.0(0.1.2-alpha.5 适配,未发布,内容并入 v0.18.0)
> 🧪 **alpha 通道**:本版仅支持 **DSH 0.1.2-alpha.x**(peer 下限 `^0.1.2-alpha.5`,npm dist-tag `alpha`)。该版本号当时未单独发布,内容已并入 **v0.18.0** 正式版;v0.19.0-alpha.0 这个号后来被 0.1.5 适配线复用(见上)。
- 🔗 **适配 DSH 0.1.2-alpha.5(npm 已发布,`alpha` dist-tag)**:CI 挂载门禁钉版、`dsh.plugin.json` engines 下限与 `@deepseek-ai/*` peer / devDependencies 基线升至 0.1.2-alpha.5(真机挂载冒烟 14/14 验证)。`dsh-client-locale` 上游停在 0.1.2-alpha.3 未发新版,其 peer 下限 / devDep 保持并天然兼容 alpha.5 运行时(`pnpm peers check` 零失配,无需新增提升传递 peer)。代码适配了 alpha.4 的兼容性标记改动——`Session.events` 属性移除,迁移到按需读 API `snapshotEvents()`(sidechat 转录 live 读、fork 继承、`jobs.output` 回放、subagent 活跃度共 8 处),新建线程 meta 中宿主已删的 `seedLength` 一并移除;alpha.4 其余变化(双向 `send_message`、自定义模型发现复用 Profile 请求头、`SessionSeq`/`SessionLogOffset` 强类型)与 alpha.5(升级启动修复)经核实不触及本插件其余表面。
### v0.18.1-alpha.0
> 🧪 **alpha 通道**:本版仅支持 **DSH 0.1.2-alpha.x**(peer 下限 `^0.1.2-alpha.3`,npm dist-tag `alpha`,安装 `dsh-better-sidebar@latest`)。该版本号未单独发布,内容已并入 **v0.18.0** 正式版。
- 🔗 **适配 DSH 0.1.2-alpha.3(npm 已发布,`alpha` dist-tag)**:CI 挂载门禁钉版、`dsh.plugin.json` engines 下限与 `@deepseek-ai/*` peer / devDependencies 基线升至 0.1.2-alpha.3(真机挂载冒烟 14/14 验证)。逐点核查了 alpha.2 → alpha.3 全部 117 个 commit:插件依赖的宿主契约(token 鉴权、斜杠 RPC、`MarkdownText` labels、`sidechat.events` 所依赖的事件流与持久化 API、`SettingsNamespaceInput`、`SUBAGENT_DESCRIPTOR_VERSION`(仍为 3)、`dsh-client-store`、profile 加载器、node-pty 钉版)均无变化,无需代码适配;alpha.3 的破坏性改动(`BeginSubmissionInput.mode` 必填、subagent 错误码改名 `attachment-invalid`、SQLite 持久化后端移除、投影 change feed 收紧为 identity-gated)经核实均不触及本插件。
### v0.18.0-alpha.0
> 🧪 **alpha 通道**:本版仅支持 **DSH 0.1.2-alpha.x**(peer 下限 `^0.1.2-alpha.2`,npm dist-tag `alpha`,安装 `dsh-better-sidebar@latest`);不再支持 0.1.0-rc.8 ~ 0.1.1-rc.2——stable DSH 用户请用 v0.17.1(npm `latest`)。
- 🔗 **适配 DSH 0.1.2-alpha.2(npm 已发布,`alpha` dist-tag)**:CI 挂载门禁钉版与 `@deepseek-ai/*` devDependencies 基线升至 0.1.2-alpha.2(真机挂载冒烟 14/14 验证)。适配点:`dsh-settings` 移除运行时导出 `settingsNamespace`(命名空间改为编译期校验,宿主侧直接传常量);`dsh-subagent` 描述符版本 2→3(由宿主包盖章,测试断言跟随 `SUBAGENT_DESCRIPTOR_VERSION` 常量);`SessionEvent.ignorable` 恢复与 Remote 网关 `RemoteError` 封装经核实对本插件无破坏。
- 🐛 **修复 DSH 0.1.2-alpha.1+ 上侧边对话转录空白**:转录轮询此前仍走 alpha.1 已移除的 `ctx.connection.api`(错误被静默吞掉,tab 永远渲染空转录),现改由插件自有 `sidechat.events` 路由供给(活线程读内存事件日志、冷线程读会话持久化,`afterSeq` 增量拉取,[sidechat-routes.ts](./src/sidechat-routes.ts))。
- 🧹 **删除对 0.1.1-rc.x 及更早的兼容层**:e2e 宿主 RPC 从点分/斜杠双方言收敛为斜杠单方言(token URL 必选,[host-protocol.ts](./tests/e2e/host-protocol.ts));`MarkdownText` labels 收敛为嵌套单形状([markdown-labels.tsx](./src/client/markdown-labels.tsx),不再双 prop 名);peerDependencies / devDependencies / `dsh.client.inject` / chunk externals 白名单四处同步清除已消亡的 `@deepseek-ai/dsh-client-runtime`。
### v0.17.1
- 🔗 **DSH 0.1.2-alpha.1 适配(双版本兼容)**:DSH 0.1.2-alpha.1 的 Remote gateway、一次性 token 浏览器鉴权与 `MarkdownText` labels 契约变更已全量适配,插件在 0.1.0-rc.8 ~ 0.1.1-rc.2 与 0.1.2-alpha.1 上一致工作(后者经 GitHub tag 源码构建的真机挂载冒烟 14/14 验证;alpha.1 至今未发布 npm,CI 钉版已在 v0.18.0-alpha.0 升至 npm 发布的 0.1.2-alpha.2)。要点:`MarkdownText` 四个渲染点统一改走双形状 labels helper([markdown-labels.tsx](./src/client/markdown-labels.tsx)),修复 alpha.1 上 markdown/mermaid 预览的 `reading 'code'` 崩溃;e2e 冒烟双协议化(token URL 换 cookie、`/api` 斜杠端点 + 按参数名包装 args,[tests/e2e/host-protocol.ts](./tests/e2e/host-protocol.ts));移除已在 0.1.2-alpha.1 消亡的 `@deepseek-ai/dsh-client-runtime` peer
### v0.16.1
自 v0.16.0 以来的全部更改:
**🐛 修复**
- 🧊 **Git 面板卡死 + 重启死循环**([#376](https://github.com/omdsh-dev/DSH-better-sidebar/pull/376),修复 [#369](https://github.com/omdsh-dev/DSH-better-sidebar/issues/369)):开启「源代码管理」面板可能整页冻结、重启后自动恢复冻结状态且无法退出——三层无上限操作叠加所致,现已全部设界:**① status 截断**——`git status --untracked-files=all` 响应上限 2000 条(超限置 `truncated`,面板显示截断提示,对齐 `fs.read` 截断语义;worktree 变更计数同步有界),海量未跟踪文件不再冻结浏览器主线程;**② 仓库发现限界**——cwd 非 Git 仓库(如家目录)时不再对每个可见子目录串行无界探测:探测超时 30s→5s、子目录探测上限 200 个、并发请求共享同一次扫描并按 60s TTL 缓存,家目录不再引发 `git rev-parse` 进程风暴;**③ 重置逃生通道**——带 `?dsh-sidebar-reset` 打开页面即丢弃持久化布局(含共享宽度)从默认布局启动,即使原页面已卡死也能自救,移除参数后恢复持久化;`statusTruncated` 文案同步全部 19 个词典
### v0.16.0
自 v0.15.2 以来的全部更改:
**✨ 新功能**
- 🪟 **自由窗口**([#354](https://github.com/omdsh-dev/DSH-better-sidebar/pull/354)):把标签栏的任意 tab(内置或插件注册)**拖到主会话区域**——会话列出现虚线提示浮层,松开即成为悬浮窗口(默认 390×780,手机竖屏比例,创建时按视口钳制后居中于松点);窗口支持头部拖动移动、右下角 SE 缩放(≥320×200)、点击任意处置顶、头部右键「回到侧边栏 / 关闭」、X 走 `closeTab` 正常关闭生命周期(释放终端等);拖到侧边栏 pane 上时该 pane 高亮、松开即**停靠**合并回该 pane;`floats` 随会话持久化(刷新原样恢复,宽容 sanitize + 几何钳入视口);服务语义:`features` 新增 `'floatWindows'`——`openTab` 的 dedupe/id 聚焦命中浮动 tab = **置顶窗口**(不重复开、不展开面板),`closeTab` / `activateTab` 对浮窗正常关窗 / 置顶并照常触发回调,浮窗内 tab `visible` 恒 true,agent 终端 reconcile 覆盖浮窗;tab 内容复用常规渲染、插件 tab 与 pane 完全同契约;附带文件二级页面 8px 网格间距规整([设计文档](docs/plans/2026-08-23-free-window-design.md))
- 📂 **模型主动打开(`sidebar_open` 工具)**([#353](https://github.com/omdsh-dev/DSH-better-sidebar/pull/353)):侧边栏新增全局设置 `agentOpenTools`(**默认关闭**),开启后向模型注入**一个**工具——模型可在调用方会话的侧边栏打开本地**文件**(editor tab,按 path 去重)、**文件夹**(全窗树窗口,以该目录为根,`meta.dir`)与 **HTTP(S) 网页**(browser tab,URL 预填);关闭设置即注销工具并清空未投递队列,已打开 tab 保留;非激活会话的打开排队、下次可见时重放(`/sidebar/ws/agent-opens` 推送,同一 trust fence);无新增公共 API、不改变 `BetterSidebarService`([设计文档](docs/plans/2026-08-23-agent-open-tools-design.md))
- 📝 **Markdown README 级内嵌 HTML + 目录大纲(TOC)**([#360](https://github.com/omdsh-dev/DSH-better-sidebar/pull/360)):Markdown 预览现在真实渲染**块级内嵌 HTML**——徽章墙 ``、`` 折叠块内嵌 markdown、表格单元格 `
`/``/`
`、`