Skip to content
dsh.fish
Bundle

dsh-budget-handoff

DeepSeek Harness 会话预算插件:按官方价目表实时算账,预算耗尽时拦截任务并落盘交接快照。

Source
ZiOstudio
stars
1 stars
License
MIT
Updated
Updated 6 days ago

Readme

# dsh-budget-handoff

> 给 DeepSeek Harness 的会话设个预算上限:**花完就停,并留下一份能接着干活的交接单。**

[![Stars](https://img.shields.io/github/stars/ZiOstudio/dsh-budget-handoff?style=social)](https://github.com/ZiOstudio/dsh-budget-handoff/stargazers)
[![Forks](https://img.shields.io/github/forks/ZiOstudio/dsh-budget-handoff?style=social)](https://github.com/ZiOstudio/dsh-budget-handoff/network/members)
[![License](https://img.shields.io/github/license/ZiOstudio/dsh-budget-handoff?style=flat-square)](LICENSE)

## 用法

**装**(一条命令,装完**重启 DSH**):

```sh
# 推荐:装 npm 上的预构建包(免构建脚本,也不需要 allowBuilds 批准)
dsh plugin --profile <你的profile> add dsh-budget-handoff

# 或者:直接装源码仓库
dsh plugin --profile <你的profile> add github:ZiOstudio/dsh-budget-handoff
```

> **想固定在某个版本**(不随主分支漂移,便于复现同样的行为),在末尾加版本号:
>
> ```sh
> dsh plugin --profile <你的profile> add dsh-budget-handoff@0.2.1
> dsh plugin --profile <你的profile> add github:ZiOstudio/dsh-budget-handoff#v0.2.1
> ```
>
> 源码安装会从 npm 拉开发依赖来跑构建;若被 pnpm 拦下,按提示把该包加进 profile 的 `allowBuilds`,或直接改用上面的 npm 包。

**然后在会话里直接发这三条**:

| 你发 | 作用 |
|---|---|
| `/budget 5` | 本会话预算设为 5 元 |
| `/budget` | 查还剩多少 |
| `/budget 20` | 不够了,提到 20 元(**要填比已花掉的钱更大的数**) |

发出去会立刻回你一句:

```
[预算] 本会话预算已设为 5.0000 元(已用 0.0000 元)
```

**四条要记住的:**

- **大小写随便混** —— `/Budget 5`、`/BUDGET 5` 都认
- **空格随便打** —— `/budget    5` 也行;数字可以是小数,比如 `/budget 0.5`
- **不花 Token** —— 这三条不经过模型,改预算本身零成本
- **每个会话各自独立** —— A 会话设 5 元、B 会话设 20 元,互不影响

**你不设的话,每个会话默认给 10 元预算。**(想改掉这个默认值,见下面的「配置」)

写错了它也会说话 —— `/budget abc` 会明确告诉你格式不对并给出用法,不会装没听见。

> 装的过程可能看到若干条 `missing peer` 告警 —— 那些 peer 由宿主提供,属结构性噪音,不影响使用。
> 本包自身的 peer 已声明为 `optional`,剩下的告警都来自宿主侧的其它包,与本插件无关。

---

## 它会怎么管你

### 花到 80% —— 提醒一句,不拦你

```
[预算] 已用 4.0234 元,达到本会话预算 5.0000 元的 80%
剩余 0.9766 元。钱花完时任务会被停下,并写一份交接快照。
要提高预算:`/budget <金额>`
```

任务照常往下跑,一个会话只提醒一次。

> 如果某一轮把消费**从 80% 以下直接顶到超过 100%**,这条提醒就没有机会单独出现 ——
> 那种情况下,下面那条拦停通知里会多一行说明。

### 花到 100% —— 任务当场停住

**那一步不会再执行**,所以界面上看起来就是「突然不出字了」。这是正常的。

同时会出现一条通知:

```
[预算] 预算已耗尽,任务已停止
注意:本次消费越过了 80% 预警线,但直到耗尽才停下。
累计消费:5.0234 元 / 预算:5.0000 元
交接快照已写入:
  1. <你的工作目录>\BUDGET-STOPPED-handoff-snapshot.md
  2. <$DSH_HOME>\storages\dsh-budget-handoff\last-stop.md
打开快照可以看到「干到哪了 / 动过哪些文件 / 怎么接着干」。
```

### 停住之后 —— 打开那份交接单

在**你自己的工作目录**里找 `BUDGET-STOPPED-handoff-snapshot.md`。它长这样:

```markdown
# DSH 预算交接快照

- 生成时间:2026-10-08 04:12:07.518 (UTC+8)
- 会话 ID:session-…
- 触发位置:turn=8, step=2
- 累计消费:5.0234 元 / 预算 5.0000 元

## 这个任务要做什么
<你在会话里说的第一句话>

## 干到哪了
- 工作目录:D:\…
- 模型已回复:12 次
- 最近动作(旧的在上,最新的在下):
  - pwsh — List files in current directory
  - edit — snapshot.test.ts

## 动过哪些文件
- `E:\repo\src\snapshot.ts`

## 怎么接着干
1. 先看上面的「动过哪些文件」,逐个确认改动是否完整。
2. 把本会话预算调大。
3. 说一句「继续」——本快照已同时注入模型上下文,模型能看到断点。

## 详细事件(最近 10 条,供排查)
```

**它是写给「第二天醒来的你」的,不是给排查用的日志。** 取不到的字段会写成「本次没有观察到文件改动」
这类明确说明,**不会出现 `null` 字样**。

### 想接着干 —— 提高上限,说「继续」

**规则只有一条:新上限要大于已经花掉的钱。**

```
/budget 10
```

```
[预算] 本会话预算已设为 10.0000 元(已用 5.0234 元)
```

然后说一句:

```
继续
```

**交接单已经同时交给了模型**,所以它知道上次干到哪,不用从头再来。

> ⚠️ 上面那个例子能成立,是因为原来设的是 5 元、花掉了 5.0234 元 —— **提到 10 元就够接着跑**。
>
> 如果你是用**默认的 10 元**跑光的,那 `/budget 10` 等于没提,得填一个**比 10 大**的数。
>
> **不确定花了多少?先发一个 `/budget` 看一眼。**

### `/budget` 的完整语法

| 你发 | 结果 |
|---|---|
| `/budget` | 查询当前预算与已用 |
| `/budget 5` | 本会话预算设为 5 元 |
| `/budget 0.5` / `/budget 12.75` | 小数可以 |
| `/budget    5` | 中间多打空格无所谓 |
| `/Budget 5` / `/BUDGET 5` | 大小写随便混 |
| `/budget abc` | 明确告诉你格式不对,并给出用法 |
| `/budget 0` / `/budget -1` | 拒绝(低于下限 `0.0001`) |
| `/budgetx 5`、`预算 5` | **不是命令** —— 前缀必须独立,或后面跟一个空格 |

### 账户余额耗尽怎么办

如果账户余额**先于会话预算**耗尽,DeepSeek 会拒绝请求。插件认得出这个信号,并走**同一套**收尾:

```
[预算] 账户余额已耗尽,任务已停止
```

不需要任何配置,也不需要插件去查询余额。**这一步零成本**:不用凭据、不发网络请求、不调模型。

---

## 为什么需要它

让 Agent 长时间干活,最后往往是两种结局:

1. **预算失控** —— 你不知道这个会话已经烧了多少钱,等发现时额度已经用完。
2. **钱照付,活没了** —— 任务被硬中断,上下文里没留下任何「干到哪了」的记录。接手的人
   (或下一次会话)只能从头再来,**于是又烧一遍钱**。

这个插件处理的是第二种:它不只是在钱花完时踩刹车,**更是在踩刹车的那一刻,把进度抢救成一份可继承的资产**。

> 「超预算了给我发个邮件没什么用,我睡了,它还在跑。」—— 事后通知在结构上是无效的,
> 因为通知到达时损失已经发生。所以这里不留通知,**留交接单**。

---

## 配置(一般不用动)

默认预算是 **10 元**。要改全局默认值,在 profile 的 `cordis.patch.yml` 里给插件加 `config:`:

```yaml
- insert:
    - id: dsh-budget-handoff
      name: dsh-budget-handoff
      config:
        budgetCNY: 20.0
```

| 字段 | 类型 | 默认 | 说明 |
|---|---|---|---|
| `budgetCNY` | number | `10.0` | 每会话预算,单位人民币元;下限 `0.0001`。**单个会话的临时覆盖请用 `/budget`,不要改这里。** |
| `verbose` | boolean | `false` | 打开后把逐笔用量与计费打进 stdout。默认关闭 —— 一次普通任务**一行都不打**,只留启动横幅、拦截提示和卸载证明 |

也可以完全不给 `config:` —— schema 会回填默认值。

---

## 价格表

`src/pricing.json`,结构 `provider → model → { cacheRead, cacheMiss, output }`,
每个桶含 `offPeak` / `peak` 两档。**单位:人民币元 / 百万 token。**

覆盖范围(只覆盖 DeepSeek 官方):

| provider | model | 备注 |
|---|---|---|
| `deepseek-official` | `deepseek-flash` | 在售主力 |
| `deepseek-official` | `deepseek-v4-pro` | 在售 |
| `deepseek-official` | `deepseek-v4-flash` | 旧名别名,按 Flash 价计费 |
| `deepseek-official` | `deepseek-v4-flash-vision-exp` | 旧名别名,按 Flash 价计费 |

**空闲时段的价格是高峰时段的一半。**

高峰时段=北京时间(UTC+8)周一至周五 `09:00–12:00`、`14:00–18:00`(半开区间,
12:00 与 18:00 整点算空闲);其余时间(含周末)为空闲。

> ⚠️ **与官方定义有一处偏差,说清楚**:官方规定高峰时段为「周一至周五**不含中国法定节假日**」,
> 也就是说**法定节假日全天算空闲**。**本插件没有建模节假日** —— 节假日的白天会被按高峰价
> (2 倍)计费,于是**消费被高估、预算提前用完**。
>
> 偏差方向是保守的(不会超支),但节假日跑长任务会被提前拦停。需要精确时,改
> `src/pricing.ts` 的 `isPeakHour()`,把节假日日期一并纳入判断。

表里查不到的 provider/model 不会按 0 虚报,而是**明确告诉你保护失效了**:

```
[预算] ⚠️ 预算保护已失效
原因:价目表里没有 <provider>/<model> 的价格,本插件无法为它计费
后果:本会话的消费不再累计,也不会在超支时拦停
怎么修:改用价目表里已有的模型,或把该模型的价格补进 src/pricing.json
```

(同一会话里同一个模型只提示一次。)

---

## 已知限制

1. **单步会话不会触发拦截**:闸门在「下一步开始前」,如果会话只有一步,那一步执行完就没有「下一步」可拦。
2. **拦截后 CLI 退出码为 1**:任务被拒绝,CLI 以非零码结束。
3. **只覆盖 `deepseek-official`**:其他 provider 走「价格未知 → 保护失效 → 明确告知」。
4. **跨进程不累计**:累计消费与 `/budget` 设的预算都是内存值,进程重启即归零,也不跨会话共享。
5. **法定节假日未建模**:节假日仍按工作日窗口判高峰(见上文警告)。
6. **价格不会自动更新**:官方调价需要手动改 `src/pricing.json` 并重新构建。

---

## 开发

```sh
pnpm install
pnpm run build              # tsc → dist/

node dist/command.test.js   # /budget 口令解析与回执
node dist/harvest.test.js   # 交接痕迹采集
node dist/ledger.test.js    # 账本累加
node dist/pricing.test.js   # 价格/时段
node dist/snapshot.test.js  # 快照渲染与写盘
```

自测不依赖任何测试框架:编译后直接 `node` 运行,读 `PASS` / `FAIL` 行。

### 内部时钟(给改代码的人看)

```
session/event       →  只记录:记账、攒任务痕迹、排队待发通知
                       ⚠️ 不要在这里 session.append():实测发不出去
agent/pre-step      →  口令处理 / 拦停 / 80% 预警
agent/turn-stopping →  80% 预警兜底(单步会话或停在最后一步时)
agent/request-error →  余额耗尽兜底
```

---

## 变更日志

每个版本改了什么、为什么改,见 [CHANGELOG.md](CHANGELOG.md)。

## Star

如果这个插件帮你省下过真金白银——尤其是那种「一觉醒来,发现钱烧完了、活也停了」的场景——点个 Star,让更多正在踩这个坑的人看到它。

它不需要你花钱,也不需要你注册账号,就是让这个仓库在搜索和推荐里更容易被找到。

## License

MIT © 2026 ZiOstudio

Install

dsh plugin --profile web add github:ZiOstudio/dsh-budget-handoff#87aea6d50ddf817427d3b0f11e717c90a29b1ee3

Profile: web

  • This package builds from source on install. pnpm will ask you to allow its build script — that is permission to run the package’s code on your machine, outside the agent sandbox. Only allow sources you trust.
Source