← 返回列表
未验证
🧊 dsh-cache-guard
尚未跑自动兼容性验证,可查看页面内的依赖与入口分析。 · 最近上游提交 2026/9/19 · 已提供中文文档
DSH 插件会在自动上下文重写生效前先估算其代价,并先询问——冷重读,以 k 个 token 计。
综合分
29.4
GitHub 分
29.4
用户评分
—
★ Stars
0
周下载量
—
安装插件(需先安装 dsh CLI 引擎:npm install -g @deepseek-ai/dsh)
dsh plugin --profile web add loonylabs-dev/dsh-cache-guard该插件未发布到 npm,走 GitHub 源安装(pnpm 若拦截 prepare 脚本,按其提示在 pnpm-workspace.yaml 的 allowBuilds 中放行后重跑)
信任档位:已验证本站已于 0 天前真实安装成功
- 是什么
- dsh 原生插件 · chat
- 装得上吗
- 本站已真实安装成功(非静态推断)
- 安全吗
- 本站尚未对该插件做风险分级(暂未覆盖,不等同于无风险)
- 还在维护吗
- 活跃:最近一次提交在 6 天前
档位由下列信号合成:本站实装验证(真实安装,当前最高到 L4)· 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。下方各区块是它的证据明细。 验证判据与等级说明 →
🟢实装验证通过· 2026/9/25
由本站实装验证器在真实 dsh 环境安装成功,非静态推断。
数据截至 2026/9/25(元数据每日更新 · 实装验证按队列轮转,单条结论的验证时间见上方)
依赖的 DSH / Cordis 模块
@deepseek-ai/cordis@deepseek-ai/cordis-plugin-group@deepseek-ai/cordis-plugin-include@deepseek-ai/cordis-plugin-loader@deepseek-ai/dsh-agent@deepseek-ai/dsh-compaction-basic@deepseek-ai/dsh-compaction-tool-result-pruner@deepseek-ai/dsh-llm@deepseek-ai/dsh-session@deepseek-ai/dsh-token-meter用户评分
还没有人投票,来当第一个
订阅周报,不错过优质插件更新
每周一封 · 高评分插件 + 新用户活动
README
由 DeepSeek 最新模型翻译生成🧊 dsh-cache-guard
主机层与代理层的 DeepSeek Harness (DSH) 插件,会在每次自动上下文重写落地前先为其定价,默认询问人类,并报告提供方不得不冷重读的内容。
npm version
CI
Node.js
License: MIT
GitHub
📋 目录
- 它防止什么
- 它展示什么
- 它在何处生效
- 它如何拦截
- 保证
- 安装
- 配置文件
- 配置
- 验证与测试
- 已知限制
- 🤝 贡献
- 🔗 链接
- 许可证
它防止什么
提供方只会复用其缓存请求前缀,直到最后一个未更改的 token 为止。有两个自动操作会重写模型可见的表面,因此二者都会按全价重新计费保留的上下文——而没有人被询问过:
| 操作 | 它重写什么 | 为什么昂贵 |
|---|---|---|
| 工具结果修剪(dsh-compaction-tool-result-pruner) | 过大的工具结果,就地重写 | 它从最旧的候选项开始,因此其后的所有内容都会变成缓存未命中 |
| 摘要(dsh-compaction-basic) | 一段较旧的区间,被一个检查点替换 | 该检查点从被替换的位置开始失效,而摘要本身是一次全前缀模型调用 |
在一个真实会话上测量(deepseek-v4.1-flash,1M 窗口):修剪器一次性重写了 28 个工具结果,释放了 93k token,而紧接着的下一个请求以全价重新读取了 720,764 个 token,而不是 16,768 个——一个请求花费 $0.11,而该会话在三个步骤后就结束了。
dsh-cache-guard 位于这两个操作之前,先为它们定价,然后询问。
它展示什么
更改前的对话框,包含决定它的数字:
Context rewrite
An automatic context rewrite is ready. Allow it?
Context: 839k of 1.05M (rewrite at 839k)
Prunes 28 old tool results: 93k freed
然后总结:约 4k 检查点(估计值)而非 746k
在位置 0 处破坏缓存:255k 被完整重新读取(占请求的 95%)
[ 允许一次 ] [ 暂不 ] [ 始终允许(本次会话) ]
以及输入框工具行中的一个 chip——位于输入卡片内,紧跟在访问模式 chip 之后,采用与其相邻元素相同的几何结构(28px 胶囊、设计系统字形、打开时旋转的 chevron)。它承载状态;点击它会打开一个小菜单,显示这些数字。标签有四个值:Cache: ask、Cache: auto、Cache: off 和 Cache: not armed——最后一个用于没有压缩引擎被守护的进程,正是这种状态让一次重写破坏了缓存却无人应答。字形跟随模式——守护程序询问时是问号,无提示运行或关闭时是勾号——在窄输入框中,标签会折叠为字形 + chevron,与相邻 chip 完全一样。
[ (?) Cache: ask ⌄ ] ╭────────────────────────────────────────────╮
│ Context 837k of 1.05M · rewrite at 839k │
│ last rewrite: 28 tool results pruned · │
│ 724k re-read in full (declined, nothing │
│ changed) │
│ ────────────────────────────────────────── │
│ (?) Ask before every rewrite ✓ │
│ Pruning and summarizing wait for your │
│ approval. │
│ (✓) Allow automatically │
│ Runs without asking; its cost shows up │
│ here afterwards. │
│ (✓) Guard off │
│ The engine compacts as shipped: │
│ nothing is priced, asked, or blocked. │
╰────────────────────────────────────────────╯
其菜单行遵循 harness 的 Menu:前导 16px 字形、带提示的标签,以及选中行的尾部勾号——选中状态是勾号,从不是颜色填充,与相邻菜单完全一样。
菜单浮动在所有列之上:它被 portal 到文档 body,并根据 chip 以视口坐标定位,因此工作室更宽的列既不能裁剪它,也不能覆盖它。打开和关闭遵循与模型选择器相同的规则——chip 的 chevron 切换它,点击菜单外任意位置关闭它。
这个长句也位于 chip 的工具提示中,因此悬停即可显示,无需打开菜单。宿主按会话保留所选模式。
它在何处生效
安装插件就是全部的启用操作:宿主端守护每一个会话、每一个预设,无需选择任何东西。
| 部分 | 所在位置 | 作用范围 |
|---|---|---|
| 宿主半 | profile 包 | 每个会话:每个预设领域引擎上的门控、药丸、其菜单以及模式端点 |
| 引擎半(可选) | 位于 compaction 组内的 agent 预设 | 仅该预设——为 profile 无法承载宿主半的部署提供的回退方案 |
agent 的引擎在其预设内创建:随附的 compaction 组隔离了 compaction 和 toolResultPruner,因此 profile 自身的层无法触及该实例。普通的 internal/service 监听器也听不到该注册——事件携带一个作用域过滤器,会丢弃提供领域之外的所有监听器。宿主半以 { global: true } 监听,恰好绕过该过滤器,并在每个引擎的领域宣告它的那一刻将其包装。这正是 harness 自身的预设不变量用来观察领域注册的同一接缝。
由于门控是从宿主平面武装的,在随附的 standard 预设上组成的会话——即产生 What It Prevents 中测量的情形——会像任何其他会话一样受到保护。退出是显式的,有三种粒度:药丸中的会话模式 off、作为 profile 或预设配置默认值的 mode: off,以及移除该包。
如何拦截
两个操作都通过一个方法运行——活动 compaction 引擎上的 compactIfNeeded。修剪是该调用内的第一阶段,摘要是第二阶段,而 provider 溢出恢复路径进入同一方法。compaction-basic 注册其监听器时将 this 绑定到引擎实例,并在内部调用 this.compactIfNeeded(...),因此替换这一个实例方法即可拦截两条路径。包装 compaction 服务则不能,只看到宿主平面服务的监听器也不能。
宣告的值是一次追踪读取,因此其 ctx 以读取上下文作答。因此,门控取其背后的实例(cordis 将其暴露在全局注册表符号 cordis.original 下),并通过该实例自身的上下文进行计价,该上下文解析领域的 pruner 和 meter。通过宿主平面计价则完全不会规划修剪,并低估冷重读。
因此,该守卫:
1. 在触碰任何东西之前对挂起的计划计价——哪些工具结果超出 pruner 的预算(由真正的 pruner 自己的 pruneContent 决定,且不进行变更)、摘要将替换哪个跨度,以及有多少 token 在哪个表面位置破坏缓存;
2. 询问(手动模式)或报告(自动模式);
3. 在接受时自行运行操作:先执行引擎的公共 compactRegion 事务,然后执行 pruner。在拒绝时——或当没有可决定的事项时——它返回 null,引擎将其解读为没有可压缩的内容。
守卫从不调用原始方法。 这正是让引擎不会在守卫背后运行自己的两阶段流程的原因,也是这些阶段能够配对的原因:检查点落在最旧的被替换位置,因此后续的每一次修剪重写都位于那个缓存断点之后,不会给它增加任何东西。反过来,只要摘要最终落地,就会两次付出缓存断点的代价;而且如果范围被拒绝,还会留下一个已被修剪的表面。引擎的事务是原子的,因此被拒绝的范围会在任何内容写入之前抛出异常。
没有从压缩包中导入任何内容,也没有修改任何引擎代码:守卫读取引擎的公开配置,调用其公开的 compactRegion,并在卸载时恢复原始方法。围绕该操作由 harness 拥有的一切——事务、事件生命周期、持久化以及调用监听器中的重试记账——仍然归 harness 所有。
修剪和摘要是一项操作
引擎自己的流程会先尝试无模型阶段,只有在表面仍然过大时才会进行摘要。停在那里的流程代价很糟糕:修剪是就地重写,因此表面几乎仍然一样大(实测:839k → 737k),而下一个请求会以接近完整大小付出缓存断点的代价——720,764 个 token 冷重读,而这一流程只释放了 93k,却需要 186。
守卫总是同时规划两者。同一个会话,按守卫将替换的跨度计价,冷启动成本为 255k:表面降至保留尾部加一个检查点,并且缓存断点只付出一次。
保证
- 守卫的 bug 永远不会阻塞引擎。 如果计价抛出异常,则运行原始调用并记录失败。无法为某个操作计价的守卫不能决定该操作:将自己的失败视为拒绝,会悄悄停止它所守卫的每个会话中的压缩。
- 提示条永远不会承诺宿主无法兑现的保护。 它会读取该进程实际守卫了多少个引擎;如果没有,它会显示 Cache: not armed,而不是“waiting for your approval”。
- 在沉默时故障关闭。 当模式设置为 manual 且未注册问题提供者时,自动重写会被拒绝,日志会说明如何允许它(mode: auto)。
- 不重复唠叨。 一次拒绝会在当前回合剩余时间内静默守卫,而不是在每一步都询问。
- 每个引擎一个包装器,无论谁先到达。 宿主部分通过全局注册表符号标记包装函数,因此第二个到达的预设行会识别出这项工作并保持惰性,即使这两部分是该包的不同副本。
- 没有新的会话事件类型。 该 bundle 不添加任何 SessionEventMap 成员:此构建不认识的事件类型会使会话日志对写入它的同一构建不可读。客户端表面从现有事件和守卫自身的状态中派生一切。
- 零运行时依赖。 该包不导入自身之外的任何内容,因此它不会绑定到 harness 类的第二份副本。它读取两个全局注册表符号(cordis.original),而不是导入 cordis。
安装
dsh plugin --profile web add file:C:/path/to/plugins/dsh-cache-guard
这就是全部安装过程:bundle patch 挂载宿主半部分,而宿主半部分守护每个预设领域的引擎。一旦某个会话的领域出现,日志就会显示 dsh-cache-guard: guarded a compaction engine of a preset realm (1 in this process, mode manual),并在启动时显示 dsh-cache-guard: host ready (default mode manual)。
只有在无法安装宿主半部分的地方才需要预设行——即在不使用 bundle patch 组合其 profile 的部署中。它必须位于预设的 compaction 组内部,因为该组隔离了 compaction 和 toolResultPruner 服务。安装器会写入一个预设,该预设包含随附的组合,并将那一行 patch 进去:
node tools/install-profile.mjs --profile web --preset cache-guard
~/.dsh/.agent-presets/cache-guard/agent.cordis.yml
- id: base
name: 'cordis:include'
config:
path: 'file:////config/agent-presets/standard/agent.cordis.yml'
patches:
- id: compaction
insert:
- id: cache-guard
name: 'file:////.dsh/profiles/web/node_modules/dsh-cache-guard/engine.js'
config:
mode: manual
cordis:include 会将 patches 应用于它读取的条目,而带有 id 的 insert patch 会将其行推入该组的子列表中。因此,该预设是随附的组合加上一行,而不是一份副本:对随附预设的 harness 更新也会在此生效。相同的文件结构也适用于 code、cordis 或你自己的预设——只需更改 --source。
预设是在会话启动时选择的,因此正在运行的会话会保持它开始时的组合——这就是为什么是宿主半部分,而不是预设行,使守护覆盖每个会话。当使用某一行时,其日志行为 dsh-cache-guard: engine guarded (mode manual),而在同时带有宿主半部分的 profile 中,该行会报告 already guarded by the host plane。
Profiles
预设根目录和设置文档属于 harness home,而不是某个 profile,因此:
- 宿主半部分守护其安装所在 profile 的每个会话——每个 profile 安装一次,并且该 profile 不需要预设。
- 预设行在使用时,对该机器上的每个 profile 都可用:引擎半部分是自包含的(它从自己的目录解析每个依赖),因此一份安装副本即可服务所有 profile。
- 胶囊及其模式菜单需要该 profile 中的宿主半部分。没有它,屏幕上没有任何东西可以切换模式——而且没有它,也没有任何东西守护任何东西。
- --set-default 会写入机器范围的 agent-presets.default。它只对基于行的安装有意义;在已安装宿主半部分的情况下,无论会话运行什么预设,它们都已受到保护,所以不要动配置文件自身的默认值。
- 一个没有 question provider 的配置文件(无头、自动化)没有什么可询问的:在 manual 模式下,守卫会拒绝每一次自动重写,会话最终会撞上它的窗口。对于这类配置文件的宿主行(cordis.patch.yml),设置 mode: auto,或者在那里把守卫设为 off。
对于插件开发,请以链接方式安装,这样编辑无需重新安装即可生效(file: 条目是副本,pnpm 不会刷新它们):
dsh plugin --profile web add link:C:/path/to/plugins/dsh-cache-guard
配置
宿主行说了算。预设行的配置仅在宿主半部分缺席时适用——一个引擎只会被包装一次,由先到者完成,而宿主平面总是先宣告,然后预设行才会应用。
| 键 | 默认值 | 含义 |
|---|---|---|
| mode | manual | manual 会在自动表面变更前询问;auto 只报告它;off 将操作留给引擎按原样处理 |
| pricePerMTokens | 未设置 | 每百万 token 的全价输入费率;在对话框和胶囊中加上货币金额 |
| estimatedSummaryTokens | 4000 | 在为计划中的摘要定价时假定的检查点大小 |
触发阈值属于引擎,而不属于守卫:它是 compaction-basic 行的 thresholdRatio(默认 0.8,即当路由窗口已使用 80% 时进行压缩)。安装器可以设置它——它会修补生成的预设中该行的配置,而这就是该行配置的全部内容,因为补丁是替换而不是合并:
node tools/install-profile.mjs --threshold 0.9
验证与测试
npm test # 单元测试和契约测试
node tools/simulate-session.mjs 02abdc01 # 为真实会话的第一次自动重写定价
node tools/install-profile.mjs --dry-run # 显示安装器将会更改的内容
node tools/verify-preset.mjs ~/.dsh/.agent-presets/cache-guard/agent.cordis.yml
dsh web --dump-config | Select-String cache-guard
tools/simulate-session.mjs 会把真实会话日志截断到其第一次自动重写之前的那一刻,重建一个真实的 Session,通过真实的 token 计量器和真实的修剪器为待处理的计划定价,并将预测与日志随后记录的 provider 用量进行比较。在上述会话中,它预测了 28 次重写、第一次变更发生在表面位置 8,以及 724,447 个冷 token——而实际是 28 次重写和 720,764 个全价 token:误差 0.5%。
tools/verify-preset.mjs 通过真实的 Loader 组合一个预设文件,并打印其内容——每一行、compaction 组的子项,以及引擎行携带的配置。它回答的是配置变更所引发、而单纯读取文件无法回答的问题:该 profile 默认的阈值是否真的能到达 compaction-basic,以及引擎行是否仍在组内。各行以桩形式挂载,因此它不需要服务、不需要模型,也不需要运行中的服务器。
已知限制
- 冷启动拆分是校准的,而非保证的。 请求总量来自 harness 自身的压力数值(在会话有 provider 用量时以 provider 为锚);固定部分从会话中最便宜的真实请求读取。一条非常大的开场消息会使固定部分略高、冷启动估算略低。
- 检查点大小是估算值——计划中的摘要化以 estimatedSummaryTokens 计价,因为真实摘要在其运行之前并不存在。剪枝数值是精确的,且计划会说明它属于两者中的哪一种。
- 按会话的模式是进程本地的。 “为此会话自动允许”会持续到 harness 重启为止;持久记录需要此构建已知的会话事件类型,而仓库外插件无法添加。
- 被拒绝的溢出仍会结束该轮次。 在 provider 确认的上下文溢出时,窗口已经耗尽,因此拒绝会保留原始的 provider 错误。
- 该计划镜像引擎的解析结果。 触发比率、保留预算、路由到的 provider/model(来自引擎自身读取的持久化 request/header),以及安全截断规则,都在这里根据公开的引擎配置和会话表面重新计算——因为守卫会选择它要替换的范围。上游的变更必须在这里跟进;引擎拒绝的范围会使表面保持不变并记录失败。
- 没有宿主那一半的 profile 完全不受守卫。 该门控由 bundle 的宿主行安装;一个在没有它的情况下组合(或将其禁用)的 profile 没有否决、没有对话框,也没有 pill。预设行会覆盖这种情况,一次一个预设。
- pill 的保证是推导出来的,而非承诺的。 Cache: ask 意味着该进程至少守卫一个引擎;新会话会在其第一步之后、实时上下文行出现时确认这一点。当没有任何东西被守卫时,Cache: not armed 是诚实的答案。
- 定价失败是花费而非搁浅。 如果守卫无法为某个操作定价,它会将该操作交给引擎并在日志中说明。这与会话在没有该插件时会付出的缓存成本相同——这是刻意选择的,而非在每个会话中拒绝每一次重写。
- 没有提问 provider 的 profile 会阻塞而非花费。 在 mode: manual 且没有可提问对象时,守卫会拒绝并记录;会话随后运行到其窗口上限。对于无头 profile 的行,请使用 mode: auto。
- 除 harness 术语外,其余内容均为英文。 代码、注释、文档、对话文案以及 pill 均为英文;只有 guard 报告的 harness 术语(compaction/prune、thresholdRatio、retainRatio)保留其上游拼写。
🤝 贡献
保持布局:lib/ 下每个关注点一个模块,test/ 下每个模块一个测试文件,每一项声称的测量结果都可追溯到真实会话。在更改前后运行 npm test。
🔗 链接
- 📚 DeepSeek Harness
- 🐛 问题反馈
- 📦 NPM 包
许可证
MIT © loonylabs-dev
由 loonylabs-dev 维护