dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
在 DSH 设置页管理「已归档」的会话:看一看、恢复回去,或者彻底删除。
入口:设置 → 归档会话。
origin: 'subagent' 的子代理子会话(界面上看不到、DSH 也没有别的删除入口)默认一并删除;你自己 fork 出来的子会话(有独立对话内容)默认保留,想连它们一起删就在弹窗里选「全部级联」。仍在运行的后代一律跳过并逐条报告。origin: 'subagent'):每委派一次子代理就产生一条。DSH 从不给这类会话工作区归属(dsh-subagent 的 childSessionMeta 只写 cwd / parentSession / origin,没有任何 attach 调用),侧栏也直接不显示它们,所以它们天生满足「无归属」。它们是会话的委派历史而不是垃圾:行内会标出父会话以及「父会话仍在」,删掉会丢内容。日志目录的删除命令按平台自适应:Windows 用 PowerShell,macOS / Linux 用 rm -rf。
archiveSession(id, { stopActivity: true }) 停止它的运行中工作(turn、子代理后代、它拥有的后台任务与计划),然后直接加入待删除队列,等它退出内存时自动完成(最迟下次重启 DSH —— 刚启动时没有会话在内存里)。队列状态显示在列表上方,界面标记是「仍在内存 · 将加入待删除」。$DSH_HOME/archived-sessions-pending-delete.json(插件自有的一个小 JSON 文件,原子写入)。除队列外还记录 24 小时内已清除的会话 id,用于自愈:万一某个已清除的日志又被写回来,下一次清扫会把它重新入队、等它退出内存后再删,不会再留下孤儿。这是本插件唯一会写的数据文件。要求: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 刷新页面,全程无需构建。
sessionQuery.listSessions()(日志介质 ∪ 内存会话),而且是拉取式基线;而一行是否可见由归档集合实时决定。删除顺带摘掉归档记录 → 浏览器里那一行立刻重新可见,但会话列表基线没有重拉 → 它顶着「未分组」显示出来。此前只有单条删除会调 sessions.refresh(),批量删除与孤儿清理都没有。workspaceRegistry.archiveSession 的 sessionKnown() 查的是开机建立、之后只增不删的头部索引,所以日志已被删掉的 id 仍被判定为「存在」,归档被接受 → 归档集合里留下一个没有日志的死 id。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 test 15/15 —— 新增 test/host.test.mjs 5 项(真 cordis Context + 假存储域/工作区注册表/会话存储:missing 标注、purgeStaleRecords 只清死 id 且两个归属都被摘、仍在内存的跳过、清单不可读时拒绝、list().drainedIds 只在队列真正完成后上报、scanOrphans 三类分拣与父会话状态)与 test/client.test.mjs 3 项(bundle 经 stub loader 加载、inject 仍为 ["slots","remote","sessions","workspaces"]、9 个 Remote 描述符全部挂载、设置区块正常注册;外加一个迷你渲染器把扫描面板真渲染一遍,断言三类分节、计数行、父会话标注与确认词闸门 —— 渲染期抛错会让整个设置区块被 slot 错误边界卸载,语法检查抓不到),策略单测 7 项(含 classifyOrphan 的三类判定与 Windows/POSIX 路径比较)。客户端行为仍需在运行中的应用里 Ctrl+F5 后实测。^0.1.7-rc.1 放宽为 >=0.1.7-rc.1 <0.3.0。桌面应用运行 DSH 0.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.section slot(owner 仍是 { close })、ctx.remote.$mount 的 CONTRIBUTION 校验(strict codec 仍走 create().parse())全部未变,无需改代码。该走廊的真实破坏(workspaceRegistry.initializeDefault 签名、workspaces.initializeDefault 线协议、client-ui-primitives 移除 OnboardingSurface)本插件都不使用。desktop profile 由桌面应用独占,dsh plugin --profile desktop ... 会被 CLI 拒绝(profile "desktop" is managed exclusively by the Electron application);请在桌面应用的插件页用绝对路径添加本插件目录。bulkMode 决定是否渲染 —— 之前它跟着「列表非空」走,若在批量模式下把列表删空,「退出批量」会消失、卡在批量模式。scanOrphans() 只读扫出「日志存在 + 不在任何工作区 + 不在归档集」的会话,逐条返回是否可读、事件数、是否在内存、创建时间、cwd,让你据此决定;purgeOrphans(ids, word) 执行清理,带固定词闸(Host 侧校验),仍在内存的走待删除队列、冷会话当场清除,并记入 recentlyDeleted 以便日志被写回时自愈。界面入口在「归档会话」区块顶部(「扫描孤儿会话」),面板列出候选、勾选 + 输入「删除」后清理,并给出逐条结果。
stopActivity 后直接入待删除队列,等它退出内存再删(最迟下次重启)。经此改动,「删了又被写回」这个窗口在插件侧被彻底关掉。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)。lib/index.js + 假宿主模拟「内存中的会话」和「被占用的日志」)18 项断言全过 —— 含分级级联、fork 保留、stopActivity 调用、pending 落盘、退出内存后队列自动完成。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 通过。create() 工厂),「归档会话」取不到数据;strictCodec() 改为提供 create。shell.run is not a function。宿主 shell 服务在 0.1.7 把 run(spec) 改成 execute(spec) → ShellExecution,前台结果改由 result() 方法给出(不再是属性)。现优先走 execute()、回退 run();并补上超时/中断判定(它们以 exitCode: null resolve,旧判断看不见,会把没跑成的删除当成成功)。peer 对齐 ^0.1.7-alpha.2。SessionFormatUnsupportedError,旧代码把它当成不可恢复的错误(重试永远不会成功)。现在读不到标题就退回到「工作区目录名 / 会话 ID / 界面显示的那串标题」,接受其中任意一个。SessionPersistence.listSnapshots() 已从接口中移除,改用 list() / stat(),消息数恢复按 revision 缓存;永久删除改用公开的 resolveCurrentLog(id),并接受带格式版本号的文件名(session.v3.jsonl.zstd)——此前输入标题后必定删除失败。@deepseek-ai/dsh-typert-protocol ^0.1.5-rc.2。MIT