🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI
广告☁️ 云服务器特惠阿里云首购 8 折 · 腾讯云合作特惠
DeepSeek Harness Hub
← 返回列表

QuanhuZeYu/dsh-idle-compactor

DeepSeek Harnessspec-screened扫描:低风险在 GitHub 查看 ↗
未验证

按「闲置时长」触发的上下文压缩插件:会话上下文涨过设定的体量下限、并且安静了设定这么久,就自动压一次。

尚未跑自动兼容性验证,可查看页面内的依赖与入口分析。 · 最近上游提交 2026/9/4 · 已提供中文文档

DeepSeek Harness 的空闲触发上下文压缩插件:当会话静默超过可配置的 token 下限后,对会话进行压缩。

综合分
28.3
GitHub 分
28.3
用户评分
—
★ Stars
0
周下载量
—
安装插件(需先安装 dsh CLI 引擎:npm install -g @deepseek-ai/dsh)
dsh plugin --profile web add QuanhuZeYu/dsh-idle-compactor
该插件未发布到 npm,走 GitHub 源安装(pnpm 若拦截 prepare 脚本,按其提示在 pnpm-workspace.yaml 的 allowBuilds 中放行后重跑)
信任档位:已验证本站已于 0 天前真实安装成功
是什么
dsh 原生插件 · other
装得上吗
本站已真实安装成功(非静态推断)
安全吗
本站尚未对该插件做风险分级(暂未覆盖,不等同于无风险)
还在维护吗
活跃:最近一次提交在 21 天前

档位由下列信号合成:本站实装验证(真实安装,当前最高到 L4)· 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。下方各区块是它的证据明细。 验证判据与等级说明 →

🟢实装验证通过· 2026/9/26
由本站实装验证器在真实 dsh 环境安装成功,非静态推断。
数据截至 2026/9/21(元数据每日更新 · 实装验证按队列轮转,单条结论的验证时间见上方)
用户评分
还没有人投票,来当第一个
订阅周报,不错过优质插件更新
每周一封 · 高评分插件 + 新用户活动

README

由 DeepSeek 最新模型翻译生成
dsh-idle-compactor(中文)

按「闲置时长」触发的上下文压缩插件:会话上下文涨过设定的体量下限、并且安静了设定这么久,就自动压一次。
下次回到这个会话,看到的是已收敛的历史,而不是每次请求都拖着一条没人再看的长尾巴。

DSH 内置的自动压缩只认两个触发条件:请求压力 pressure 与提供方确认的上下文溢出。它不看「这个会话
多久没人用了」。本插件补的就是这一维 —— 闲置时长和上下文体量,两个都得过线才动手。

环境要求

- 带 compaction seam 与 tokenMeter 的 DeepSeek Harness(在 0.1.2-alpha.3 上验证)。
- 零运行时依赖:源码里的 @deepseek-ai/ 全是 import type,编译后消失,lib/index.js 是一个自包含的 ESM 文件。

安装

git clone https://github.com/QuanhuZeYu/dsh-idle-compactor.git
dsh plugin --profile web add ./dsh-idle-compactor

pnpm 9 往 workspace root 加依赖需要显式 -w:
dsh plugin --profile web add -w ./dsh-idle-compactor

装完重启 profile:patch 层是热加载的,bundle 挂载不是。

bundle 自带已提交的 lib/ 产物,所以无论 git 还是路径安装都不触发构建脚本,也就不需要 allowBuilds 授权。
dsh plugin add 做的事就是把 bundle 追加进 profile 的层栈,它的 cordis.patch.yml 才会生效。想手工配,就往
~/.dsh/profiles/web/package.json 里写这两处:

{
"dsh": { "profile": { "bundles": ["@deepseek-ai/dsh-base", "@deepseek-ai/dsh-web-app", "dsh-idle-compactor"] } },
"dependencies": { "dsh-idle-compactor": "link:../../dsh-idle-compactor" }
}

判定条件

每 scanMs(默认 30 秒)扫一遍当前进程里的活体 agent,全部满足才压:

| 条件 | 依据 |
|---|---|
| agent 处于 idle(没有回合、没有维护任务在跑) | agent.status |
| 会话最新一条日志事件距今超过 idleMs | session.events.at(-1).time(durable,重启不会把老会话误判成新会话) |
| 实测请求压力 ≥ thresholdTokens | ctx.tokenMeter.measure(session).totalTokens |
| 模型可见表面也 ≥ thresholdTokens | 同一次 measure 的 surfaceTokens |
| 上次压缩之后确实又有新内容 | 每会话 compactedAt 水位 |
| 冷却 / 失败退避已到期 | cooldownMs、retryBackoffMs |
| 该会话没有被归档 | ctx.workspaceRegistry.archivedSessionIds |
| 该会话没有还在等的后台工作 | 自己名下未结的 ctx.jobs、自己收件箱、以及它作为父会话的活体子会话 |

归档只是把会话从所有一级分组表面隐藏掉,并且故意留下活体 agent 继续跑。所以扫描必须显式点名归档 id:
隐藏一段对话不等于授权改写它给模型看的内容。归档集合还在加载中的会话同样跳过 —— 分类不明的会话一律不动。

压力和表面为什么要各查一遍

totalTokens 是请求压力:锚在上一次成功调用的 provider usage 上,只有下一次调用才会重新定价。
surfaceTokens 才是压缩能动的那部分 —— 模型可见表面的实际内容。刚压完的会话会呈现「压力仍高达 33 万、
表面只剩 7 千」的状态;此时再压只是把已经很短的上下文又摘要一遍,白丢细节。所以两个数都要过线才动手
(这种「压力过线、表面不够」的跳过会记在 debug 级日志里)。

这也是为什么界面上的上下文数字在压缩之后可能还是很高:它显示的是压力锚点,要等下一次请求才重新定价。

执行走标准 seam ctx.compaction.compactNow(agent, signal),也就是 /compact 用的同一个入口:轮次之间的
maintenance 事务,选区与保留策略仍由压缩后端决定。插件自己不选区、不写摘要。

安静不等于干完了

父代理派完子代理、自己结束回合之后,按 loop 的判定它就是 idle;而这段等待多久只取决于子代理,跟
idleMs 无关 —— 一次委托可能跑十分钟,也可能跑三小时。在等待期间压缩父会话,等于把它马上要回去接续
的那份上下文先改写掉。所以扫描还会查三本账:

- 本会话名下的未结束 job(ctx.jobs.list(agent),只算 ownerSession 就是本会话的那些;因此一个跑很久
的后台 pwsh 构建同样会把住它);
- 本会话的收件箱,那里可能已经落了通知但还没开回合;
- 本会话直接或者间接当父会话的活体子会话。

第三本账不是重复的。continuable 子代理(以及被 send_message 唤醒的子代理)在 harness 里根本不登记
job,而子代理 driver 在「已接受 prompt」和「真的开回合」之间那一段还会报 idle;所以只要某个后代在
安静窗口之内产生过任何日志,就算作在飞的活 —— 包括仍在创建窗口里的。已经安静满 idleMs 的子会话不再
把住父会话;不在本进程里的冷子会话本来就管不住,因为没有东西会替它唤醒父会话。

waitWarnMs 只做通报不做放行:一段等待超过它,就在这一段里警告一次,然后继续等。

为什么 bundle 还要额外启用一个 host 平面的压缩后端

Agent preset 把 compaction-basic 挂在 isolate realm 里,而 dsh-web-app 关掉了 host 平面那一份,host 光纤
读不到 realm 实例。所以本 bundle 的 patch 层把 host 那一份重新启用并设 auto: false:它只应答闲置 compactNow(),
不注册自己的压力压缩,压力压缩仍完全归 preset 那份后端管。附带好处:minimal 模式的会话本来根本没有压缩
后端,现在也能被压。

配置

写在 profile 的 cordis.patch.yml 里(后层会覆盖整份 config,要保留的键请一并重写):

- id: idle-compactor
config:
thresholdTokens: 131072   # 128K
idleMs: 600000            # 10 分钟

| 键 | 默认 | 含义 |
|---|---|---|
| enabled | true | 关掉就完全不注册扫描。 |
| thresholdTokens | 131072 | 上下文体量下限;要十进制 128K 就写 128000。 |
| idleMs | 600000 | 闲置窗口。 |
| scanMs | 30000 | 扫描间隔,决定闲置达标后最多再等多久动手。 |
| includeSubagents | false | 是否也压 origin: subagent 的子会话。 |
| excludeArchived | true | 跳过 workspace registry 标记为归档的会话。 |
| skipAwaitingWork | true | 有未结束后台工作(自己的或活体后代的)时一律不压。 |
| waitWarnMs | 10800000 | 一段等待把会话把住这么久就警告一次(每段只警告一次)。 |
| cooldownMs | 60000 | 同一会话两次压缩的最小间隔。 |
| retryBackoffMs | 300000 | 失败或「无可选区」后的退避。 |
| maxPerScan | 1 | 单轮最多压几个会话,限制并发摘要请求。 |
| dryRun | false | 只记日志不真压。 |

未知键名或越界值在装载时报错,不会静默兜底。

模型侧影响

- 每次落地压缩多一次摘要请求,由压缩后端用该会话的路由模型发起;不往对话里插任何提示,也不加 prompt section。
- 模型可见的唯一变化,就是替换掉被压区段的那一个 checkpoint 节点。
- 低于 thresholdTokens 的会话测一次就转入下一轮,不会每 tick 重复测量。

压缩不会做什么

日志永远不被重写。一次压缩追加 compaction/start、compaction/summary、compaction/end,再用一条带
surfaceOp: replace 的 user/message 替换选中区段;shadowedSeqs 记录了被移出模型可见表面的每一条事件,
原文仍在会话日志里,消费方可以读回。存储索引和归档标记都不变。

已知限制与待办

- 只管活体会话。 进程里没有 agent 的会话既没有可测的 surface,也没有承载 maintenance 的 agent,只有被
打开之后才会进入候选。要覆盖冷会话,就得为候选者临时 resume 一个 agent;includeSubagents 默认关掉也是
同一个考虑:后台子会话本来就归父会话的生命周期管。
- 绝不打断正在跑的回合。 资格条件要求 agent.status === "idle",且 compactNow 是轮次之间的维护任务。
它的另一半 —— 回合之间但还在等子代理 —— 由 skipAwaitingWork 兜住:等待没有上限的父会话,只会在等待
结束之后才被压。
- 没有设置面板。 目前只能改 patch 层;加一个带热重载的 settings.section 是下一步。

开发

npm install                                  # typescript + vitest 作为 devDependency
npm run link-dsh --        # 从 checkout 解析 harness 包
npm run build                                # src/index.ts -> lib/index.js
npm test                                     # 12 个集成测试
npm run typecheck:tests                      # 连测试一起类型检查(tsconfig.check.json)

源码依赖的多数 @deepseek-ai/ 包并不在公共 registry 上,所以 scripts/link-checkout.mjs 是把一个本地
DeepSeek Harness checkout 链接进本项目自己的(已被 gitignore 的)node_modules。它不会往那个 checkout 里写
任何东西;同级存在多个 checkout 时它会停下来让你指定,不会替你猜版本。npm install 不是必需的:没有它
脚本会借用 checkout 自带的 typescript 和 vitest,而 npm scripts 是按文件路径而不是按 bin 垫片调用它们的,
所以借来的和装的一样能用。

tests/idle-compactor.spec.ts 跑在真实 agent loop + 真实会话日志 + 真实 tokenMeter + 真实
BasicCompactionEngine(只脚本化它的 summarize)之上,断言的是日志里出现的 compaction/summary 事件,
不是 mock 调用次数。默认测的就是已构建产物 —— 和 profile 加载的是同一份;设 DSH_IDLE_COMPACTOR_BUNDLE
可以指向别的构建。

覆盖:闲置超阈值恰好压一次且实测上下文下降;同一安静期不重复压;有新活动后重新武装;未达阈值不动;
压力过线但表面已低于阈值时不动;归档会话不动(归档集合还在加载时同样不动);dryRun 不写任何东西;
坏配置直接抛。等待这块正反都测:名下有未结 job 的、子会话正在跑回合的,都不压,等工作结束后才压;
而「无关会话在跑」和「job 属于别的会话」两种情况照点压 —— 说明起作用的是父子关联与 job 归属,不是
场上有没有别的动静。

目录结构

src/index.ts              插件本体(纯类型导入,零运行时依赖)
lib/                      已提交的构建产物 —— profile 实际加载的东西
cordis.patch.yml          bundle 层:插件行 + host 平面压缩后端行
tests/                    在真实组装的 harness 上跑的集成测试
scripts/link-checkout.mjs 仅开发用:把 checkout 的包只读链接进来
vitest.config.mjs         独立测试根,不依赖任何 checkout 配置
tsconfig.json             只输出可擦除语法的 ESM,声明文件进 lib/types
tsconfig.check.json       测试平面类型检查:src + specs,带 Node 环境类型

许可证

MIT,见 LICENSE。

上游仓库有新提交时邮件通知你(每天最多一封,无更新不打扰),随时一键退订。

同作者(QuanhuZeYu)的其他插件

💬 加入社群

插件用法、部署报错、新插件第一时间同步——群里问,比一个人翻文档快。

DPharness QQ 群二维码,QQ 扫码进群
QQ 扫码进群
DPharness 飞书群二维码,飞书扫码进群
飞书扫码进群