跳到主要内容

dsh-compact-after-task

已验证

@xiaobanli/dsh-compact-after-task · v0.1.1 · MIT · Web 界面

Auto-compact a DSH conversation after the current task finishes, at a user-set context threshold. Settings-page switch + slider included.

安装

dsh plugin add @xiaobanli/dsh-compact-after-task

dsh --profile default --dump-config 确认 layer 已生效 —— 参见安装指南

源码

标签

作者

说明文档

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-basicdsh-token-meterdsh-agentdsh-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 等)在 isolate realm 里为每个会话挂载;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,不需要市场、不需要发布)

  1. 把本包复制进 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
    
  2. 在 profile 的 cordis.patch.yml(用户补丁层,在每个 bundle 层之后应用)中插入插件行:

    - insert:
        - id: compact-after-task
          name: '@xiaobanli/dsh-compact-after-task'
          config:
            enabled: true
            thresholdRatio: 0.5
    
  3. 在 profile 的 package.json 中声明依赖,防止以后 pnpm install 把它清掉:

    "dependencies": {
      "@xiaobanli/dsh-compact-after-task": "file:plugins/dsh-compact-after-task"
    }
    
  4. 重启 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。

许可证

MIT