DeepSeek Harness 插件

mrzhangkris/dsh-session-pruner

Star 数 ★ 3 下载量(近 30 天) 961 分类 会话与消息 收录于 2026-08-21 npm dsh-session-pruner

全类型会话生命周期管理:一次性子 agent 完成后归档,闲置的可续聊子 agent 与主会话归档(可恢复),容量上限、投影缓存清理,以及热重载设置面板。

安装

# npm 包(预构建)

dsh plugin --profile web add dsh-session-pruner

# GitHub 源码(首次需按提示配置 allowBuilds 构建授权后重试)

dsh plugin --profile web add github:mrzhangkris/dsh-session-pruner

装任何插件都等于在你的机器上跑第三方代码,权限和你本人一样大——能读你的文件、用你的凭据、访问网络,工具审批管不到它。GitHub 来源的插件还会在安装时执行构建脚本——pnpm 默认拦截,所以安装可能停在 ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED 或 ERR_PNPM_IGNORED_BUILDS;dsh 会打印出需要添加的确切键名,把它加进该 profile 的 pnpm-workspace.yaml 的 allowBuilds 下,重跑一次即可装上。放行构建本身就是一次信任判断:请只安装可信来源,并尽量锁定 commit(github:owner/repo#sha)。

README

DSH 会话生命周期管理插件 — 全类型会话生命周期管理:one-shot 完成即归档、可续子代理与主会话闲置归档、容量保底、连带清理 projcache 缓存。从源头杜绝会话库堆积导致的卡顿。

每类会话都有明确的归宿:跑完的一次性子代理自动归档、闲置的可续子代理/主会话归档、总量超限按优先级回收。先归档(可恢复)再到期删除,GUI 30 秒内自动同步,全程面板配置、热加载生效。

English · Apache-2.0 · npm · · 更新日志

背景

DSH(DeepSeek Harness)的 session_projcache.json 缓存每个会话的完整投影(token 统计、context 压力等),且存储后端每次写入都全量序列化 + 原子替换。当会话库堆积上千个子代理会话时:

  • 缓存膨胀到 100MB+,每次 checkpoint 全量重写 → 主进程 CPU 250%+
  • 单线程事件循环被占满 → 所有会话加载卡顿,甚至 GET / 超时

管理会话生命周期(本插件)是治本:会话不堆积 → 缓存条目不产生 → 卡顿不复发。

功能:全类型生命周期

会话类型 触发 动作 默认
one-shot 子代理 subagent/end / agent/disposed 事件(+ 宽限) 秒级归档(事件驱动) 事件 + 3 分钟宽限
continuable 子代理 闲置超过 N 天 归档(可恢复) 关闭(0 天)
主会话(main) 闲置超过 N 天 归档(可恢复) 关闭(0 天)
任意类型 总量超过容量保底 按「one-shot → continuable → main」+ 最旧回收 400 个
归档目录 保留超过 N 小时 物理删除 24 小时

行为说明(v0.2.3+):one-shot 子代理统一按 oneShotMinAgeMinutes(默认 3 分钟)闲置阈值归档,有/无 end-seed 阈值一致。早期版本中「未写 end-seed 的 one-shot 需闲置满 1 小时才归档」的兜底已移除。

归档机制(可恢复)

被清理的会话先移入 ~/.dsh/sessions-archive/(保留 工作区/会话ID 结构)——GUI 立即消失(列表只读 sessions 目录),但文件还在,可手动恢复:

# 恢复:mv 回 sessions 目录
mv ~/.dsh/sessions-archive/<工作区>/<会话ID> ~/.dsh/sessions/<工作区>/

# ⚠️ 恢复后请立即 pin(或打开)该会话——打开前它不受 live 保护,
# 一个扫描周期内若命中闲置判定(如 one-shot 超阈值、main 超闲置天数)
# 会被再次归档。把会话 ID 加入设置卡片的「Pin 白名单」即可。

也可选「直接删除」(不归档,不可恢复)。

安全保护(双保险)

  • 运行中的会话绝不动:live 会话(内存 session store 里还挂着、被打开/加载中)跳过——且 live 检查 fail-closed:store 查询异常时视为 live,不确定时绝不删除
  • 闲置 = 最后一次日志写入:闲置按会话日志文件 mtime(最后写入时刻)判定,而非目录 mtime——DSH 追加写 session.jsonl.zstd,活跃会话的 mtime 持续刷新,永不被误判闲置
  • one-shot:完成的一次性子代理统一按 oneShotMinAgeMinutes 闲置阈值归档(有无 end-seed 同阈值);容量保底额外跳过缺 session/end-seed 的会话
  • 主会话默认不参与容量回收(可配置)
  • 单点失败隔离:每个动作独立 try/catch

工作原理

双轨触发(事件为热路径,磁盘为权威)
  ┌─ 事件驱动(秒级):subagent/end + agent/disposed
  │     ├─ 500ms 批窗口合并风暴 → oneShotMinAge 宽限复查
  │     └─ 单会话判定(内存优先,磁盘只解压一个)→ 归档
  └─ 定时对账(兜底,默认 60min)
        ├─ pruneArchive:归档目录超期物理删除
        ├─ 遍历 ~/.dsh/sessions/*/ 解压会话日志(系统 zstd,多帧)
        │     ├─ origin: main | subagent       (会话头)
        │     ├─ mode: one-shot | continuable  (subagent/descriptor 事件)
        │     └─ ended: 是否含 session/end-seed
        ├─ one-shot 闲置超阈值 ──→ 归档(archiveMode)
        ├─ continuable/main 闲置 N 天 ──→ 归档
        ├─ 总量 > cap ──→ 按优先级+最旧 归档(跳过运行中/live)
        └─ 每次归档连带:删 projcache 行 + workspace 记账

GUI 同步双轨(变更驱动为主,全量兜底为辅):

  • dirty-flag(主路径):host 每次归档写内存变更日志(单调 seq);client 每 3s 轮询 /plugins/dsh-session-pruner/archived,只有有变更才发 refreshList()
    • refreshSubagents()——侧边栏/任务管理面板秒级一致,无变更零 RPC。
  • 全量兜底:client 每 uiRefreshSeconds 秒刷新两套数据源——主会话列表 refreshList() + 各父会话子代理目录 refreshSubagents()(dirty-flag 失效(host 旧版/路由不可用)时兜底,无需刷新页面)。

安装

从 npm(推荐)

dsh plugin --profile web add dsh-session-pruner

从源码(开发)

dsh plugin --profile web add /path/to/dsh-session-pruner

安装后重启 dsh web 生效(launchctl kickstart -k gui/$(id -u)/com.deepseek.dsh-web)。

配置(设置面板,热加载)

设置面板

安装后打开 设置 → 插件配置 → 会话生命周期管理 卡片,10 项配置保存即热加载(无需重启)。常用 6 项默认可见,4 项低频兜底收在「高级设置」折叠区(高级项有未保存更改时折叠标题会提示):

字段 默认 说明
扫描间隔(分钟) 60 对账兜底周期(事件驱动为主路径)
容量保底(会话数) 400 超限按优先级+最旧回收
界面兜底刷新间隔(秒) 30 dirty-flag 为主(3s 变更检测),此为全量兜底
归档保留(小时) 24 归档目录到期物理删除
归档方式 归档 归档(可恢复)/ 直接删除(不可恢复)
可续子代理闲置归档(天) 0 超过 N 天未活动归档,0 = 关闭
主会话闲置归档(天) 0 超过 N 天未活动归档,0 = 关闭
超限时清理主会话 关 容量超限时 main 参与回收
one-shot 闲置归档阈值(分钟) 3 所有 one-shot 闲置超 N 分钟即归档(有/无 end-seed 统一)
Pin 白名单(每行一个会话 ID) 空 名单内会话永不自动清理(恢复会话后建议立即 pin)

卡片展开后还有实时状态行(30s 轮询):归档数量与最早到期时间、会话总量(含超限提示)、最近一轮清理数量与时间、已固定数量。

环境变量(兜底,面板配置优先):DSH_SESSION_PRUNER_INTERVAL_MS / _MAX / _CLEAN_MAIN / _ARCHIVE_HOURS / _ARCHIVE_MODE / _CONTINUABLE_IDLE_DAYS / _MAIN_IDLE_DAYS / _ONE_SHOT_MIN_AGE_MINUTES / _PINNED_IDS(逗号分隔)。

日志

输出在 guard 的 server-*.out.log:

[dsh-session-pruner] armed: interval=60min cap=400 cleanMain=false
[dsh-session-pruner] hot-reloaded: interval=60min cap=400 ... contIdle=0d mainIdle=0d pinned=0
[dsh-session-pruner] archived a1b2c3d4 (subagent/one-shot) one-shot idle cache=true
[dsh-session-pruner] archive pruned: 2 expired

cache=true/false 表示 projcache 缓存行是否连带清理成功。

测试

npm test               # 回归套件:审计 PoC 校验 + 完整 e2e(隔离的临时 DSH_HOME)
node test/dry-run.js   # 只读扫描全库,验证识别逻辑(不删除)
node test/e2e.js       # 构造 fake one-shot 会话,验证真实清理链路
node test/poc-audit.js # 审计回归:ended 误判 / 双源漂移 / 归档孤儿 / pin 拦截

实现要点

  • 多帧 zstd:DSH 会话日志是多 zstd frame 拼接(append 写入),Node zlib 只解单帧,插件调用系统 zstd 命令(macOS: brew install zstd)
  • 缓存行删除:storageDomain.get('session_projcache').table('sessions').delete(id) 走官方写链(原子持久化 + 内存同步)
  • workspace 记账:归档时同步从 workspace 域移除 sessionId,数据源与磁盘一致
  • 零捆绑依赖:运行时模块(@deepseek-ai/dsh-settings、schemastery)由 DSH 宿主提供,插件自身不携带依赖
  • 面板与热加载:installSettingsSection + 手写 client 卡片(__ModuleLoader__ bundle),onChange 即时重排定时器

开发文档

  • docs/DEVELOPMENT-GUIDE.md — DSH 插件开发实践指南(架构、Host/Client、设置面板、部署运维、坑与解法),为后续插件开发打基础
  • docs/DESIGN.md — 设计决策与理由(三层策略、双轨触发、fail-closed 安全矩阵、关键不变量)
  • docs/TESTING.md — 测试矩阵、验证金字塔(V0/V2/V3)、发布 checklist
  • docs/PROJECT-STATUS.md — 项目状态快照与 backlog,给新贡献者/新会话

已知限制

  • 完成事件丢失时(如 host 中途重启),已完成的 one-shot 子代理要等下一轮对账扫描才发现——最坏一个 intervalMinutes(默认 60 分钟)
  • mv 恢复的会话在打开前不受 live 保护——请 pin 住度过窗口期(见「归档机制」)
  • 依赖系统 zstd 命令
  • 根治性修复在上游:projcache 陈旧会话淘汰 / storage-json 增量写,见 deepseek-harness Discussion #1550

许可证

Apache-2.0

内容来自项目 README(GitHub)↗

链接

同类插件

查看整个分类 →

社区评论

评论公开保存在 GitHub Discussions。加载评论会连接 GitHub 和 Giscus;发表内容需要 GitHub 账号。