WorkBuddy 上下文 handoff 门禁
Purpose
在 WorkBuddy 桌面端搭一条"上下文快满 -> 强制产出 handoff 文档 -> 新会话自动续接"的流水线。
- 触发端:Stop hook,按 transcript 里的 token 用量判断是否到阈值,到了就用
{"continue": false, "reason": "..."}强制模型先写 handoff。 - 注入端:SessionStart hook,把本项目最新未被消费的 handoff 注入新会话上下文。
- 附带用途:解释/排查 WorkBuddy 的自动压缩阈值、hooks 事件与 transcript 用量计算。
When to Use
- 用户要"上下文到 N% 自动生成 handoff / 交接文档 / 新会话续接"。
- 用户问 WorkBuddy hooks 怎么配、为什么自动压缩了、上下文用量怎么算。
- 需要定位 WorkBuddy 桌面端的运行时行为(阈值、config option、hook 事件)。
- 触发词:上下文交接、handoff、上下文压缩、自动压缩、hooks 配置、新会话续接、
context handoff、auto compact。
When NOT to Use
- 用户只是想知道"我现在用了多少上下文" -> 直接按 transcript 算一次回答,不必配 hook。
- 目标运行时不是 WorkBuddy 桌面端(路径、事件名、环境变量都不同)-> 不适用,先确认运行时。
- 用户要的是一份手工交接文档 -> 直接写,不需要这套门禁。
Workflow
- 确认位置与备份:定位 settings(用户级
~/.workbuddy/settings.json或项目级
<proj>/.workbuddy/settings.json),先备份,再读现有hooks键。 - 写三个脚本到
~/.workbuddy/hooks/:handoff-trigger.mjs(Stop)、
handoff-resume.mjs(SessionStart)、run-hook.sh。各文件职责与防死循环要求见
references/hook-implementation.md。 - 注册 hooks(合并进 settings 的
hooks键,不要整体覆盖):
"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 } ] }
]
}
- 校验 JSON:用
JSON.parse解析整个 settings 文件,确认其余键未丢。 - 实测触发:手动复现单个 hook ——
echo '<payload json>' | /bin/bash -lc '<command>',
再跑一轮真实会话确认 handoff 产出与注入都发生。 - 清理测试产物:删掉
hooks/state/与handoffs/下的测试文件。
Decision Rules
- 触发点只能选 Stop:它是唯一能强制模型继续的事件;注入点只能选 SessionStart:它的 stdout
或hookSpecificOutput.additionalContext是唯一能进新会话上下文的位置。PreCompact 不能当
handoff 触发器(stdout 只进压缩指令,exit 2 只给用户看消息)。 - 阈值调整优先级:先看是不是被内置 50% pre-message 压缩截断 —— 开桌面端 Max Mode,
或设CODEBUDDY_PRE_MESSAGE_COMPACT_PCT=80;确认能到目标比例后,再调
handoff.config.json里的 handoff 阈值。 - contextWindow 兜底顺序:
handoff.config.json.contextWindow留 null 时依次取
settings.autoCompactWindow->CODEBUDDY_AUTO_COMPACT_WINDOW->200000。 - 需要确认事件名 / matcher 语义 / hook 入参 / 阈值常量 -> 读
references/runtime-facts.md。 - 要写或改 hook 脚本本身 -> 读
references/hook-implementation.md。 - hook 不触发 / 到不了阈值 / 搜不到字符串 -> 见
## Troubleshooting。
Constraints
- 改
~/.workbuddy/settings.json前先备份;改完必须JSON.parse校验,并确认其余键未丢。 - 所有 hook 脚本的异常分支都必须
exit 0,否则会卡住对话。 - 每 session 只触发一次:Stop 脚本要检查
stop_hook_active===true直接 return,并用
state/<sid>.fired去重。 - handoff 目录按
sha1(cwd).slice(0,12)分项目,不要依赖 app 的 cwd-slug(不同版本大小写规则不一致)。 - 脚本内不写 Unicode 制表/装饰字符(会乱码);中文可正常写。
- 测完清理
hooks/state/与handoffs/下的测试产物。 - 不要用 PreCompact 做 handoff 触发;不要把 hook 的 stdout 当成能进用户可见消息的通道。
Priority
- 不卡住对话(hook 任何异常一律
exit 0)> "一定要触发 handoff"。 - 先解除原生 50% 压缩,再调 handoff 阈值 —— 否则阈值永远达不到。
- 防死循环(
stop_hook_active检查 + 每 session 一次)> 尽量多触发。
Stop Conditions
- handoff 到不了阈值 -> 不要反复下调阈值;先确认是否被 50% 原生压缩截断、或用户没开 Max Mode。
grep app.asar搜不到目标字符串 -> 不要反复 grep;改用
npx @electron/asar extract <asar> <dir>后再 grep。- 改完 settings 后
JSON.parse失败 -> 立即还原备份,不要继续追加改动。
Verification
交付前逐项确认:
- [ ]
~/.workbuddy/settings.json已备份,且JSON.parse通过、其余键未丢。 - [ ] 三个脚本就位且可执行;每个异常分支都
exit 0。 - [ ] Stop 注册带
stop_hook_active与state/<sid>.fired双重防死循环。 - [ ] SessionStart 注册的 matcher 为
startup|clear,且source==="compact"时跳过。 - [ ] 实测确认:达标时产出 handoff,新会话读到且只读一次。
- [ ]
hooks/state/与handoffs/下的测试产物已清理。
Output
- Outcome:一条可用的 handoff 门禁 —— 上下文达阈值时自动产出 handoff 文档,
新会话(startup / clear)自动注入该文档继续干活。 - Done when:
## Verification全部勾选,且用户能在真实会话里看到 handoff 产出与注入。 - Evidence:settings 中新增的
hooks段;handoffs/下带<file>.consumed标记的文件;
state/<sid>.fired标记;一轮实测会话的前后对比。
References
按需加载,不要预先全读:
references/runtime-facts.md— 需要确认 hook 事件名、matcher 语义、hook 入参、transcript 路径
或内置压缩阈值常量时读。references/hook-implementation.md— 要写或修改handoff-trigger.mjs/handoff-resume.mjs/
run-hook.sh时读。
Troubleshooting
- hook 不触发:确认
~/.workbuddy/settings.jsonJSON 合法、命令用绝对路径、脚本可执行。
手动复现:echo '<payload json>' | /bin/bash -lc '<command>'。 - 到不了阈值:几乎一定是被内置 50% pre-message 压缩截断 —— 开 Max Mode,或设
CODEBUDDY_PRE_MESSAGE_COMPACT_PCT=80,或降低 handoff 阈值。 - grep app.asar 搜不到字符串:284MB 大文件直接 grep 会漏。用
npx @electron/asar extract <asar> <dir>(3-4s,约 800MB)解包后再 grep。