Skip to content

fix(bridge): 无回复 sentinel 改为末行终止符匹配 - #656

Merged
deepcoldy merged 1 commit into
masterfrom
fix/no-reply-last-line
Jul 29, 2026
Merged

fix(bridge): 无回复 sentinel 改为末行终止符匹配#656
deepcoldy merged 1 commit into
masterfrom
fix/no-reply-last-line

Conversation

@deepcoldy

@deepcoldy deepcoldy commented Jul 29, 2026

Copy link
Copy Markdown
Owner

背景

#554 引入无回复回合静默协议:模型判断本轮无需回复时,final 只输出 BOTMUX_NO_REPLY,worker 侧 gate 静默吞掉兜底转发。合并并上线后,申晗在生产复现漏网 case(截图见群):

模型的 final 实际是

Codex acknowledged and is reviewing. Nothing for me to do — no reply needed.

BOTMUX_NO_REPLY

先解释一句「无需回复」,再把 token 另起一行附在结尾。而原 gate 是 final.trim() === "BOTMUX_NO_REPLY" 精确全等,要求整条 final 只有这一个 token → 不命中 → 整条(含字面量 token)被兜底原样转发到飞书,泄漏 sentinel

根因:精确全等对真实模型行为太脆。botmux-aware 模型极难做到「只输出一个词」,几乎总要先解释再补 token。

改动

isBridgeNoReplyFinal 从「整条精确全等」改为「final 的最后一个非空行精确等于 BOTMUX_NO_REPLY」:

final 形态 行为
BOTMUX_NO_REPLY 静默吞掉(不变)
<解释>\n\nBOTMUX_NO_REPLY(结尾独立行) 整条 turn 静默吞掉(新)
token 出现在句中(… BOTMUX_NO_REPLY now.) 照常转发
token 之后还有正文 照常转发

取舍(已与仓库 owner 申晗确认,选激进方案 B)

命中即整条 turn 丢弃,彻底静音无回复回合。风险=真答复若恰好以独立 sentinel 行结尾,会被整条丢掉且用户看不到;产品明确选择接受此风险以换取完全静音(而非更保守的「剥掉 token 行、转发剩余正文」方案 A)。用「末行匹配」而非 substring/endsWith,把误吞限定在模型主动以 sentinel 收尾的 final。

提示词(i18n)保持不变——仍要求模型「final 只输出该 token」,gate 改动纯粹是模型不听话时的防御。

影响范围

  • 只改共享 bridge-fallback-gate.tsisBridgeNoReplyFinal,波及所有走 transcript 兜底的非 adopt 会话,共用同一 gate(shouldSuppressBridgeEmit)的自动回投路径有三条:Claude 家族 emitReadyTurns、结构化 bridge emitReadyCodexTurns、以及 Codex App/Mira/Mir 的 handleCodexAppMarker(新旧两个协议分支)。多行 final 的组装保留换行,与末行匹配假设一致。
  • fix(bridge): 修复 Traex 空完成漏发兜底 #553 空完成兜底组合正确:sentinel 终止的 final 非空,shouldEmitEmptyCompletedBridgeFallback 返回 false,不会触发「已完成但无文本」诊断卡。
  • adopt 模式不解释 sentinel(不变)。

测试

  • test/bridge-fallback-gate.test.ts 新增/改写:末行终止符 suppress、prose+空行+token 行 suppress、句中 token 照发、token 后接正文照发。
  • 目标 6 文件(bridge-fallback-gate / cli-adapters / prompt-builder / bridge-final-output-retry / codex-bridge-queue / claude-turn-terminal-contract):480 passed
  • 额外对抗 11 条(CRLF、尾部标点、大小写、trailing 空行、undefined/空、fix(bridge): 修复 Traex 空完成漏发兜底 #553 组合、adopt):全过。
  • pnpm build:通过。

由 origin/master 3c0d64de 切出,单文件逻辑改动 + 测试。

#554 的 gate 用 `final.trim() === BOTMUX_NO_REPLY` 精确全等,要求整条
final 只有这一个 token。实测模型几乎总是先解释一句"无需回复"再把 token
另起一行附在结尾(prose + 空行 + token),不命中全等→整条含字面量 token
被兜底原样转发到飞书,泄漏 sentinel。

改为判「final 的最后一个非空行」是否精确等于 BOTMUX_NO_REPLY:
- 纯 token / prose+空行+token(结尾独立行)→ 命中,整条 turn 静默吞掉
- token 出现在句中、或 token 之后还有正文 → 不命中,照常转发

取舍(已确认):命中即整条丢弃(方案 B,激进),彻底静音无回复回合;
风险=真答复若恰好以独立 sentinel 行结尾会被整条丢掉,产品选择接受此
风险以换取完全静音。用「末行」而非 substring/endsWith,把误吞限定在
模型主动以 sentinel 收尾的 final。

- 与 #553 空完成兜底组合:sentinel 终止的 final 非空,不触发空完成诊断
- adopt 模式不解释 sentinel(不变)
- 测试:新增末行终止/句中 token/token 后接正文/CRLF/尾部标点等用例;
  目标 6 文件 480 + 组合对抗 11 全过;pnpm build 通过

Co-Authored-By: Riff <noreply@riff.dev>
@deepcoldy
deepcoldy merged commit 6349152 into master Jul 29, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant