dsh-compact-after-task
Verified@xiaobanli/dsh-compact-after-task · v0.1.1 · MIT · Web UI
Auto-compact a DSH conversation after the current task finishes, at a user-set context threshold. Settings-page switch + slider included.
Install
dsh plugin add @xiaobanli/dsh-compact-after-task Confirm the layer applied with dsh --profile default --dump-config — see the install guide.
Source
Tags
Creators
Readme
dsh-compact-after-task
一个针对 DSH(DeepSeek Harness,Cordis 插件体系)的插件:当前任务执行完毕后,若上下文压力达到你设定的阈值(默认 50% 的路由模型上下文窗口),自动压缩一次会话历史。装上它,你就不用再手动点压缩、也不用眼睁睁看着上下文和成本一起膨胀。
开关和阈值都可以在 设置 → 插件 里直接调(开关 + 滑块),改完立即生效、无需重启。
它解决的痛点
长对话里,模型每轮都要把完整历史重新发一遍。随着对话变长,几个问题越来越明显:
- 不压缩 → 成本爆炸。 上下文越大,每轮回传的 token 越多。哪怕提供方有 KV 缓存(缓存命中价比全新输入便宜),这些历史每一轮都要重新传输、重新计费。对话从几万 token 滚到几十万、上百万,单轮成本跟着翻几倍——越聊越贵,而且是在不知不觉中。
- 手动压缩又烦又容易忘。
/compact或界面上的压缩按钮确实能用,但每次都要记得自己去点;而人总是等到"明显变慢、明显变贵"才想起来,那时几轮大额开销已经花出去了。 - 内置自动压缩太晚,还卡在任务中途。 内置的
dsh-compaction-basic要等到上下文用到 80%、并且只在 step 边界(任务进行中)才触发——到那个点你已经为好几轮巨型请求付过费了,而且压缩会打断手头正在干的事。 - 响应越来越慢。 前缀越长,每次请求的处理时间越长,体感明显变卡。
这个插件把"记得去压缩"这件事自动化:每次任务跑完(agent 回到 idle)自动检查压力,达到你设的阈值就压一次——在成本开始痛之前就处理,而不是痛了之后才补救。
它和内置压缩的关系(互补,不冲突)
内置 dsh-compaction-basic |
本插件 | |
|---|---|---|
| 触发时机 | 每个 step 边界(agent/pre-step),任务中途 |
任务彻底跑完(agent 回到 idle)之后 |
| 阈值 | thresholdRatio 固定 0.8 |
thresholdRatio 用户可设,默认 0.5 |
| 定位 | 任务中途的最后兜底 | 日常的"任务之间自动瘦身" |
压缩动作与 /compact 完全同款:调用 ctx.compaction.compactNow(),生成一个 <compacted-summary> 检查点,对话界面照常显示"已压缩 N 条历史记录"卡片。
环境要求
- DSH Desktop / DSH profile,且带有基础 bundle(
dsh-base),其中包含dsh-compaction-basic、dsh-token-meter、dsh-agent、dsh-llm。 - 核心自动压缩功能任何版本都能用。 但设置页卡片(开关 + 滑块)需要 DSH ≥
0.1.0-rc.7(2026-08-17 发布):rc.7 起 Host 自动把每个已注册的 settings 命名空间暴露给网页端,第三方插件的设置卡片开箱即用,这正是官方机制。rc.6 及更早版本里命名空间白名单是硬编码的,第三方命名空间会被拒绝(settings-not-exposed)——请升级;确实无法升级时,才用下面第 5 步的补丁。 - rc.7 起本插件改为通过
agentPresets.serviceFor()访问压缩服务,不再inject: ["compaction"]。 原因:rc.7 引入 agent-presets 架构后,compaction服务不再由 host 平面提供,而是由 agent preset(standard/cordis等)在isolaterealm 里为每个会话挂载;host 平面的插件若inject: ["compaction"]会永远 pending(waiting for service: compaction)。本插件在 agent 回到 idle 时用官方 READ 接口ctx.agentPresets.serviceFor(agent, "compaction")取该 agent 的实例(该接口正是为"请求关于某个 session、但从外部到达"的调用者设计的);rc.6 及更早版本自动回退到ctx.get("compaction")。 - 客户端卡片能显示的前提:包的
exports必须放行"./package.json": "./package.json"。dsh-client-modules扫描 client bundle 时用require.resolve("<包名>/package.json")读取包的dsh.client元数据;一旦声明了exports字段而没放行./package.json,该调用会抛ERR_PACKAGE_PATH_NOT_EXPORTED,被永久静默缓存为"非客户端包"——插件 host 侧正常启动,但设置卡片永远不出现。官方包都在exports里带这一行。
安装
两种方式,二选一(不要混用:重复插入同一 entry id,加载器会拒绝启动)。
方式 A:标准安装 —— 插件市场 / dsh plugin add(推荐)
本包自带 dsh.bundle.patch,指向包内的 cordis.patch.yml,是一等公民的 bundle:通过市场或 CLI 安装时,它会被加进 profile 的 dsh.profile.bundles,下次启动时加载器自动应用包自己的补丁层——完全不需要手动改任何配置。
# 从 npm(发布后)—— 或直接从 GitHub:
dsh plugin --profile desktop add @xiaobanli/dsh-compact-after-task
dsh plugin --profile desktop add github:xiaoliang2/dsh-compact-after-task
或者在桌面端 设置 → 插件市场 里一键安装。重启 DSH Desktop 即完成。
方式 B:手动安装(本地 profile,不需要市场、不需要发布)
把本包复制进 profile,并让 profile 的
node_modules能解析到它(与dshmarket的安装方式一致):# 在 DSH home 下执行,例如 C:\Users\you\.dsh\profiles\desktop Copy-Item -Recurse <path-to>\dsh-compact-after-task .\plugins\dsh-compact-after-task New-Item -ItemType Junction -Path .\node_modules\@xiaobanli\dsh-compact-after-task -Target .\plugins\dsh-compact-after-task在 profile 的
cordis.patch.yml(用户补丁层,在每个 bundle 层之后应用)中插入插件行:- insert: - id: compact-after-task name: '@xiaobanli/dsh-compact-after-task' config: enabled: true thresholdRatio: 0.5在 profile 的
package.json中声明依赖,防止以后pnpm install把它清掉:"dependencies": { "@xiaobanli/dsh-compact-after-task": "file:plugins/dsh-compact-after-task" }重启 DSH Desktop,profile 变更才会生效。
如果已经用手动方式 B 装过,请先卸载/移除,再走市场安装;否则同一个 entry id
compact-after-task会被插入两次,加载器拒绝启动。
仅 DSH ≤ 0.1.0-rc.6 需要(无法升级时才做)
rc.7 之前 Host 只暴露一份硬编码的命名空间白名单,因此要让设置卡片能读能写,得先把本插件的命名空间加进去(应用安装目录下两处文件,位于 resources\app.asar.unpacked\node_modules\@deepseek-ai\dsh-host-apiproxy):
lib/index.js—— 在const WEB_SETTINGS_NAMESPACES = [ ... ](约第 888 行)里加"compact-after-task",lib/types/api-proxy.js—— 在单行的const WEB_SETTINGS_NAMESPACES = [...](约第 50 行)里加"compact-after-task"
然后重启。注意 DSH 升级会覆盖这两个文件,每次升级后要重打。rc.7+ 不需要这个补丁,且这两个文件已不存在于新版。
配置项
| 键 | 类型 | 默认值 | 含义 |
|---|---|---|---|
enabled |
boolean | true |
总开关。 |
thresholdRatio |
number (0.05–1) | 0.5 |
上下文压力达到该比例(相对路由模型 context window)时压缩;0.5 = 50%。 |
onlyRoots |
boolean | false |
只压缩顶层对话 agent(跳过后台 subagent)。 |
设置页调节(开关 + 滑块)
装好后,在 设置 → 插件 → 可配置插件 里会有一张"自动压缩"卡片:
- 开关:是否在任务结束后自动压缩;
- 滑块:自动压缩阈值(5%–100%,步进 5%)。
拖动滑块松开即保存,settings/updated 事件实时生效,无需重启。设置值持久化在 settings 文档里,优先于 cordis.patch.yml 里的加载器配置(加载器配置作为默认值/基础层)。
阈值怎么选
按"你能接受的单轮回传上限 ÷ 模型上下文窗口"来定:
- DeepSeek 默认 1M 窗口:0.3~0.6 都是合理区间,0.5 是保守稳妥的默认值;
- 想让单轮回传 ≤ 40 万 token → 0.4;≤ 25 万 → 0.25;
- 别设 ≥ 0.8:内置压缩会在 0.8 的 step 边界先触发,你的阈值设得比它还高就失去意义;
- 小窗口模型(128K/64K)建议 0.6~0.8,否则有效工作上下文会被压没;
- 阈值太低(如 0.1 以下)意味着刚干完小事就压一次,摘要把还有用的细节也吞了,得不偿失。
调完跑一两轮长任务看体感:刚干完小事就压缩、摘要变粗糙 → 阈值太低;每条消息还是回传大几十万 token → 阈值太高。改 cordis.patch.yml 里的 thresholdRatio 后重启生效。
行为细节
- 监听
agent/status事件,只在idle状态转换时动作 —— 也就是本轮任务的驱动器已完全排空,正是"执行完本次任务再压缩"的时机,不会打断任务。 - 压力口径与内置引擎一致:
tokenMeter.measure(session).totalTokens对比contextWindow × thresholdRatio(context window 通过llm.resolveModelInfo解析)。 - 压缩是一次
ctx.compaction.compactNow(agent, signal)调用 —— 与/compact完全同一操作;compactNow会保留下一次轮次的入场权,后续消息会被正确地排在压缩完成之后。rc.7 起ctx.compaction换成agentPresets.serviceFor(agent, "compaction")取得的实例,操作不变。 - 预期内的失败静默处理:
busy(agent 忙 / 已有压缩在跑)与cancelled直接跳过;其余失败仅记 warn,不影响 agent 生命周期事件。 - 若某 agent 挂载的 preset 不包含 compaction 组(如
minimal),serviceFor返回undefined,该 agent 自动跳过压缩。
测试
npm install
node test-compact-after-task.mjs
单测用桩服务覆盖:空闲+超阈值触发、低于阈值不触发、running 不触发、busy 静默、disabled 完全不动作、onlyRoots 过滤 subagent。