jiulanyanyue-boop/dsh-archive-manager

dsh-archive-manager

DSH(DeepSeek Harness)归档会话管理插件:按工作区分组列出所有归档会话,显示归档时间、最后活动时间与占用体积;可一键恢复回原位置,或彻底删除本地存储(含会话历史、投影缓存、工作区记账与 live 会话),不留幽灵残留、无需刷新页面。

待分类archivecordis-plugindeepseek

归档会话管理 · archive-manager

给 DeepSeek Harness (DSH) 装一个真正的「归档中心」:按工作区分组看清所有归档会话,一键恢复回原位,或彻底抹掉本地存储 —— 不留幽灵行、不留点不开的会话、不用重启。

简体中文 · English

这是什么

DSH 自带的「归档」只是一个隐藏开关:把会话从侧边栏藏起来,仅此而已。

  • 你看不到自己归档了多少个、占了多大空间、属于哪个工作区;
  • 想找回一个三个月前的会话,得先把侧边栏里所有会话翻一遍;
  • 想把没用的归档会话彻底清掉腾空间,DSH 没有这个功能;
  • 更糟的是:删过存储文件之后,侧边栏还会留着点开就报 `ENOENT` 的幽灵会话。

archive-manager 把这些补齐了:在 设置 → 归档会话管理 里给你一整页,把「归档」从单向的隐藏操作,变成可看、可恢复、可彻底删除的完整生命周期管理。

纯前端 + Host 路由,零构建、零 npm 依赖、零外部服务,装完重启一次即可使用。

它解决了哪些真问题

这一页不是把 API 套个壳,而是逐条堵住了归档会话长期存在的坑:

问题 · 真实表现 · 本插件的做法 归档内容不可见 · 归档 = 从侧边栏消失,剩下什么也不知道 · 按工作区分组列出:标题 / 归档时间 / 最后活动 / 占用体积 / 文件是否存在,一眼看清 归档时间无处可查 · DSH 只持久化「归档 id 集合」,不存归档时间 · 插件自建索引 archive-times.json,首次观测到归档即记录,原子写、恢复/删除时同步清理 恢复找不到「原来在哪」 · 只能翻侧边栏猜 · 恢复走 unarchiveSession,工作区记账原样保留,会话精确回到原工作区原位置 删完还剩幽灵行 · 删除文件后侧边栏仍列着它、点开仍在、发消息报 ENOENT · 删除时先 workspace/session-stop 停活动 → detachEntered 摘掉 live 会话 → 再删磁盘 → detachSession 清记账 → 摘归档集合 删除只删了一半 · 文件没了但工作区记账还在,侧边栏永远挂着一条打不开的记录 · 删除语义改为「抹掉这个会话的全部本地痕迹」,磁盘 / 记账 / 归档集合 / 时间索引四者收干净 界面残留要手动刷新 · 删完还得关设置页重开 · 本地先摘行 + ctx.sessions.refresh() + 带序号对账,页面内即时生效 刷新竞态把删掉的行复活 · 并发 /state 响应乱序,旧数据覆盖新状态 · /state 请求带序号单飞,过期响应直接丢弃 历史残留无人清理 · 磁盘上没文件、记账里还有,越积越多 · POST /repair + 启动 3 秒静默自检(幂等),页面发现残留时也会弹出提示条一键清理

功能一览

能力 · 说明 设置页整页入口 · 设置 → 归档会话管理,走宿主预留的 settings.section(id archived-sessions,order 25,排在「Agent 预设」之后,图标是归档盒而非默认齿轮) 按工作区分组 · 每组显示工作区标题 + 目录名 + 条数;已不属于任何工作区的会话单独归入「未归组会话」 每行信息完整 · 完整标题(不截断,长标题换行)、归档时间(绝对 + 相对)、最后活动时间、创建时间、占用体积;存储缺失时标红提示 恢复 · 立即取消归档,会话回到原工作区原位置;不需要刷新页面即可在侧边栏看到 永久删除 · 二次确认(显示将删除的项数与体积)→ 停活动 → 摘 live 会话 → 删磁盘 → 清记账 → 摘归档 → 清索引 失效引用清理 · 自动 + 手动双通道;清理只动记账,不碰任何现存会话文件 悬停出按钮 · 「恢复 / 删除」默认隐藏,鼠标悬停或键盘聚焦时平滑淡入;触屏设备常显 —— 列表干净,又不牺牲可发现性 主题完全跟随 · 配色、字体、间距、圆角全部引用宿主变量(--dsw-alias-* / --dsw-radius-*),浅色、深色、任意皮肤下都不跑偏 中英双语 · 词典随宿主 locale 服务切换,不是硬编码文案 可访问性 · role="dialog" / aria-labelledby / Esc 关闭 / 焦点管理 / 键盘可达 空状态也有温度 · 没有归档会话时是一块鲸鱼装饰,而不是冷冰冰的「无数据」

截图

列表:按工作区分组,信息密度刚刚好

永久删除:二次确认,说清楚删掉多少

空态

安装

前置条件

项 · 要求 DSH · Web / 桌面端,0.2.1-rc.2 上验证通过(依赖 webServer、workspaceRegistry、sessions 三个服务) Node · >= 22.19.0(DSH 自带运行时即可,无需自行安装) 构建 · 不需要。仓库里就是可直接运行的产物

⚠️ 先读这条:不要用裸包名安装

本仓库的包名是 archive-manager,但 `archive-manager` 与 `dsh-archive-manager` 这两个名字在 npm 上都已被别人占用。DSH 的「添加插件」会把裸包名当作 npm 包去解析,于是:

你填 · 实际装到 · 结果 archive-manager · 别人的 npm 包 · ❌ 词不达意,且版本大概率不兼容当前 DSH dsh-archive-manager · 别人的 npm 包(peer 要求 0.1.0-rc.6) · ❌ 报 Plugin ... is incompatible with dsh 0.2.1-rc.2 `https://github.com/jiulanyanyue-boop/dsh-archive-manager` · 本仓库 · ✅ 正解

结论:请填完整 GitHub 地址,别填裸包名。

方式一:GitHub 链接(推荐)

打开 设置 → 插件 → 添加插件,填入:

https://github.com/jiulanyanyue-boop/dsh-archive-manager

几种等价写法(由 DSH 安装器解析,都会克隆本仓库):

写法 · 说明 https://github.com/jiulanyanyue-boop/dsh-archive-manager · 最直观,直接粘地址 github:jiulanyanyue-boop/dsh-archive-manager · pnpm 的 GitHub 简写 https://github.com/jiulanyanyue-boop/dsh-archive-manager#v1.2.0 · 固定到某个 tag / 分支 / commit(需该 tag 已推送,见「开发与发布」) git+https://github.com/jiulanyanyue-boop/dsh-archive-manager.git · 显式 git 协议

安装器会先做一次 git ls-remote 连通性检查(默认 5 秒超时),再交给 pnpm 克隆安装。

装完请重启一次 DSH 桌面应用。 profile 组合在启动时一次性构建(未开启 HMR),插件条目需要重启才生效;之后的所有操作都在页面内即时生效,不再需要重启或手动刷新。

方式二:本地目录 / tarball(离线场景)

# 在仓库目录里打一个 tarball
npm pack --pack-destination "$env:USERPROFILE\Downloads"

然后在「添加插件」里填 tarball 的绝对路径,或直接填解包后的目录绝对路径。 「安装源」下拉只对 npm 包名生效,填本地路径不会联网。

打 tarball 前请确认 package.json 的 files 白名单包含你需要的文件;
GitHub 安装是整仓克隆,不受 files 限制,只有 npm pack 会受。

方式三:手动挂载到 profile

  1. 把仓库目录放到 /plugins/archive-manager
  2. 编辑 /profiles//package.json:
  • dependencies 里加 "archive-manager": "link:/plugins/archive-manager"
  • dsh.profile.bundles 末尾加 "archive-manager"
  1. 重启 DSH
同一个包只装一次。 archive-manager 既是包名也是 Loader 条目 id,装两份会出现重复条目。
换来源时请先把旧的一条依赖 + bundles 项删掉。

使用

归档一个会话

归档入口由 DSH 自带(会话列表里的「归档」),本插件不改变原有行为。 归档后的会话会从侧边栏消失,并出现在「设置 → 归档会话管理」里。

恢复

在列表里悬停目标行 → 点「恢复」。

  • 会话立刻取消归档,回到它原来的工作区、原来的位置;
  • 侧边栏无需刷新即可看到;
  • 页面顶部出现「已恢复「标题」,会话列表已刷新」提示。

永久删除

悬停 → 点「删除」→ 二次确认 → 「永久删除」。

确认框会明确告诉你将删除几项存储、大约多少体积。确认后按固定顺序执行:

  1. workspace/session-stop —— 并行停下该会话的回合、子代理、后台任务、提醒;
  2. detachEntered —— 把 live 会话从 SessionStore 摘掉,等 session/disposed 的清理落地;
  3. 删除会话目录 + 投影缓存文件(失败则整体失败、不摘任何记账);
  4. workspace.detachSession —— 清工作区记账;
  5. unarchiveSession —— 摘归档集合;
  6. 清归档时间索引。

不会进回收站,无法撤销。

清理失效引用

若检测到「记账里有、磁盘上没有」的历史残留,页面顶部会出现提示条:

发现 N 条失效的会话引用 …… 「一键清理」

清理只 detachSession 记账,不会删除任何现存会话文件。插件启动 3 秒后也会静默自检一次(幂等,正常情况下什么也不做)。

兼容性

项 · 状态 DSH 0.2.1-rc.2 · ✅ 已验证(本插件开发与测试基线) DSH 0.1.0-rc.x · ⚠️ 未验证,且大概率不可用(该时期的客户端/宿主服务形态与现在不同) Host 服务缺失时 · 插件不激活,不会渲染一个假列表 本地化 · 中文 / English,跟随宿主 locale

说明:本插件的 package.json 没有声明 peerDependencies,也没有 engines.dsh 字段 ——
DSH 的兼容性预检对「未声明要求」的插件一律放行,实际兼容性由运行时的服务探测决定(缺服务即不激活)。

架构

插件是 Host 半 + 浏览器半 两件套,和 boot-splash、dsh-purge 等现有 DSH 插件同构。

文件组成

文件 · 作用 host.js · Host 侧:注册 /plugins/archive-manager/* 路由、读写索引、执行恢复/删除 client.js · 浏览器侧:window.__ModuleLoader__.load() 形态,注册设置页 settings.section cordis.patch.yml · bundle patch 层:向宿主声明一条已启用的 Loader 条目 package.json · 包清单:dsh.bundle.patch 指向 patch,dsh.client.platform = web

Host 半(host.js)

inject = ['webServer', 'workspaceRegistry', 'sessions']

注册 5 条 `exact` 路由(刻意不用 prefix —— prefix 会吃掉 client.js 的静态物化路径):

方法 · 路径 · 作用 GET · /plugins/archive-manager/ping · 自检({ ok, plugin }) GET · /plugins/archive-manager/state · 整棵归档列表(分组 + orphanCount,组内按归档时间倒序) POST · /plugins/archive-manager/restore · { sessionId } 恢复 POST · /plugins/archive-manager/delete · { sessionId } 永久删除 POST · /plugins/archive-manager/repair · 清理失效记账(幂等)

数据来源:

  • 归档集合与工作区记账:ctx.workspaceRegistry(archivedSessionIds / list())
  • 标题、创建时间、最后活动:/storages/session_projcache/sessions/.json
  • 存储体积与存在性:/sessions///
  • 归档时间:/archive-manager/archive-times.json(自建索引,原子写,上限扫描 20000 条会话)

并发控制:恢复 / 删除 / 清理共用同一条 Promise 串行队列,任何时刻只有一个变更在跑。

浏览器半(client.js)

  • 无构建步骤,require('react') 由宿主模块表提供,不用 JSX(React.createElement 直接构建);
  • 注册 settings.section:{ id: 'archived-sessions', order: 25, label, locale, inject };
  • 数据流:createStore(不可变快照 + 订阅)→ useStore(useState + useEffect)→ 重渲染;
  • 样式以 ` 注入,全部引用宿主 CSS 变量;圆点/胶囊类控件显式 corner-shape: round`,避开宿主全局 superellipse 对圆形的影响;
  • 刷新链路:本地先摘行 → ctx.sessions.refresh() → /state 带序号对账;并订阅 ctx.workspaces 的归档集合变化,其它页签或外部操作也会触发本页对账;
  • 生命周期:所有 setInterval / addEventListener / store 订阅 / slot 注册都在 useEffect 清理函数或 ctx.effect 里释放。

接口示例

BASE=http://127.0.0.1:<port>/plugins/archive-manager

curl $BASE/state
curl -X POST $BASE/restore -H 'content-type: application/json' -d '{"sessionId":"session-xxxx"}'
curl -X POST $BASE/delete  -H 'content-type: application/json' -d '{"sessionId":"session-xxxx"}'
curl -X POST $BASE/repair

返回码约定:

码 · error 字段 · 含义 200 · — · 成功 400 · invalid-session-id / 请求体解析失败原因 · 请求体不合法 403 · host-not-allowed(/ping) · Host 不是本机回环 403 · untrusted-request(/state)/ foreign-origin(其余) · 来源校验未通过 404 · not-archived · 恢复时该会话不在归档列表中 404 · session-not-found · 删除时本地已无任何痕迹(可安全重复调用) 405 · method-not-allowed · 方法不对(如对 /delete 用 GET) 409 · session-active · 仍有回合 / 子代理 / 后台任务在跑,拒绝删除 500 · delete-failed / internal · 磁盘删除失败(记账保持原样)/ 未预期异常 503 · registry-unavailable · Workspace 注册表尚未就绪 503 · activity-probe-failed / session-stop-failed · 无法确认活动状态 / 停不下来 → 取消删除

安全设计

这个插件能递归删除会话存储,所以删除入口按「宁可拒绝,不可误删」来写: