Bundle
dsh-spend-guard
Rolling-window spend fuse for DeepSeek Harness: warns at 1 CNY and cancels the running turn at 2 CNY spent within the last 10 minutes.
- Source
- summmmerz
- License
- MIT
- Updated
- Updated 8 days ago
Readme
# dsh-spend-guard
DeepSeek Harness 的**滚动窗口花费熔断**(宿主侧插件)。
> 最近 **10 分钟**内的花费达到 **¥1** → 弹 Windows 通知提醒;
> 达到 **¥2** → **结束当前这一轮**(等同你按 GUI 的「停止」按钮)。
## 为什么需要它
- dsh 核心**没有**任何花费/步数上限开关。
- `dsh-cost-meter` 的「预算」是**展示型**的:只有 今日/本月/累计/自定义区间 四种周期,
行为仅限于 ≥80% 预警、≥100% 超支时变色,**不会暂停任务**。
- 插件市场里也没有「滚动分钟窗口 + 金额 + 停当前轮」的组合
(`dsh-budget` 是 会话/日/月 + USD 且 peer 范围写死 `<0.2.0`;
`dsh-balance-tracker` 单位是元、动作是取消当前回合,但窗口是**自然日**,
且依赖 dsh 0.2.0 已移除的 0.1.x 客户端包)。
## 它怎么算钱
不自己定价,而是**轮询差分** `dsh-cost-meter` 的账本
(`<DSH_HOME>/storages/cost-meter/ledger.json`):
1. 每 `pollMs`(默认 10s)读一次账本;
2. 对**当天聚合值** `days[<日期>].cost` 求相对上次轮询的增量;
3. 增量按采样时刻塞进环形窗口,窗口内求和即为「最近 N 分钟花了多少」;
4. 金额口径跟随账本:`pricingCurrency`(默认 USD)× 账本自己的 `config.exchangeRate`
(默认 7.2)= 人民币元,与 dsh-cost-meter 界面上的数字同源。
### ⚠️ 为什么不用 per-session 明细(踩过的坑)
账本里 `days[日期].sessions[]` 是「每个会话当天的累计值」,**且在重负载日会截断**。
实测 2026-10-04 那次事故当天:该数组只保留 **200 条、仅覆盖当天 42% 的花费**
(33.95 / 81.17 元),缺口 47.2 元全是**子代理**消耗 —— 那天正是一次 `workflow`
工具扇出 383 个子代理烧掉的。
所以按条目差分会**漏掉本插件最该拦住的那类事故**。当天聚合值在任何负载下都是完整
口径(含子代理),因此只信它。`test/window.test.mjs` 里有两条回归测试锁住这个行为
(`sessions` 为空、以及条目被替换/截断时,窗口仍必须计数)。
**代价**:检测延迟最多约 `pollMs` + cost-meter 的落盘间隔(默认各 10s)。
对一个 10 分钟窗口的熔断来说不影响使用。
**降级**:账本读不到(未装 cost-meter、格式变动、权限问题)时**不熔断**,只每 5 分钟
打一条警告日志 —— 算不出钱的守卫不应该把 harness 弄死。
## 它怎么停
```js
agent.cancel({ kind: 'user' }, { keepInbox: true })
```
与 GUI「停止」按钮**完全同一条路径**(见 `dsh-api-session-controller` 的 stop 实现):
只结束当前回合,不销毁会话,**已排队的输入保留**。
默认会结束**所有正在运行的 agent**(含子代理)—— 熔断的目的是立刻止血,子代理同样
在烧钱(2026-10-04 那次 47 元几乎全在子代理上)。用 `cancelScope: root` 可只结束主会话。
## 配置
默认值可通过 profile 的 `cordis.patch.yml` 覆盖:
```yaml
- id: dsh-spend-guard
config:
windowMs: 600000 # 滚动窗口(ms),默认 10 分钟
warnYuan: 1 # 窗口内达到该金额(元) → 提醒
stopYuan: 2 # 窗口内达到该金额(元) → 结束当前回合
pollMs: 10000 # 账本轮询间隔(ms)
field: cost # cost(含订阅折算) | apiCost(纯 API 口径)
exchangeRate: null # 汇率覆盖;null = 用账本的 config.exchangeRate
warnCooldownMs: 60000 # 两条提醒之间的最小间隔(ms)
stopCooldownMs: 60000 # 两次熔断之间的最小间隔(ms);0 = 超限就一直熔断
cancelScope: all # all=结束所有在跑的 agent(含子代理) | root=只结束主会话
toast: true # 是否发 Windows 通知
debug: false # 每次轮询打印窗口金额
ledgerPath: "" # 账本路径;留空=自动定位
```
禁用整行:
```yaml
- id: dsh-spend-guard
disabled: true
```
## 日志
```
[dsh-spend-guard] 已启用: 滚动 10 分钟, 提醒 ¥1.00 / 熔断 ¥2.00, 账本 <path>
[dsh-spend-guard] 提醒: 最近 10 分钟已花 ¥1.44(停止阈值 ¥2.00)。
[dsh-spend-guard] 熔断: 最近 10 分钟已花 ¥2.16(阈值 ¥2.00),已结束当前回合。(被结束的 agent 数: 1)
```
用 `提醒:` / `熔断:` 两个标记即可 grep。
## 测试
```sh
node test/window.test.mjs
```
用合成账本驱动真实的 `apply()`,覆盖:首次轮询只建基线、按当天聚合增量而非累计、
1 元提醒 / 2 元熔断、**`sessions[]` 为空或被截断时仍必须计数(回归)**、
跨日新日期不误熔断、聚合值回退不作负增量、`cancelScope` 语义、
以及熔断必须走 `cancel({kind:'user'}, {keepInbox:true})`。
## 已知边界
- 依赖 `dsh-cost-meter` 在跑并持续落盘;它没启用时本插件静默不生效(只记日志)。
- 窗口精度受轮询间隔限制(默认 ~10-20s)。
- 只统计 dsh 自己计到的花费;在别处(其他终端/网页)产生的消费不在窗口内。
- 熔断会结束**所有**在跑的回合(默认,含子代理);如果你同时跑多个无关会话且不希望
被连带中断,设 `cancelScope: root`。
Install
dsh plugin --profile web add github:summmmerz/dsh-spend-guard
Profile: web
With the hub plugin installed, ask your agent to install it by name — it resolves the same plan shown here.
dsh plugin --profile web add github:stvlynn/dsh.fish#path:packages/dsh-plugin-hub
install dsh-spend-guard from the hub
- This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.