dsh-archived-sessions
Manifest validHaving trouble managing archived sessions with dsh? This is the one to use!
dsh-archived-sessions
在 DSH 设置页管理「已归档」的会话:看一看、恢复回去,或者彻底删除。
能做什么
入口:设置 → 归档会话。
- 查看:列表秒开,直接显示会话标题、原工作区和最后活跃时间(绝对时间戳);消息数在后台统计并缓存,反复打开列表不会重复解析日志。
- 预览:点「预览」看会话开头的 6 条对话(用户 / 助手消息,以及调用了哪些工具)。
- 恢复:点「恢复到工作区」,归档记录被移除,会话回到原来的工作区位置,侧边栏立即刷新。
- 彻底删除:日志文件、工作区归属、归档记录三处一起清理。删除前要输入会话标题确认(防误删);有子会话的归档会被拦下;删除后还会验证日志确实已经消失,避免会话「复活」。删除成功后侧栏会话列表在同一拍刷新,不会留下「未分组」的残留行(v1.5.3)。
- 批量删除:点「批量删除」进入批量模式,每张卡片左侧出现复选框,顶部工具条提供「全选 / 清空 / 已选 N 项 / 删除所选 / 退出批量」。确认弹窗会列出将被删除的会话(超过 20 个折叠)、显示每个会话的子会话构成,并要求**勾选「我知道这些会话不可恢复」+ 输入固定词「删除」**才会执行(这道闸在 Host 侧也校验,绕过界面直接调 Remote 同样会被拒)。
- 子会话分级处理:
origin: 'subagent'的子代理子会话(界面上看不到、DSH 也没有别的删除入口)默认一并删除;你自己 fork 出来的子会话(有独立对话内容)默认保留,想连它们一起删就在弹窗里选「全部级联」。仍在运行的后代一律跳过并逐条报告。 - 执行后给出逐条结果(✅ 已删除 / ⏳ 待删除 / ❌ 失败 / ⏭ 跳过 + 原因)与汇总(成功、待删除、失败、连带删除的子会话数);一个失败不会中断整批。
- 子会话分级处理:
- 孤儿会话扫描与清理:找出「日志还在、但不属于任何工作区、也不在归档列表」的会话。这条判定很机械,会把三类完全不同的东西捞出来,扫描结果因此按类分组显示:
- 可疑残留:普通会话、cwd 属于已注册工作区却没有归属 —— 归属只会在三种情况下写入(在工作区内新建会话、fork、首次启动的一次性迁移),所以「cwd 有工作区却没归属」意味着归属被摘掉了,这才是删除残留的形态;标着「不可读」的更确定。清理它们是安全的。
- 子代理子会话(
origin: 'subagent'):每委派一次子代理就产生一条。DSH 从不给这类会话工作区归属(dsh-subagent的childSessionMeta只写 cwd / parentSession / origin,没有任何 attach 调用),侧栏也直接不显示它们,所以它们天生满足「无归属」。它们是会话的委派历史而不是垃圾:行内会标出父会话以及「父会话仍在」,删掉会丢内容。 - 未分组的普通会话:cwd 没有注册工作区,本来就会显示在侧栏「未分组」;删掉会丢内容。 扫描只读且逐条保留证据(可读 / 不可读、事件数、是否仍在内存、创建时间、原 cwd、父会话)。清理仍需「勾选 + 输入固定词『删除』」并在 Host 侧校验;仍在内存的走待删除队列,其余当场清除。
- 失效归档记录清理:删除后若残留行又被重新归档(宿主的归档校验查的是开机时建立、之后只增不删的头部索引,日志已不在的 id 仍会被判定为「存在」而接受),本页会出现标注「日志已不存在(归档记录失效)」的条目。顶部**「清理失效记录(N)」**一键清掉这些死记录 —— 只清「归档集合里有、刚取到的列表里没有」的 id,跳过仍在内存中的会话,不触碰任何会话内容。
日志目录的删除命令按平台自适应:Windows 用 PowerShell,macOS / Linux 用 rm -rf。
已知限制
- 仍在内存中的会话走「待删除队列」,绝不当场删日志:归档只把会话从列表隐藏、不会停止它,所以刚归档的那个会话一定还在内存里。运行中的会话会继续写日志 —— 如果当场把日志删掉,而验证只是「删完立刻重新列举」,它的写入链会在验证之后把日志重建出来;那时工作区归属与归档记录已经移除,它就以「未分类 + 数据缺失」的孤儿形态回到左侧工作区(v1.4.0 的回归,v1.4.1 修掉)。现在的做法是:先请 DSH 官方的
archiveSession(id, { stopActivity: true })停止它的运行中工作(turn、子代理后代、它拥有的后台任务与计划),然后直接加入待删除队列,等它退出内存时自动完成(最迟下次重启 DSH —— 刚启动时没有会话在内存里)。队列状态显示在列表上方,界面标记是「仍在内存 · 将加入待删除」。 - 待删除队列的落盘位置:
$DSH_HOME/archived-sessions-pending-delete.json(插件自有的一个小 JSON 文件,原子写入)。除队列外还记录 24 小时内已清除的会话 id,用于自愈:万一某个已清除的日志又被写回来,下一次清扫会把它重新入队、等它退出内存后再删,不会再留下孤儿。这是本插件唯一会写的数据文件。 - 删除要输入标题或会话 ID:每张卡片上都会显示该会话的 ID,确认框里还会再给一次。标题、工作区目录名、会话 ID、以及界面上显示的那串标题,输入任意一个都算通过。
- 有子会话时会先问一次:子会话的日志是独立的,删父会话不会损坏它们。确认框给两个选择——「仅删父会话」(子会话保留,只失去父会话关联)或「连同 N 个子会话一起删除」(整棵子会话树一起永久删除;仍在运行的子会话及其下级会被跳过)。批量删除用的是上面的分级默认值。
安装
要求:DSH >=0.1.7-rc.1 <0.3.0(已在 0.1.7-rc.2(web 宿主)与 0.2.0-rc.1(桌面应用)上实测:兼容检查通过、组成解析通过、Host 激活、客户端产物注册成功)与 pnpm。
# 发布态:钉死提交,最稳定
dsh plugin --profile web add github:Zalpha263/dsh-archived-sessions#<40位commit>
# 开发态:裸目录路径 = link:(源码即部署,改完不用重装)
dsh plugin --profile web add D:/path/to/dsh-archived-sessions
# 卸载
dsh plugin --profile web remove dsh-archived-sessions
装完重启 DSH。之后只有界面(Client)改动刷新页面(Ctrl+F5)即可,Host 改动需要重启。
桌面版(DeepSeek Harness 桌面应用):desktop profile 由桌面应用独占,dsh plugin --profile desktop ... 会被 CLI 直接拒绝(profile "desktop" is managed exclusively by the Electron application)。请在桌面应用侧边栏的插件页里用绝对路径添加本插件目录(或 GitHub 仓库地址),装完重启应用生效。桌面应用自带 Node / pnpm 运行时并走应用内更新(不依赖 npm 全局安装),它的 DSH 版本可能与全局 CLI 不同(实测桌面 0.2.0-rc.1、全局 CLI 0.1.7-rc.2),本插件对两者都通过兼容检查。
常见问题
| 问题 | 原因与解决 |
|---|---|
| 设置里没有「归档会话」 | 装完没有重启 DSH;重启后再看 |
| 列表里出现「日志已不存在(归档记录失效)」 | 归档集合里还留着这个 id,但会话日志已经不在(正常是删除后又被侧栏的残留行重新归档进来的)。点顶部「清理失效记录(N)」一键清掉;只清记录,不删内容 |
| 删除后侧栏还留着那一行(未分组) | 已修(v1.5.3):所有删除路径都会在同一拍刷新侧栏会话列表。旧版本请 Ctrl+F5 |
| 删除时提示「仍在运行」 | 见上面的「已知限制」;重启 DSH 后再删 |
| 删除时提示「该会话有 N 个子会话」 | 这是二次确认不是拒绝:选「仅删父会话」或「连同 N 个子会话一起删除」。仍在运行的子会话会被跳过 |
| 预览提示「读取会话内容失败」 | 该会话是旧格式(v0)日志,当前 DSH 无法读取;不影响删除,按提示输入确认值即可 |
| 恢复后侧边栏没变化 | 正常会自动刷新;没刷新就 Ctrl+F5 |
开发者
lib/index.js—— Host 半区,注册archivedSessions远程服务(list/preview/restore/deleteSession/planDelete/deleteSessions/scanOrphans/purgeOrphans/purgeStaleRecords)。lib/client.js—— Client 半区,web 模块加载器格式,注册设置页的「归档会话」入口。lib/plan.js—— 纯策略函数(子会话分类 / 级联模式 / 确认词 / 结果汇总 / 孤儿三类分拣)。cordis.patch.yml—— bundle 层注册行(id:archived-sessions)。npm test——test/plan.test.mjs(策略单测)+test/host.test.mjs(真 cordis Context + 假服务的 Host 半区集成测试)+test/client.test.mjs(客户端接线冒烟)。
归档集合存在 workspace 存储域(version 2)的 archivedSessionIds 字段里;删除通过 Host 的 shell 服务执行平台命令。改完源码:Host 重启 DSH,Client 刷新页面,全程无需构建。
更新日志
v1.5.3
- 修复(删除后侧栏残留 → 复活的归档记录):用「批量删除」删掉已归档会话后,它们会以未分组的形态重新出现在侧栏;再对着这个残留行点「归档会话」,本页就出现「数据缺失的会话」。根因不是删除本身(日志目录、工作区归属、归档记录三项都真的清了),而是删除结果的传播与失效记录的善后:
- 宿主侧栏的会话列表权威是
sessionQuery.listSessions()(日志介质 ∪ 内存会话),而且是拉取式基线;而一行是否可见由归档集合实时决定。删除顺带摘掉归档记录 → 浏览器里那一行立刻重新可见,但会话列表基线没有重拉 → 它顶着「未分组」显示出来。此前只有单条删除会调sessions.refresh(),批量删除与孤儿清理都没有。 - 把它重新归档时,宿主
workspaceRegistry.archiveSession的sessionKnown()查的是开机建立、之后只增不删的头部索引,所以日志已被删掉的 id 仍被判定为「存在」,归档被接受 → 归档集合里留下一个没有日志的死 id。
- 宿主侧栏的会话列表权威是
- 修正(孤儿扫描口径):扫描的候选判定(日志在 ∧ 无工作区归属 ∧ 未归档)本身没错,但它把子代理子会话和正常的未分组会话一起当成「孤儿」报了 —— 实测本机 19 个候选 19/19 都是
origin: 'subagent'的委派子会话(18 条来自同一项目),一个真正的删除残留都没有,而面板原来的文案(「可读=多半是正常的未分组会话;不可读=删除回归的产物」)并不覆盖这一类。新增classifyOrphan纯函数(lib/plan.js):subagent(委派子会话)/suspect(普通会话、cwd 属于已注册工作区却无归属=残留形态)/unaccounted(cwd 无工作区的正常未分组),scanOrphans返回kind、parentSession、parentAccounted与counts,面板按三类分节展示并各自说明「能不能删」。保留全部候选(不做静默剔除),避免把「没归属」这一事实藏起来。 - 修复:
deleteSessions/purgeOrphans成功后按汇总(deleted + pending > 0)调用sessions.refresh();list()新增drainedIds(待删除队列本轮真正完成的 id,由drainPending记入一个带上限的短账本,因为清扫是 fire-and-forget、列表不能等队列 I/O),客户端见非空即刷新基线 —— 「排空队列后留下幽灵行」这条路径也一并堵住。purgeRecords改为遍历所有 owner 工作区逐个detachSession(此前只摘第一个,第二个归属会把会话重新顶回未分组)。 - 修复:单条删除命中「仍在内存」时,宿主返回的是
{ pending: true, reason, stopError },客户端此前不判断pending、直接提示「已永久删除」;现在如实显示「尚未删除,已加入待删除队列」,并带出排队数量、原因与停止失败信息。 - 新功能(失效归档记录善后):
list()的missing条目在界面上标注为「日志已不存在(归档记录失效)」并打红色标签,删除确认框说明「只会清掉这条失效记录,不会删除任何会话内容」;新增purgeStaleRecords()与顶部**「清理失效记录(N)」一键入口** —— Host 侧自己重新枚举(只清「归档集合里有、刚取到的列表里没有」的 id,绝不相信客户端传来的集合),跳过仍在内存中的会话,清理后照常记入recentlyDeleted;列表读不出来时直接拒绝(无法读取会话列表,已取消清理),绝不把「介质故障」当成「日志都没了」。 - 验证:
npm test15/15 —— 新增test/host.test.mjs5 项(真 cordisContext+ 假存储域/工作区注册表/会话存储:missing标注、purgeStaleRecords只清死 id 且两个归属都被摘、仍在内存的跳过、清单不可读时拒绝、list().drainedIds只在队列真正完成后上报、scanOrphans三类分拣与父会话状态)与test/client.test.mjs3 项(bundle 经 stub loader 加载、inject仍为["slots","remote","sessions","workspaces"]、9 个 Remote 描述符全部挂载、设置区块正常注册;外加一个迷你渲染器把扫描面板真渲染一遍,断言三类分节、计数行、父会话标注与确认词闸门 —— 渲染期抛错会让整个设置区块被 slot 错误边界卸载,语法检查抓不到),策略单测 7 项(含classifyOrphan的三类判定与 Windows/POSIX 路径比较)。客户端行为仍需在运行中的应用里 Ctrl+F5 后实测。
v1.5.2
- 适配桌面版:peer 由
^0.1.7-rc.1放宽为>=0.1.7-rc.1 <0.3.0。桌面应用运行 DSH0.2.0-rc.1,其兼容检查为semver.satisfies(运行时版本, 范围, { includePrerelease: true }),旧范围上界<0.2.0-0不含0.2.0-rc.1;而应用自有 profile 对 peer 不兼容的 bundle 是静默跳过、不打印任何错误(dsh-app-boot的loadProfileDirectory把它们收进skippedBundles),表现就是「装上了却不加载」。放宽后同时覆盖 web 宿主0.1.7-rc.2与桌面0.2.0-rc.1。 - 走廊核对(
0.1.7-rc.1 → 0.1.7-rc.2 → 0.2.0-rc.1,逐包 sha256 + 逐行差异):本插件用到的 host 服务sessionQuery/sessionPersistence/storageDomain/workspaceRegistry(含archiveSession(id,{stopActivity})/archivedSessionIds/detachSession)/sessions/shell(execute(spec)→ShellExecution.result())/fs,以及slots.inject/slots.register、settings.sectionslot(owner 仍是{ close })、ctx.remote.$mount的 CONTRIBUTION 校验(strict codec 仍走create().parse())全部未变,无需改代码。该走廊的真实破坏(workspaceRegistry.initializeDefault签名、workspaces.initializeDefault线协议、client-ui-primitives移除OnboardingSurface)本插件都不使用。 - 桌面版安装方式:
desktopprofile 由桌面应用独占,dsh plugin --profile desktop ...会被 CLI 拒绝(profile "desktop" is managed exclusively by the Electron application);请在桌面应用的插件页用绝对路径添加本插件目录。
v1.5.1
- 界面:「批量删除」与「扫描孤儿会话」并到同一行(同一个操作条)。进入批量模式时该行变成选择工具条(全选 / 清空 / 已选 N / 删除所选 / 退出批量),「扫描孤儿会话」仍在同一行;孤儿说明改为按钮 tooltip + 面板首行,操作条保持紧凑。工具条现在只由
bulkMode决定是否渲染 —— 之前它跟着「列表非空」走,若在批量模式下把列表删空,「退出批量」会消失、卡在批量模式。 - 修复(安全护栏):批量删除现在跳过已离开归档列表的会话(例如期间被恢复、或在别处被删)。单删路径本来就有这条校验,批量路径此前缺失:勾选后若会话状态变化,会被照着旧选择删掉。
v1.5.0
- 新功能(孤儿会话扫描与清理):
scanOrphans()只读扫出「日志存在 + 不在任何工作区 + 不在归档集」的会话,逐条返回是否可读、事件数、是否在内存、创建时间、cwd,让你据此决定;purgeOrphans(ids, word)执行清理,带固定词闸(Host 侧校验),仍在内存的走待删除队列、冷会话当场清除,并记入recentlyDeleted以便日志被写回时自愈。界面入口在「归档会话」区块顶部(「扫描孤儿会话」),面板列出候选、勾选 + 输入「删除」后清理,并给出逐条结果。- 判定口径刻意保守:只列出「无归属 + 未归档」的会话,并把可读性作为证据呈现 —— 正常的未分组会话日志是可读的,一眼可辨;不可读的才是本次回归的产物。
- 验证:孤儿扫描/清理 harness 15/15(已归档与已归属的会话不被列入、不可读被如实标注、固定词闸生效、冷孤儿当场删、内存中孤儿入队并在退出内存后完成、复扫只剩可读孤儿且其余文件原封不动);孤儿面板客户端渲染 11/11(入口、计数、证据文案、闸门禁用/开启、逐条结果)。
v1.4.1
- 修复(回归):批量删除运行中的会话后,左侧工作区出现「未分类 + 数据缺失」的对话。机理:v1.4.0 对内存中的会话采取了「先停活动 → 立即删日志 → 删后立即重新列举验证」,但运行中的会话会在验证之后把日志写回来,而工作区归属与归档记录此时已移除 → 它变成既不属于任何工作区、日志又不完整的孤儿。现在改为:内存中的会话绝不删日志,只请求官方
stopActivity后直接入待删除队列,等它退出内存再删(最迟下次重启)。经此改动,「删了又被写回」这个窗口在插件侧被彻底关掉。 - 自愈:队列文件额外记录 24 小时内已清除的会话 id;若某个已清除的日志又被写回(例如删除瞬间被重新打开),下一次清扫会把它重新入队并等它退出内存后再删,不再留下孤儿。
- 验证:功能级 harness 18/18 —— live 会话删除不执行任何删除命令、日志与归档记录原封不动;cold 会话照常就地删除且分级级联不变(子代理子会话删、fork 保留);复活的日志被清扫重新入队并清除;live 会话退出内存后队列自动完成并清空。单元 6/6 不变。
v1.4.0
- 新功能(批量删除):批量模式 + 顶部工具条(全选 / 清空 / 已选 N / 删除所选 / 退出批量),确认弹窗列出待删会话、显示子会话构成,要求「勾选不可恢复 + 输入固定词『删除』」,Host 侧同样校验该词。执行后给出逐条结果与汇总,一个失败不中断整批。
- 子会话分级默认(已确认):
origin: 'subagent'的子代理子会话默认一并删除(界面上看不到、别处也无删除入口),用户 fork 的子会话默认保留;弹窗可切换「仅删所选 / 分级(默认)/ 全部级联」三种模式。 - 修复(刚归档的会话删不掉):原来的硬闸是
sessions.get(id) !== undefined就拒绝,而归档不会把会话移出内存,所以刚归档的那个必然删不掉、只能重启。现在改为:请求官方archiveSession(id, { stopActivity: true })停止其运行中工作 → 立即尝试删除 → 若日志仍被占用则转入待删除队列($DSH_HOME/archived-sessions-pending-delete.json),在它退出内存时自动完成,最迟下次重启(启动后 1.5 秒做一次清扫;每次列表也会顺带清扫)。列表上方显示队列状态。第三方插件没有驱逐内存会话的公开接口(store 移除归拥有该会话的 fiber),所以「立即且必定成功」做不到,队列是唯一不丢数据且不需要重启两次的可行解。 - 新增
lib/plan.js:把「子会话分类 / 级联模式策略 / 确认词 / 结果汇总」抽成纯函数并加单测(npm test,6/6)。 - 验证:功能级 harness(真实
lib/index.js+ 假宿主模拟「内存中的会话」和「被占用的日志」)18 项断言全过 —— 含分级级联、fork 保留、stopActivity 调用、pending 落盘、退出内存后队列自动完成。
v1.3.7
- 迁移:对齐 DSH
0.1.7-rc.1(自0.1.7-alpha.2)。逐包比对原始产物:@deepseek-ai/dsh-typert-protocol(Remote/TypertRemoteService,含本插件手工 decorator-context 写法)、@deepseek-ai/dsh-client-modules(clientPath()、__ModuleLoader__.load({id,factory}))、以及本插件探测的 6 个 host 服务(sessionQuery/sessionPersistence/storageDomain/workspaceRegistry/sessions/shell)在alpha.2 → rc.1之间逐字节未变,因此无需改接口代码。peer 对齐^0.1.7-rc.1。 - 修正注释(非兼容性改动):
shell.run(spec)在0.1.5-rc.2 → 0.1.7-rc.1之间从未存在 —— 抽象ShellExecutor与 4 个执行器都只有execute(spec),ShellExecSpec也没有foreground字段(“前台”指 await 返回的ShellExecution.result())。原注释声称「0.1.7 把run改名成execute」与原始产物不符;run分支保留为不可达的防御代码并如实标注,行为零变化。 - 验证:隔离
DSH_HOME冷启动 rc.1 → 本插件在宿主__DSH_BOOT__中已注册、客户端产物 HTTP 200 且含__ModuleLoader__.load;node --check通过。
v1.3.6
- 修复:DSH 0.1.7 起 Remote namespace 挂载失败(strict codec 必须带
create()工厂),「归档会话」取不到数据;strictCodec()改为提供create。 - 修复:删除会话报
shell.run is not a function。宿主 shell 服务在 0.1.7 把run(spec)改成execute(spec)→ShellExecution,前台结果改由result()方法给出(不再是属性)。现优先走execute()、回退run();并补上超时/中断判定(它们以exitCode: nullresolve,旧判断看不见,会把没跑成的删除当成成功)。peer 对齐^0.1.7-alpha.2。
v1.3.5
- 新增:会话有子会话时,删除确认框给出两个选项——「仅删父会话」与「连同 N 个子会话一起删除」。级联删除整棵子会话树(叶子优先,父会话最后),仍在运行的子会话及其下级会跳过并在结果里说明。
- 安全:级联与单删共用同一套路径校验(目录名必须等于会话 ID、必须含规范的世代文件名);任一目录删除失败或删除后仍存在都会中止,不会留下半删的持久记录。
v1.3.4
- 新增:卡片上显示「会话 ID」,删除确认框里再显示一次;标题、工作区目录名、会话 ID、界面显示的标题,输入任意一个都能通过确认。
- 变更:有子会话不再直接拒绝。第一次删除会返回子会话数量并要求二次确认;确认后只删除父会话,子会话的日志独立,会保留(仅失去父会话关联)——此前这条路径是死结,因为该页面只能看到归档会话,而子会话通常没有归档、也没有别的删除入口。
v1.3.3
- 修复:删除确认不再因为「读不到持久化标题」而死锁。部分归档会话是旧格式(v0)日志,当前 DSH 拒绝迁移读取,宿主读标题时会直接抛
SessionFormatUnsupportedError,旧代码把它当成不可恢复的错误(重试永远不会成功)。现在读不到标题就退回到「工作区目录名 / 会话 ID / 界面显示的那串标题」,接受其中任意一个。 - 变更:界面把显示的标题一起发给宿主,所以「复制界面上的标题」一定能通过确认;确认失败时提示里会列出所有可输入的值。
v1.3.2
- 适配 DSH 0.1.5-rc.2:
SessionPersistence.listSnapshots()已从接口中移除,改用list()/stat(),消息数恢复按 revision 缓存;永久删除改用公开的resolveCurrentLog(id),并接受带格式版本号的文件名(session.v3.jsonl.zstd)——此前输入标题后必定删除失败。 - peer 依赖对齐
@deepseek-ai/dsh-typert-protocol ^0.1.5-rc.2。
v1.3.1
- 修复:回退路径下消息数永不更新(每次列举都生成唯一 revision,缓存不再被钉死)。
v1.3.0 及更早
- v1.3.0:适配 DSH 0.1.2-rc.1;修复删除确认面板报错、确认门死锁、fork 继承的消息被多算。
- v1.2.1:删除命令按平台分支(非 Windows 也能删);恢复后侧边栏立即刷新;确认失败时回显期望标题。
- v1.2.0:删除时拦截运行中的会话与子会话;删除前做路径校验、删除后做持久化验证。
- v1.1.0:消息数改为后台统计并按日志 revision 缓存。
- v1.0.0:初版(列表 / 预览 / 恢复 / 删除)。
License
MIT
Comments
Loading…