DSH HUB
HomePlugin StorePlugin PacksCommunityRankingsResourcesPublish Guide
Plugin source
Back to catalog

uugvkbvkdjbsdbvwxxl /

uugvkbvkdjbsdbvwxxl/dsh-archived-chats

Verified

Browse archived DSH sessions grouped by workspace in Settings, search them, restore one, or delete a session log for good.

★ 0 Stars0 Forks0 IssuesN/A Community rating0 Confirmed installs
View on GitHub
READMESource: main@7f111584

Archived chats for DeepSeek Harness

设置里的一页「已归档的聊天」:按工作区分组浏览已归档会话,搜索、按工作区筛选、恢复,或真正删除。

DSH 自带归档(隐藏),但没有删除会话的能力;这个插件补上浏览与管理那半边,并且是唯一能真正删掉会话日志的实现。

安装

dsh plugin --profile <your-profile> add github:uugvkbvkdjbsdbvwxxl/dsh-archived-chats

也可以在插件市场(dsh-market)的「发现」里搜 Archived chats 一键安装。

装完刷新窗口,打开 设置 → 已归档的聊天。

已归档的聊天:按工作区分组

按工作区筛选

为什么需要 Host 半

官方没有会话删除能力,两处文档都写明了:

  • @deepseek-ai/dsh-session-persistence-jsonl:Nothing deletes session files — 日志会一直累积在根目录下,seam 没有删除 API。
  • @deepseek-ai/dsh-workspace:归档是唯一的「隐藏」手段,session deletion 是 absent capability;删除工作区不会删除会话。

所以恢复用官方 API(workspaceController.unarchiveSession),删除由本插件在 Host 侧完成。

页面

settings.section 的一位,id = archived-chats,order = 12(模型之后、内置插件之前)。

  • 数据全部来自设置页 slot 的标准 props:useWorkspaces(工作区行、sessionIds 归属、archivedSessionIds)与 useSessions(标题、更新时间)。
  • 分组:有归属的会话落在其工作区下;找不到归属的进「无项目」。组内按更新时间倒序。
  • 按工作区筛选:搜索框右侧的自绘 listbox(aria-haspopup="listbox" + role="option",方向键/Home/End/Escape 可用)。选项只列当前有归档会话的工作区并带条数,选中后只显示该工作区的内容,标题下的统计同步变成「<工作区> · N 个归档聊天」;工作区被清空时自动回到「所有工作区」。
  • 操作:恢复(走 ctx.workspaces.unarchiveSession)、删除(走下面的 HTTP 路由)、全部删除(删除当前可见范围内的会话)。
  • 状态:冷启动时会话目录还没返回时显示骨架占位,而不是把每一行都说成「会话信息缺失」;错误与成功提示分别用 role="alert" / role="status" 播报。
  • 列表靠 workspace 流的 archived 增量自动刷新,不做轮询。

交互与样式约定

  • 颜色只用 --dsw-alias-*;所有可交互控件都有 :focus-visible 焦点环,图标按钮带 aria-label。
  • 不弹 window.confirm,也不用原生 <select>:删除确认就地展开,筛选是自绘 listbox。
  • prefers-reduced-motion 下关闭过渡与骨架动画。

Host 路由

方法 路径 作用
POST /api/dsh-archived-chats/delete body { sessionIds: string[] },返回 { deleted: [...], refused: [{ sessionId, reason, message }] }
POST /api/dsh-archived-chats/prune 维护清扫:把工作区账本里已经找不到日志的会话 id 摘掉(工作区成员、归档集、置顶集),返回 { stored, pruned }
GET /api/dsh-archived-chats/health 解析出的会话根目录、是否存在、项目目录与会话目录计数、Host 代码 revision

请求围栏:composed connection 存在时用 ctx.connection.requestRejection()(Host/Origin 校验 + 浏览器签名凭据);没有 Connection 时退回显式回环围栏。两个路由都注册在 ctx.effect 里,插件卸载即摘除。

删除的三道自检

路径规则逐字取自已安装的 dsh-session-persistence-jsonl/lib/index.js (encodeSegment / projectKey / sessionDir),不自行发明:

  1. 身份:sessionPersistence.stat(id) 必须存在,且 header.id === id;
  2. 路径:会话目录名必须等于 encodeSegment(header.id),父目录名必须等于 projectKey(header.cwd)(无 cwd 时 _no-cwd);
  3. 包含:最终路径必须严格落在会话根目录内,目录里必须存在 session.*.jsonl[.zstd]。

任一条不满足就拒绝并报错,一个字节都不动。

「正在运行」怎么判断:直接问 Workspace registry 归档时用的同一个 seam —— ctx.waterfall('workspace/session-activity', { sessionId }, next)。它由 Agent / jobs / subagent / schedule 四个 provider 回答「这个会话还有没有在跑的东西」,返回数组 (元素形如 { kind: 'turn' | 'job' | 'subagent' | 'schedule' });非空即拒绝,并把 family 列表回给页面。「在内存里驻留」不算运行:会话只是被打开着、或刚被归档,都不该挡住删除 (1.0.0 用 agents.get() / sessions.get() 判断,正是它把刚归档的会话误报成「正在运行」; 1.0.1 修正)。文件仍被占用时(Windows 的 EBUSY/EPERM)单独回 session-open。

删除流程(1.0.2 起):

  1. 删除前先记下所属工作区实体(Workspace.sessionIds 会按 header 索引过滤,日志删掉后就再也找不到它了);
  2. unarchiveSession 去掉归档标记(幂等、不探测存在性);
  3. 删掉会话目录,再尽力清理 storages/session_projcache/sessions/<uuid>.json(纯派生缓存);
  4. WorkspaceEntity.detachSession(id) 把 id 从工作区账本里摘掉 —— 它的 mutate 还会顺手 剪掉该工作区里所有不再通过 candidateMatches 的候选。

第 4 步是 1.0.1 漏掉的:只删文件不动账本,会话会以「幽灵行」的身份留在侧边栏(用户看到的 「删除后又回到工作区」),点开时 DSH 为这个已不存在的日志创建/恢复会话,一发送就 ENOENT ... open '<已删除目录>/session.v4.jsonl.zstd'。历史遗留的幽灵行用 POST /api/dsh-archived-chats/prune 清理(它按 sessionPersistence.list() 的现存 header 逐条比对)。

配置

apply(ctx, config) 读取可选 config.root;缺省为 $DSH_HOME/sessions,再缺省为 ~/.dsh/sessions。

已知边界

  • 删除是硬删除,没有回收站;页面上的两次确认是唯一防线。
  • 1.0.2 起删除会同时摘掉工作区账本,1.0.3 起还会 emit api-session/removed,所以删除后不会再留行; 历史遗留的用 /prune 清。
  • 早期版本留下的残留行只存在于当时那个页面里:Host 的 session.list 不返回它 (用全新页面实测 未分组 为 0 行),刷新窗口即可清除,不必重启进程。
  • Host 内存里会留下一个「已无日志的活会话」(/health 的 liveOnly 能看见)。它没有任何 API 可 主动销毁(ctx.sessions 只暴露 create/prepare/enter/announce/flush/get/list/fork),进程重启后消失, 不占磁盘、也不出现在列表里。
  • 删除后工作区 sessionIds 里的悬挂 id 不会被立即改写:workspace 读取时会把无 header 的 id 过滤掉,并在下一次变更时剪除。
  • FTS 索引是派生库,下次搜索时自行对账已删除的来源。

验证记录(2026-10-27,本机 desktop profile)

装好后在同一 Host 上跑过:

验证 结果
Host 行与设置分区注册 Config 目录出现 include:archived-chats;settings.section 出现 archived-chats(order 12,active)
路由围栏 无 cookie 请求 /api/dsh-archived-chats/health → 401(Connection 认证生效,不是 404)
健康检查 root=C:\Users\lenovo\.dsh\sessions,6 个项目目录 / 71 个会话目录,与磁盘一致
拒绝路径 不存在的 id → session-missing;有运行中工作 → session-active(带 family 明细)
真实删除 自建的合成会话目录 → 返回 deleted,目录从磁盘消失(现有 6 条归档会话全程未动)
页面渲染 6 条归档、按 2 个工作区分组(测试 5 / default-workspace 1),标题与时间正确
单条删除确认 点垃圾桶 → 行内变为「确认删除 / 取消」并显示不可恢复提示;取消恢复原状
全部删除确认 点「全部删除」→「删除全部 6 个归档会话?/ 确认删除 6 个 / 取消」;取消恢复原状
搜索 输入 number → 只剩 1 行;清除后恢复 6 行
恢复 「The Number 11」→ 侧边栏 archivedSessionIds 少一条、页面剩 5 条
状态还原 验证后把该会话从侧边栏「归档会话」移回归档,workspace.json 的归档集与动手前逐条一致,会话目录数仍为 71

工作区筛选与样式改版(第二轮)

验证 结果
筛选器选项 「所有工作区 6 / 测试 5 / default-workspace 1」,带路径副标题与条数
选中工作区 只剩「测试」一组 5 行,标题统计变为「测试 · 5 个归档聊天」,触发按钮显示「测试 5 个」
回到全部 两个工作区、6 行全部回来
冷启动占位 会话目录未就绪时改为骨架占位(该分支按第一轮实测到的「整页会话信息缺失」状态补的,短时间没能再复现出截图)

1.0.1:修「已归档却提示会话正在运行」

线上反馈:刚归档的会话点删除,报「会话正在运行」。归档本身能成功,说明 Host 自己的 archive admission 已经判定它没有在跑的工作 —— 拒绝来自我旧版的 agents.get() / sessions.get() 判断,它把「在内存里驻留」当成了「正在运行」。

改法是换成 Host 归档时用的同一个 seam(workspace/session-activity waterfall)。 离线 harness(archived-chats-work/host-harness.mjs,用假 ctx + 临时根目录真删文件)跑过 4 个用例:

用例 结果
驻留但空闲 200,deleted: 1,目录被删,归档标记先被摘掉
有运行中工作(turn + job) 200,refused: [{ reason: 'session-active', message: 'turn, job' }],目录未被删
未知 id 200,refused: [{ reason: 'session-missing' }]
再次空闲 200,deleted: 1,目录被删

1.0.3:修「删掉后侧边栏还留一行(切对话也不消失)」

现象:删除后侧边栏仍有一行(落在「未分组」),切到别的对话不消失,只有重启进程才消失。

排查(这一步很关键,别再靠猜):

  • /health 新增 liveOnly:["session-8315b0b8-…"] —— Host 内存里确实有一个已无日志的活会话;
  • 但用全新页面连同一个 Host 打开侧边栏,未分组 下面是 0 行 —— 说明 Host 的 session.list 不返回它。

结论:那一行是调用方页面自己的客户端目录残留(它把这个 Session 留在自己的目录里), 切对话不会清,只有重新加载页面或收到 api-session/removed 才会丢。

修法:删除成功后由 Host 主动 ctx.emit('api-session/removed', sessionId) —— 这正是 Session Controller 在 session/disposed 时发出、并由应用转发给所有页面的事件。 /prune 里对每条被清扫的记录也发同一个事件。离线 harness 记录到 emitted: [["api-session/removed","session-test-0001"]]。

给用户的答案:这种残留行刷新窗口(Ctrl+R)就能清,不需要重启进程。

1.0.2:修「删掉后又回到工作区,一发送就 ENOENT」

线上反馈:删除后会话又出现在工作区里,点进去发消息报 ENOENT … open '…\session-66e05842-…\session.v4.jsonl.zstd'。

原因:1.0.1 只删了日志文件,没把 id 从工作区账本 sessionIds 里摘掉。行还在 → 点开时 DSH 为这个已不存在的日志创建/恢复会话 → 后端首次 append 往已删除的目录写文件 → ENOENT。

验证 结果
离线 harness 补了 list()/detachSession 假实现 deleted: 1 之后 detached: ["session-test-0001"],说明摘账本被调用
首次加载 1.0.2(改 patch 触发重组) revision 3 → 4,约 2 秒
现场清扫 POST /prune stored: 70,摘掉两条幽灵记录:session-66e05842-…、session-219285fc-…(都在「测试」工作区)
清扫后核对 workspace.json 两个 id 已不在任何 sessionIds 里;归档集 6 条,成员总数 52

Host 半怎么热重载(2026-10-27 实测)

三件事分别验证过:

做法 结果
重启进程 生效(revision 2,删除成功:归档集 7 → 6、会话目录 71 → 70)
在插件市场把插件关掉再打开 无效,页面仍回旧版文案 —— 那条开关走的是 dsh-market 自己的路径,不触发 profile 重组
改一次 $DSH_PROFILE_DIR/cordis.patch.yml(加一行注释) 约 2 秒生效:revision 2 → 3,无需重启

原理:DSH 自带的 @deepseek-ai/dsh-hmr 已经在做这件事,而且做法比 URL cache-busting 更稳 —— 它从 Node 内部的 ModuleLoader.loadCache(以及 CJS 的 require.cache)里逐出已改动文件的模块, 保留模块身份,再让 Loader 重新 import。给 import() 拼 ?v= 只会制造同一包的第二份实例, instanceof、Cordis 服务注册、单例都会跟着坏掉,所以不要那样做。

这个 profile 的 hmr 行是 root: [](只监听配置与清单,不监听源码),所以默认不会自动追文件改动; 要让它自己追,可以在 profile patch 里给 hmr 行配 root(示例):

- id: hmr
  disabled: false
  config:
    root: ["."]

注意 ignored 默认排除 **/node_modules,而 linked 插件在 <profile>/node_modules/@<scope>/..., 所以更可靠的是把链接目标目录直接写进 root;不配 root 也可以,靠「profile 重组」这条已测通的路径即可。

局限(HMR README 明确列出):packed worker 加载器、Worker 线程与 TSX loader 的模块缓存换不掉; Host 依赖图之外被静态导入的模块改动会保持 pending;package.json 单独改动不触发。这些仍需重启。

Client 半(client.js)不受这些限制,刷新窗口即可,这也是改版式/加筛选能立刻看到的原因。 /health 里的 revision 就是用来判断当前进程跑的是哪一版 Host 代码。

—/ 5

No ratings yet

Verified DSH bundle

Commit 7f111584d705

Community comments

No comments yet. Be the first to write one.

DSH HUB

A community index for DSH plugins. Not an official GitHub or DeepSeek AI product.

CommunityResourcesAPIAbout