# 会话管理 · dsh-session-keeper [English](README.md) | 中文 把 dsh 侧边栏变成真正能管得住的地方:**分类树、拖拽排序、置顶、归档分区、可撤回的删除回收站**。 每个 Workspace 有自己独立的分类,每个会话最多属于一个分类;删掉的会话进回收站而不是蒸发。 | | | | --- | --- | | 包名 | `dsh-session-keeper` | | 显示名 | **会话管理**(设置 → 插件里的 tab 就是这个名字) | | 版本 | `1.2.1` | | 许可 | MIT | | 形态 | 一个包内含 Host 入口 + Web Client bundle + Cordis profile patch,不需要另装 Client / Bundle | > **这是谁的代码?** 本项目是 [ht719/dsh-session-categories](https://github.com/ht719/dsh-session-categories)(`0.1.0`,MIT)的**维护分支**。 > 上游那份装进当前版本的 dsh 会直接打不开窗口,本仓库在它之上做了移植与扩展。 > 每一项改动**来自哪里**(上游基线 / 竞品思路 / 使用者反馈)都记在 [CHANGELOG.md](CHANGELOG.md);移植的技术细节在 [FORK-NOTES.md](FORK-NOTES.md)。 > > **不要与上游包同时安装。** `dsh-session-keeper` 与 `@deepseek-ai/dsh-session-categories` 抢的是同一个 > `sidebar.workspaces` 槽位,打开的也是同一个存储域(`session_categories`,version 0)。两个一起装, > 侧边栏会互相顶掉,分类文档也会被写进同一份。二选一:这个分支,或者上游。 --- ## 为什么会有这个分支(源于什么) 上游 `dsh-session-categories@0.1.0` 发布于 2026-08-19,对着更早的 dsh 预发布版写的。之后 dsh **移除了 `@deepseek-ai/dsh-client-runtime` 这个包**,而上游的 client bundle 仍然 `require` 它: ``` upstream lib/client.js 需要: "@deepseek-ai/dsh-client-runtime/client" ← 现在解析不到 ``` dsh 的模块加载器会拿插件的 `inject` 声明去解析 bundle 里的 `require`。解析不到就在 bundle 物化时抛异常,`sidebar.workspaces` 这个 slot 条目的挂载随之崩溃;官方侧边栏把 slot 收回去, 而崩溃发生在 shell 引导阶段,于是**窗口永远开不完** —— 表现出来的样子是「dsh 打不开了」。 本分支从源码重建 bundle(不是打补丁改 `lib/`),所以重装插件也不会把修复冲掉。 上游还缺两样东西:仓库里**没有 `Rows.module.css`**(`Rows.tsx` 却 import 它), 以及重建出来的样式表里 `@keyframes row-in` 块被截断过(一个坏 at-rule 会让后面所有规则静默失效)。 这两处也在本分支补齐,细节见 [FORK-NOTES.md](FORK-NOTES.md) 与 [BUILD.md](BUILD.md)。 --- ## 功能 ### 1. 分类树 - 分类**按 Workspace 隔离**:A 工作区的分类不会跑到 B 工作区去。 - 支持**多级嵌套**:分类下可以再开子分类,缩进随层级增加。 - 新建方式三个入口:Workspace 操作菜单建一级分类、分类 `⋯` 菜单建子分类、会话行的**移动到分类**。 - 分类的 `⋯` 菜单:新建子分类 / 重命名 / 删除。重命名是**就地输入**(回车确认、Esc 取消),不弹框。 ### 2. 拖拽 四种拖拽都支持,各有各的落点规则: | 拖什么 | 拖到哪 | 结果 | | --- | --- | --- | | 会话行 | 单列表 / 工作区分组里的另一行 | 调整**顺序**(写进本地存储) | | 会话行 | 分类里的行 | 在**该分类内**调整顺序 | | 会话行 | 分类容器 | **改派**到那个分类 | | 分类行 | 同级分类行 | 在**兄弟之间排序** | | 分类行 | 其他分类行的**中间带** | 变成它的**子分类** | 分类行拖到另一行的**上边缘 / 下边缘**是排序,落在**中间带**是改父级 —— 两种手势靠落点高度区分。 跨父级排序会被 Host 拒绝(`anchor-not-found`),这是设计如此:排序只在同级内有意义。 需要「手动排序」生效,把视图菜单的**排序方式**切到 `手动排序`;停在 `最近更新` 时,新活动会把会话顶回前面。 ### 3. 会话操作 每个会话行右缘有 `⋯`,鼠标悬停时时间戳让位、按钮落到最右: | 操作 | 作用 | | --- | --- | | 重命名 | 改标题 | | 分叉会话 | 在最后一个完成的回合处分叉 | | 置顶会话 / 取消置顶 | 标记该行醒目:置顶行会被抬进所有分类上方的全局置顶段,或者留在自己所属的分组里 | | 归档会话 / 取消归档 | 归档该行,或把归档行放回原位 | | 移动到分类… | 改派到别的分类,或放回未分类尾部 | | 删除会话 | 把会话记录移进回收站(见第 5 节) | ### 4. 视图菜单 **分组方式**(三种,与官方浏览器一致): | 模式 | 说明 | | --- | --- | | 按工作区 | 每个 Workspace 一个平铺分区 | | 按工作区树 | 按目录路径嵌套:目录位于另一个 Workspace 目录**之内**的,作为那个分区里的**子分区**渲染。按严格路径边界匹配,`E:\AI\DSHX` 不是 `E:\AI\DSH` 的子级 | | 单列表 | 一条按时间排的列表,不分组 | 路径比较前会归一化(Windows 分隔符、结尾分隔符、大小写都不计),所以 `E:\AI\DSH` 与 `e:/ai/dsh/` 是同一个父级。 **筛选会话**(三态,与官方一致): - **隐藏已归档** —— 归档分区和归档行一起消失; - **全部对话(包括已归档)** —— 分区出现并展开; - **仅显示已归档** —— 工作区分组全部隐藏,只剩归档分区。 第二项写「包括」而不是官方的「显示」,是有意为之;行为完全一致。 **置顶位置**(一个开关,管所有置顶行): - **置顶到分类上方** —— 置顶行从各自分类里抽出来,汇成**所有分类之上**的全局置顶段。置顶说的是 「在整个列表里醒目」,不是「在某个分组里靠前」; - **保留在所属分类** —— 置顶行留在自己所属的分组里,只在那个分组内靠前。 开关在同一个视图菜单里,默认是**置顶到分类上方**。 ### 5. 删除与回收站 「删除会话」把会话记录移到 dsh 数据目录下的**批次目录**。文件不销毁,删除随时可逆。 移任何文件**之前**,确认框会先走 `/session-recycle/scan` 扫一遍并列出: - 会话记录本身占多大; - 选了更大范围时,会跟着一起移动的、该会话写过的文件; - **每一个被拒绝的文件,以及拒绝的理由**。 三档范围:只移会话记录 / 连该会话在工作目录里写过的文件一起移 / 预留的「全部」档。 安全规则很严:只有**真实路径严格位于会话工作目录内、且不在 dsh 数据目录下**的文件才会被动到; 符号链接、不存在的文件、其余情况一律拒绝并给出理由。日志会在移动**之前**读出来,因为一旦离开 sessions 目录,持久化层就读不到它了。 删除同时把该会话从所有工作区名册里摘除、并从归档集合中释放,所以不会留下孤儿行。 撤回会把三面一起还原:恢复日志、重新挂回名册、原本已归档的重新归档。 回收站管理在 **设置 → 插件 → 会话管理**: - 按**批次**分组列出,显示删除时间、会话数、占用体积; - **看内容** 展开该会话的存档对话,让「再删一次」是个知情决定; - **撤回最近一批** 把最新一批放回原处; - **彻底删除**(单批)与 **清理过期**(默认保留最近 20 批 / 30 天)—— **这两项不可逆**; - **清空回收站** 一次清掉全部批次。 ### 6. 插件日志 设置页的**插件日志**是个环形记录,按时间倒序记插件自己的动作与失败(归档、取消归档、删除、扫描、撤回)。 被拒绝的动作会留下**看得见的理由**,而不是一闪而过的 toast。可以一键复制全部日志。 --- ## 安装 shell 里要能用 `dsh` 命令,然后装到未修改的 dsh Web profile: ```sh dsh plugin --profile web add dsh-session-keeper dsh --profile web ``` 在 dsh 源码仓库里跑的话,用它自己的 launcher: ```sh pnpm dsh plugin --profile web add dsh-session-keeper pnpm dsh --profile web ``` 仓库里已含生成好的运行时文件,**不需要额外构建插件**。 从 tarball 装(离线或想锁定版本时): ```sh dsh plugin --profile web add ./dsh-session-keeper-1.2.1.tgz ``` 装完**重启 dsh**。插件注册三个 slot:`sidebar.workspaces`、`settings.plugins.tab`、`sidebar.footer.action`, 不碰任何 dsh 核心包,也不需要改 `ui-workspace` / `ui-slots` / `api-remotes`。 --- ## 注意事项 **1. 预发布版不承诺兼容。** 插件对着当前的 dsh 预发布插件 API。dsh 升级时请**一起更新本插件**——只升一边很容易撞上 「包被移除 / 导出改名」这类断裂,症状往往就是侧边栏整块不见或窗口打不开。 **2. 「彻底删除」是真的删。** 「删除会话」是可逆的(进回收站);「彻底删除」「清理过期」「清空回收站」**不可逆**。 回收站不是备份,重要会话请另外导出。 **3. 视图状态在你自己的浏览器里。** 分组方式、排序方式、分类展开状态、归档筛选、各账户的手动顺序,都以 `dsh.session-categories.view.v2` 存在**浏览器 localStorage**。换机器、清缓存、换 profile 都会丢。 会话本身和分类数据不在这里,不会因为清缓存丢。 **4. 分类删除会连带归档会话。** 递归删除一个分类,会把里面的会话**归档**(不是删除)。日志不会被删掉,随时可以取消归档找回来。 **5. 跨 Workspace 的操作会被拒绝。** 把会话移到别的 Workspace 的分类、把分类移进自己的后代、对着过期 revision 动手 —— 都会被拒并给出理由。 这是保护,不是 bug。 **6. 排序要开「手动排序」。** 停在「最近更新」时,新活动会把会话顶回最前面,把用户手排的顺序不断覆盖。 **7. 本插件不注入全局 CSS。** 所有样式都走 CSS Modules(类名哈希化),作用域限定在自己渲染的 DOM 里。它不会改侧边栏的全局宽度、 不会改窗口背景、不会写 `document.body`。所以它既不会修好、也不会弄坏外壳层的布局问题。 **8. 卸载。** `dsh plugin --profile web remove dsh-session-keeper`。分类与会话数据留在 dsh 存储里,不会跟着插件走。 --- ## 数据与行为 - 递归删除分类会**归档**其中的会话,不删持久化日志。 - 归档操作带 operation id 重试,进程重启后可以恢复。 - 变更会拒绝:过期 revision、跨 Workspace 的会话 id、移到自身后代、指向未知分类。 - 删除的批次目录在 dsh 数据目录下,结构 `<批次id>/<会话id>/`;批次 id 形如 `2026-10-17T14-31-25-939Z`。 ## 兼容性 面向当前 dsh 预发布版的插件 API,目标是在**未修改**的 dsh 安装里工作。 dsh 不承诺跨预发布版本兼容,升级时请同时更新 dsh 和本插件。 ## 仓库结构 仓库根目录就是**可安装的预构建包**。源码保留在 `packages/workspace/session-categories/`、 `packages/client/ui-session-categories/` 与 `packages/bundle/session-categories/`。 构建说明(含本分支不得不打的上游补丁)见 [BUILD.md](BUILD.md)。 ## 许可与致谢 MIT。上游版权归原作者 `ht719`;本分支保留同样的许可与署名,改动以同等条款发布。 上游仓库:[ht719/dsh-session-categories](https://github.com/ht719/dsh-session-categories)。