workbuddy-context-handoff · git:20260916.b57377e · 2026-09-16 · sha256 c03e572c9f4672be
workbuddy-context-handoff git:20260916.b57377eA
Immutable. This exact content never changes and is served at /api/v1/blob/c03e572c9f4672be.
---
name: workbuddy-context-handoff
description: 给 WorkBuddy(桌面端 CodeBuddy Code 运行时)配置基于 hooks 的上下文 handoff 门禁 —— 上下文占用达阈值时自动强制产出 handoff 文档,新会话自动注入继续干活。也用于排查 WorkBuddy hooks / 上下文压缩阈值 / transcript 用量相关问题。
agent_created: true
---
# WorkBuddy 上下文 handoff 门禁
## 何时用
- 用户要"上下文到 N% 自动生成 handoff / 交接文档 / 新会话续接"
- 用户问 WorkBuddy hooks 怎么配、为什么自动压缩了、上下文用量怎么算
- 需要定位 WorkBuddy 桌面端的运行时行为(阈值、config option、hook 事件)
## 关键事实(已实测,别再猜)
1. **运行时**:桌面端 agent 跑的是 app.asar 里的 `cli/dist/codebuddy.js`(CodeBuddy Code)。
查行为:解包 asar 后 grep 这个文件。
2. **hooks 位置**:settings 的 `hooks` 键。
- 用户级 `~/.workbuddy/settings.json`(PRODUCT_LOWERCASE_NAME=workbuddy)
- 项目级 `<proj>/.workbuddy/settings.json`
- hooks 跨 scope 合并,同事件并行执行
3. **已实现的 hook 事件**:PreToolUse / PostToolUse / PostToolUseFailure / Notification /
UserPromptSubmit / SessionStart / SessionEnd / Stop / SubagentStart / SubagentStop /
PreCompact / PermissionRequest / WorktreeCreate / WorktreeRemove
4. **matcher 是正则**:`new RegExp(matcher).test(matchValue)`。matchValue 按事件:
SessionStart→`source`(startup/resume/clear/compact)、PreCompact→`trigger`(manual/auto)、
SessionEnd→`reason`、PreToolUse/PostToolUse→tool_name。
5. **hook 入参**没有 token 用量:`{session_id, transcript_path, cwd, permission_mode, hook_event_name, ...}`
6. **transcript**:`~/.workbuddy/projects/<cwd-slug>/<session-id>.jsonl`。
逐行 JSON,取最后一次 `message.usage.input_tokens`(+`output_tokens`)即上下文体积。
`cache_read_input_tokens` 是子集,**不要**再加一遍。
7. **内置压缩阈值**(product.json `tokenUsageThresholds.inputTokens`):
preMessage 0.5 / warning 0.6 / critical 0.7 / emergency 0.9。
→ **约 50% 就 pre-message 自动压缩**,原生到不了 80%。
8. **pre-message 压缩不受 `autoCompactEnabled` 控制**,也不读 settings;它读
`session.meta.preMessageCompactPct`(ACP `session/set_config_option`)。
桌面端 **Max Mode** 开关把它设成 `"0"` 关掉压缩,关掉开关设回 `""`(默认 0.5)。
环境变量 `CODEBUDDY_PRE_MESSAGE_COMPACT_PCT` 也能覆盖(值 1-100)。
9. **PreCompact hook 不能做 handoff 触发器**:stdout 只进压缩指令
(`hookContexts.preCompactInstructions`);exit 2 只给用户看消息。
10. **能强制模型继续的只有 Stop hook**:`{"continue": false, "reason": "..."}`。
**能注入新会话上下文的**:SessionStart hook 的 stdout 或
`hookSpecificOutput.additionalContext`。
## 落地方案
三个文件放 `~/.workbuddy/hooks/`,然后在 `~/.workbuddy/settings.json` 注册:
```json
"hooks": {
"Stop": [
{ "hooks": [ { "type": "command", "command": "<abs>/run-hook.sh <abs>/handoff-trigger.mjs", "timeout": 15 } ] }
],
"SessionStart": [
{ "matcher": "startup|clear",
"hooks": [ { "type": "command", "command": "<abs>/run-hook.sh <abs>/handoff-resume.mjs", "timeout": 15 } ] }
]
}
```
- `handoff-trigger.mjs`(Stop):算 `input_tokens / window`,>= threshold 就输出
`continue:false` + 写 handoff 的指令。必须做两件防死循环:
`stop_hook_active===true` 直接 return;每 session 只触发一次(`state/<sid>.fired`)。
- `handoff-resume.mjs`(SessionStart):注入本项目最新未消费的 handoff,
注入后写 `<file>.consumed` 保证只注入一次。`source==="compact"` 时跳过。
- `run-hook.sh`:解析 node 绝对路径(managed → homebrew → PATH),
找不到也 exit 0。**所有 hook 脚本异常都要 exit 0**,否则会卡住对话。
- handoff 目录按 `sha1(cwd).slice(0,12)` 分项目,别依赖 app 的 cwd-slug
(不同版本大小写规则不一致)。
阈值可通过 `handoff.config.json` 调;`contextWindow` 留 null 时按
`settings.autoCompactWindow` → `CODEBUDDY_AUTO_COMPACT_WINDOW` → 200000 兜底。
## 排错
- **hook 不触发**:确认 `~/.workbuddy/settings.json` JSON 合法、命令用绝对路径、
脚本可执行。手动复现:`echo '<payload json>' | /bin/bash -lc '<command>'`。
- **到不了阈值**:几乎一定是第 8 条 —— 50% 就被压缩了。开 Max Mode,或设
`CODEBUDDY_PRE_MESSAGE_COMPACT_PCT=80`,或降低 handoff 阈值。
- **grep app.asar 搜不到字符串**:284MB 大文件直接 grep 会漏。用
`npx @electron/asar extract <asar> <dir>`(3-4s,~800MB)再 grep。
## 交付规范
- 改 `~/.workbuddy/settings.json` 前先备份,改完用 `JSON.parse` 校验且确认其余键未丢。
- 脚本内不写 Unicode 制表/装饰字符(乱码)。中文可正常写。
- 测完清理 `hooks/state/` 与 `handoffs/` 下的测试产物。