/btw —— 运行中插一句话
主 agent 正在跑的时候,问一句、或者补一条信息,都不打断它。
/btw 为什么这一步这么慢
/btw 记住 MEMORY.md 已经改过,别再改
两种行为,由旁注助手自己判(不需要 ? 前缀)
| 用户说的话 | 判定 | 实际发生什么 |
|---|---|---|
| 提问题(进度 / 原因 / 某个数值) | question |
起一次独立的模型请求,回答作为命令结果返回 |
| 补充事实 / 决定 / 约束 / 偏好 | info |
整理成一条记忆条目,agent.inject() 投到 next-step |
提问为什么是"独立请求"而不是 steer
steer 的消息是用户指令,模型会把它当成新的主任务:常见后果是丢掉手上的活,或者把已完成的一步重做。旁注要的恰恰是"只回答、不改变"。
所以提问走这条路:
session.deriveMessages()拿到当前会话真正送给模型的那份历史(压缩、裁剪、投影都算好了);- 原 system prompt 保留(旁注要知道这个会话在干什么),末尾追加一段"你现在是旁注助手,只回答、不动手";
ctx.llm.stream()发一次请求,模型路由取session.requestHeader().config—— 和主线同一个模型;- 回答作为命令结果返回。
command/run/command/done是 log-only 事件,不进模型上下文,所以主 agent 的上下文一个字都没多。
补充信息为什么走 inject
agent.inject() 的语义正好对上:
Queue model-facing context for the next pre-step without waking the driver. A running driver claims it at the nearest later step boundary.
也就是:进下一步的 pre-step 收件箱、不唤醒正在跑的 driver、下一个模型请求里就能读到。注入的消息带 [补充信息 · 用户插话] 前缀和"不要把它当成新任务"的说明——它是 user 角色,不加这句模型很可能当成新指令。
判错了会怎样
- 解析不出 JSON ⇒ 一律退化成"回答原文"(当提问),绝不会把没解析出来的内容注入记忆。
info但memory为空 ⇒ 同样退化。- 往记忆里塞脏数据比分错类危险得多,所以这里刻意不对称。
配置
行里的 config 全部可省略:
| 键 | 默认 | 说明 |
|---|---|---|
enabled |
true |
总开关 |
historyMessages |
40 |
喂给旁注助手的历史条数上限 |
maxHistoryChars |
120000 |
历史字符上限(大会话兜底) |
maxAnswerChars |
4000 |
回答最大字符数 |
timeoutMs |
120000 |
单次旁注请求超时 |
界面:两个入口,同一份数据
| 入口 | 行为 |
|---|---|
输入框工具行的气泡按钮(+ 号右侧) |
浮层里提问,回车发送 |
输入框直接敲 /btw … |
同一个命令节点 ⇒ 浮层自动弹出并显示答案 |
两条路都产生 command/run + command/done,浮层渲染的永远是最新一条 btw 命令节点
(useChat 读 snapshot.nodes.values(),该 API 明确包含被折叠隐藏的节点)。所以
Agent 正在跑、对话流把行折进「运行过程」时,浮层照样能显示——这是它存在的理由。
对话流里另有一张同源卡片(conversation.chat.commandview 的 btw 键):空闲时按
时间线显示,位置更自然;运行中会被折叠,需要时点开「运行过程」也能看到。
浮层怎么跟随窗格
浮层不再按整个视口写死 left:24; bottom:132。打开时按触发按钮、composer 和
所在窗格的实时矩形计算:
- 底边贴在 composer 上方 8px,不压住输入框;
- 宽度不超过当前窗格,平铺成窄格时不会横跨到隔壁;
- 高度限制在 composer 上方的可用空间,放不下时浮层内部滚动;
- 通过 portal 挂到
document.body,避免被 composer 的层叠上下文或overflow裁掉;resize/scroll/ResizeObserver会跟着窗格尺寸变化重新定位。
输入框为什么能立刻清空
composer 是在 commands.execute 结算之后才清空输入框的。所以命令被拆成两段:
btwhandler 立刻返回占位文本 → 命令结算 → 输入框清空、节点出现;- 后台跑完模型调用,用同一条
commandId再追加一条command/done。客户端按 commandId 折叠生命周期,后到的 done 覆盖前一个的 outcome(dsh-client-ui-chat的commandFromDone(match, previous)就是这么写的),于是同一张卡片/浮层原地刷新 成答案,不新增行、不新增事件类型。
已知边界
- 旁注助手看的是模型可见历史。主 agent 正在等一个 20 分钟的构建时,"还在跑"这件事不在历史里,旁注只能从工具调用和结果推断。要更准得让它读会话日志尾部或
job表(P2)。 - 历史从最近一条
user消息切开:OpenAI 兼容协议要求 tool 结果对得上前面的调用,从中间切会被网关拒。 - 回答是一次性返回的,不是流式(P2)。
测试
node --test test/*.test.mjs
25 条用例,重点覆盖三个不变量:提问不注入、补充信息才注入、解析失败绝不注入; 另有 5 条浮层几何用例(主对话、窄窗格、极窄窗格、按钮未测量、无 composer 兜底)。
No comments yet. Be the first to write one.