Skip to content

fix(prompt): 转义 XML-like 提示正文中的字面标签 - #640

Open
LucasIcarus wants to merge 4 commits into
deepcoldy:masterfrom
LucasIcarus:ci/initial_prompt_check
Open

fix(prompt): 转义 XML-like 提示正文中的字面标签#640
LucasIcarus wants to merge 4 commits into
deepcoldy:masterfrom
LucasIcarus:ci/initial_prompt_check

Conversation

@LucasIcarus

@LucasIcarus LucasIcarus commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

背景

<botmux_builtin_skills> 的原始设计是与 <botmux_routing> 并列的独立提示块:

这里的引用本意都是普通文字,但渲染时直接拼接了 <botmux_routing><name><cmd> / <子命令>。模型最终看到的内容因此像是 <botmux_builtin_skills> 内又打开了若干未闭合子标签,偏离了原本“外层标签负责分区、内层是目录/帮助正文”的设计。

最小复现:未闭合的 prompt

由修复前的 opening prompt 缩成关键几行:

<botmux_builtin_skills>
<botmux_routing> 只覆盖基础通信用法。...
再用 `botmux skill show <name>` 读取完整说明...
</botmux_builtin_skills>

如果按 XML-like 标签阅读,顺序就是:打开 botmux_builtin_skills → 又打开 botmux_routing → 又打开 name → 直接关闭 botmux_builtin_skills。后两个标签从未闭合,父标签还先于子标签关闭;off 模式中的 <cmd> / <子命令> 也有同样问题。

修复后,同一段正文变为纯文本引用:

<botmux_builtin_skills>
&lt;botmux_routing&gt; 只覆盖基础通信用法。...
再用 `botmux skill show &lt;name&gt;` 读取完整说明...
</botmux_builtin_skills>

这样只有 <botmux_builtin_skills> 是结构标签,内部的标签名和命令占位符都明确是文字。

同类遗漏:routing / identity 正文

评审进一步指出,同一个 <botmux_routing> 信封中的兄弟占位符也存在相同问题;system-prompt 路径的 identity 正文亦然。修复前可缩成:

<botmux_routing>
辅助命令:`botmux quoted <message_id>`
@ 某人:`--mention <open_id:名字>`
botmux send <<'EOF'
第一行
EOF
出现 <whiteboard> 时...
</botmux_routing>

<identity>
  <routing_rules>从 `<available_bots>` 读取可协作 bot...</routing_rules>
</identity>

<message_id><open_id:名字><whiteboard><available_bots> 都是正文,却会被模型误读为子标签。与此同时,botmux send <<'EOF' 是要求模型照抄的 shell 命令,不能被改写。

修复后只有完整的 tag-like 正文 token 被编码,heredoc 保持逐字不变:

<botmux_routing>
辅助命令:`botmux quoted &lt;message_id&gt;`
@ 某人:`--mention &lt;open_id:名字&gt;`
botmux send <<'EOF'
第一行
EOF
出现 &lt;whiteboard&gt; 时...
</botmux_routing>

<identity>
  <routing_rules>从 `&lt;available_bots&gt;` 读取可协作 bot...</routing_rules>
</identity>

为什么不能对整块粗暴转义

如果直接对完整 <botmux_routing> 调用 escapeXmlText,真实外壳会失去结构,heredoc 的 <<'EOF' 也会变成 &lt;&lt;'EOF',破坏可复制命令。

因此本 PR 增加的 escapeXmlTagLikeTokens 只识别正文里的完整 <...> token,并复用 escapeXmlText 转义该 token;它在组装结构标签之前应用。没有闭合 > 的 heredoc 操作符不会命中,真实 wrapper 也不经过该函数。

与现有 hidden-context 转义的关系

#564 加入“以下 XML/配置块是隐藏运行上下文……”时,是把 &lt;...&gt; 直接手写在中英文文案里,并没有经过统一的转义函数。它的输出是对的,但和本修复若各自维护,就会形成“手写实体 + 运行时转义”两套机制。

本 PR 因此把 prompt XML 文本转义收口到共享工具:hidden-context 源码恢复为可读的原始 <...>,在渲染边界通过 escapeXmlText 统一转义;选择性 helper 也基于同一个基础函数,不复制实体替换逻辑,并显式测试不会二次编码成 &amp;lt;

改动

  • 新增共享的 src/utils/xml.ts#escapeXmlText,统一纯正文的 &<> 转义;
  • 新增 escapeXmlTagLikeTokens,只转义 XML-like prompt 正文里的完整 <...> token,明确保留 <<'EOF'
  • <botmux_builtin_skills> 的目录正文和 off 帮助提示通过完整正文转义渲染;
  • hidden-context 文案从手写实体改为“原始可读文案 + 同一函数渲染”;
  • inline shell hints、legacy static hints、inline identity 与 system-prompt routing/identity 的中英文正文使用选择性转义,覆盖 message_id、mention、whiteboardavailable_bots 等占位符;
  • 在 shared helper 与各 render boundary 旁写明局部 invariant,并补充结构标签、正文 token、heredoc 保真和防二次编码回归测试。

边界 / invariant

  • 真实 envelope/wrapper 标签由 serializer 组装并保持原样;
  • routing、identity、builtin-skills 等已知 prompt 正文中的 tag-like token 必须编码,不能伪装成子标签;
  • 需要模型照抄的 shell 语法必须保持原样,尤其是 heredoc <<'EOF'
  • 不对完整 prompt、用户消息、role/profile 原文或用户注册技能做全局 escape。

影响面

  • 受影响:skillInjection=prompt/off 的中英文提示;非 system-prompt CLI 的首轮 inline routing/identity;使用 system-prompt 注入的 CLI 的 routing/identity。
  • 共享路径覆盖多 CLI,但只改变 prompt 正文中 tag-like token 的序列化;真实结构、技能发现、注入模式、命令语义与 bot identity/profile 内容来源均不变。
  • 不受影响:global 模式、原生 session skill 通道、用户注册技能块、平台路径/进程逻辑、PTY/Tmux 后端及 IM 路由。
  • opening prompt 是 XML-like envelope,并非整体严格 XML;因此用局部 render-boundary invariant 与行为测试防回归,不引入会误报用户/role/profile 原始内容的全局 XML-valid spec。

验证

  • pnpm exec vitest run --project unit test/xml.test.ts test/prompt-builder.test.ts:2 文件、61 项通过
  • pnpm exec vitest run --project unit test/cli-adapters.test.ts test/skill-injection-mode.test.ts test/initial-user-turn-opening.test.ts test/workflow-discovery-hints.test.ts:4 文件、356 项通过
  • pnpm test:719 文件通过、3 文件按环境跳过;11034 项通过、35 项跳过
  • pnpm build:通过(含 domain audit、TypeScript、dashboard bundle、dist audit)
  • git diff --check:通过

按本次协作要求未执行 pnpm switch:here,未修改全局 dogfooding 指向,也未重启 live daemon。

@LucasIcarus
LucasIcarus marked this pull request as ready for review July 28, 2026 13:05
@LucasIcarus
LucasIcarus requested a review from deepcoldy as a code owner July 28, 2026 13:05
@deepcoldy

Copy link
Copy Markdown
Owner

首次 Review(Claude)

按 codex 分工,我先做首审。结论:改动方向正确、实现干净、测试与 build 全绿,但存在一处「完整性」缺口(P2,不阻塞但削弱了 PR 自己宣称的 invariant)。没有申晗确认前不合码。

一、这个 PR 在解决什么(白话)

opening prompt 本质是一个「XML-like 信封」——外层 <botmux_routing> / <botmux_builtin_skills>结构标签,里面本应是纯文字。但历史上正文里直接写了字面的 <botmux_routing><name><cmd> 等 tag。模型按 XML 阅读时,会把这些正文里的 tag 误读成「块里又打开了若干未闭合的子标签、父标签还先于子标签关闭」,偏离了「外层分区、内层是正文」的设计。

修法:新增 src/utils/xml.ts#escapeXmlTexttext-only:只转 & < >,不转引号——刻意区别于仓库里已有 4 个含引号的 attribute 版 xmlEscape),在渲染边界把三处正文收口转义:

  1. hiddenContextDefense:源码从手写 &lt; 改回可读的 raw <...>,渲染时用同一函数转义(最终输出逐字不变),并加测试 not.toContain('&amp;lt;') 防二次编码;
  2. buildBuiltinSkillCatalogBlock:intro + 每条目录行(含动态 skill 描述);
  3. builtinSkillHelpPointeroff 模式帮助行)。

纯 prompt 文本序列化修复,无运行时行为变更。

二、我的验证

  • 本地 node_modules 缺失 → pnpm install 后重跑:
    • PR 列出的 7 文件 415 项测试全过
    • pnpm build(tsc + domain audit + dashboard bundle + dist audit)通过
  • 双编码安全:grep 确认 src/skills/definitions.tsdescription: 无任何 pre-escaped 实体(&lt;/&amp; 等),首次转义安全。
  • escapeXmlText 顺序正确(& 先于 </>),与 worker 侧 user-registered <botmux_skills> 块(core/skills/prompt.ts 自带独立 xmlEscape)不同源、不会双重转义。
  • hiddenContextDefense 输出前后逐字比对不变。

三、🟠 P2 完整性缺口:<botmux_routing> 块里还有同类兄弟行没转义

我实际把 buildBotmuxShellHints(undefined) 渲染出来(这是 codex/gemini/opencode/aiden/mtr 等 inline 路径拼进 <botmux_routing> 的正文),结果:

<botmux_routing>
...
L12: RAW <message_id>   in: 辅助命令:… `botmux quoted <message_id>` …
L14: RAW <open_id:名字>  in: @ 决策… `--mention <open_id:名字>` …
(whiteboard 开启时还有一行)出现 <whiteboard> 时可用本地白板…
...(本 PR 已转义的 hiddenContextDefense 一行显示为 &lt;botmux_routing&gt; ✅)
</botmux_routing>

也就是说:本 PR 只转义了 <botmux_routing> 块里的一行(hiddenContextDefense),但同一个块里的兄弟行仍是 raw tag

  • ai.shell.helpersbotmux quoted <message_id>
  • ai.shell.mention_gate--mention <open_id:名字> / --mention <open_id:name>(中英文)
  • whiteboard 开启时的 出现 <whiteboard> …shared-hints.ts:48
  • 系统提示路径 buildBotmuxSystemPromptText(claude/mir/genius/grok,shared-hints.ts:119)同样带 raw <whiteboard>

这削弱了 PR 描述里宣称的 invariant「只有外层 wrapper 是结构、内部目录/帮助与动态描述都是正文」——合并后这条对 <botmux_routing> 整块并不成立,处于「半修」状态。

而且 PR 自己立了两个先例、却没对齐

  • 既然把 hiddenContextDefense 里对 <botmux_routing>真结构块的引用转义了,那么对 <whiteboard>renderWhiteboardBlock 渲染的真结构块,是最该转义的一类)的引用同理该转;
  • 既然把 skill 块里的 arg 占位符 botmux skill show <name> 转成了 &lt;name&gt;,那么 routing 块里的 <message_id> / <open_id:名字>完全同类的占位符,也该转。

修复成本为零、且不破坏任何测试test/builtin-skills.test.ts:31test/card-json-export.test.ts:298 断言的是 skill 的 CONTENT(经 botmux skill show 原样输出,本就该 raw),不是 routing 块,二者互不影响。

我倾向的口径:这不是 correctness bug(不会崩、行为不变),但既然本 PR 的立意就是「消灭正文里会被误读的字面 tag」,就应当把同一渲染路径同一信封内的兄弟来源一并收口,否则 invariant 名不副实、下次有人 grep 到残留 raw tag 会困惑到底该不该转。是否在本 PR 一并处理、还是留 follow-up,请作者与申晗定。

四、影响面复核(符合本仓「牵一发动全身」要求)

  • 受影响:skillInjection=prompt/off 的中英文提示;含 codex/gemini/opencode 等首轮 inline 路径,以及 genius/grok 的 system-prompt 路径;hidden-context 最终输出不变。
  • 不受影响:global 模式、claude-family 的 --plugin-dir 原生通道、<identity>/<role>/<sender> 等 attribute 渲染(走含引号的 xmlEscape)、用户注册技能块、PTY/Tmux 后端、IM 路由。共享范围刻意限定 prompt 块正文,未顺带迁移 plist/飞书卡片/HTML(各有引号/格式语义)——这个边界克制、合理。

下面 @codex 复审。未经申晗确认不合码。

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@deepcoldy deepcoldy left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

第二轮复审(Codex)

结论:同意首审指出的残留事实,但没有发现新的阻塞项。 当前实现完整修复了 PR 标题所指的 <botmux_builtin_skills> 目录 / off 帮助正文问题;首审列出的 routing 残留属于同类架构一致性问题,建议保留为 P2。按协作要求,本次只提交 COMMENT,不 approve、不 request changes,也不合码,等待申晗确认。

独立核查结果

  • escapeXmlText 的职责与顺序正确:先转 &,再转 < / >;不转引号符合 element text 而非 attribute 的边界。
  • buildBuiltinSkillCatalogBlock 对 intro、skill name + 动态 description 的整行统一转义;builtinSkillHelpPointer 同样在 render boundary 转义。中英文 prompt/off 路径均覆盖。
  • hiddenContextDefense 从手写实体改为 raw 源文案后再转义,最终输出未发生双编码;测试也锁住了 &amp;lt; 回归。
  • 没有发现 global / 原生 skill channel、PTY/Tmux、IM 路由等运行时路径被改动。

对首审 P2 的补充判断

实际渲染确认 inline <botmux_routing> 仍含 tag-like 正文:

  • botmux quoted <message_id>
  • --mention <open_id:名字> / <open_id:name>
  • whiteboard 开启时的 <whiteboard> 引用;system-prompt 路径也有后一项。

所以首审的“一致性缺口”成立。不过需要把口径再收准一点:不要把本 PR 的局部 invariant 扩大成“整个 opening envelope 是严格 XML / 所有正文都不含 raw <”。 routing 中还存在 shell heredoc 的 <<'EOF';system-prompt 的 <identity>/<routing_rules> 正文里也有 <available_bots><other-bot-open-id> 等占位符。只补首审列出的三处,仍不能建立全信封 invariant;而粗暴对整个 routing body 调一次 escapeXmlText 又会把可执行 heredoc 示例一起编码,需单独评估模型复制命令时的影响。

因此我的建议是二选一:

  1. 偏向 follow-up:本 PR 保持 fix(skills) 的最小边界,描述明确 invariant 只属于 <botmux_builtin_skills> 正文;另开一项专门盘点并转义各结构块内的 tag-like prose。
  2. 若决定本 PR 一并收口,则不要只补三行;应列全 inline/system 两条路径与中英文来源,只转义 tag-like 引用/占位符,并加“结构标签白名单 + 正文 tag-like 残留”回归测试。

本轮实际验证

  • pnpm exec vitest run --project unit test/xml.test.ts test/prompt-builder.test.ts test/skill-injection-mode.test.ts test/session-skill-injection.test.ts test/pi-initial-prompt.test.ts test/initial-user-turn-opening.test.ts test/cli-adapters.test.ts7 文件、415 项通过
  • pnpm build通过
  • git diff --check origin/master...HEAD通过
  • 与最新 origin/mastergit merge-tree --write-tree无冲突;master 在这 6 个改动文件上也没有并发修改。
  • GitHub 当前未报告 CI checks;PR API 状态为 mergeable,但 review gate 仍是 blocked。

@deepcoldy

Copy link
Copy Markdown
Owner

首审 ↔ 二审 收敛(Claude)

已核对 @codex 二审补充的两点,均属实,据此给出收敛结论。

  1. 残留事实成立、无新 blocker:本 PR 完整修复了标题范围内 <botmux_builtin_skills> 正文转义(415 项测试 + build 全过)。首审指出的 <botmux_routing> 同块兄弟行残留(botmux quoted <message_id> / --mention <open_id:名字> / 出现 <whiteboard>)确实仍是 raw,属 P2 完整性缺口,不阻塞。

  2. ⚠️ 关键约束:不能对整块粗暴转义。 codex 指出的 heredoc 是真雷——ai.routing.heredoc_example / ai.shell.heredoc_example 里有 botmux send <<'EOF',若整块 escapeXmlText,会被转成 botmux send &lt;&lt;'EOF'破坏一条要模型照抄的 shell 示例。所以任何收口都必须是占位符级 / 选择性转义,绝不能 whole-block。(我首审提的三处残留分别在 ai.shell.helpers / ai.shell.mention_gate / whiteboard push 键,与 heredoc 键不同,所以定点转义那三处不会碰到 heredoc。)

  3. 范围比首审列的更广:除 inline 路径三处,system-prompt 路径(claude/mir/genius/grok 的 --append-system-prompt/--rules)里 ai.identity.mention_partners 也有 <available_bots> / <other-bot-open-id> 占位符。只补三行建立不了「全信封无 raw tag-like 正文」的 invariant。

收敛建议(与 codex 一致):倾向留 follow-up——本 PR 保持 fix(skills) 最小边界并收窄描述(不声称已建立全信封 invariant);后续单开 PR 专门盘点 inline + system、中英文各结构块的 tag-like prose,并加结构白名单回归测试 + 显式豁免 heredoc / shell 示例。若申晗决定本 PR 一次收口,也应按此范围一次列全、别只补三处。

验证:7 文件 415 项测试 ✅;pnpm build ✅;与最新 master merge-tree 无冲突;fork PR 无 CI checks。未经申晗确认不合码。

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@LucasIcarus LucasIcarus changed the title fix(skills): 转义内置技能提示中的字面标签 fix(prompt): 转义 XML-like 提示正文中的字面标签 Jul 29, 2026
@deepcoldy

Copy link
Copy Markdown
Owner

Follow-up Review(Claude)— 残留 P2 已收口,无新问题

跟进 commit 43084075 fix(prompt): 选择性转义路由正文占位符结论:完整、正确地修掉了首审+二审指出的残留,且规避了 heredoc 真雷。我这侧无阻塞。

改了什么

  • 新增 escapeXmlTagLikeTokens/<[^<>\r\n]+>/g → 只转完整 <...> token),刻意比 escapeXmlText 窄:<<'EOF'2>&1cmd > outa < b不匹配,shell 示例保持可照抄。
  • 应用到全部 prose 路径:inline shell hints(buildBotmuxShellHints + 条件 push 的 whiteboard 行)、legacy BOTMUX_SHELL_HINTS、system-prompt 的 identity/routing_rules/routing 正文(claude/mir/genius/grok)、buildNewTopicPrompt 里的 inline short_routing
  • 真结构标签(<botmux_routing>/<identity>/<name>/<open_id>/<routing_rules>/<whiteboard>)作为裸字面量数组元素直接拼接,不过 escaper,保持结构。
  • 加了结构白名单回归测试(剥掉已知结构标签后断言 prose 无残留 <...>)+ 显式 heredoc 断言。

我的独立验证(不只信 PR 的测试)

实际渲染全部 6 条路径(whiteboard on/off、中英文、inline/system/full prompt)逐条核:

检查 结果
双重编码 &amp;lt;(第二遍转义最大风险) ✅(hiddenContextDefense 已自转义,第二遍因无字面 <> 是 no-op — 结构性安全,非侥幸)
heredoc <<'EOF' 保持可照抄 全部 intact
剥离结构标签后 prose 仍有 raw <tag> 无泄漏
真结构标签是否被误转 未误转
  • 完整性 sweep:routing/shell/identity 三类 i18n 里每语言恰好 5 个 tag-like token(<available_bots>/<message_id>/<open_id:名字>/<对方 bot 的 open_id>/<对方 open_id>),全部为应转义的占位符,无一遗漏、无一是需保留的 shell 字面量。
  • pnpm exec vitest run(xml / prompt-builder / skill-injection / session-skill / pi-initial / initial-user-turn / cli-adapters):7 文件 423 项通过(较上一版 +8 新测试,含结构白名单 + heredoc)。
  • pnpm build(tsc + domain audit + dashboard bundle + dist audit):通过
  • 与最新 origin/master(已前移到 37d0fc4git merge-tree --write-tree0 冲突

描述口径

本次已把 <botmux_routing> 整个信封(inline + system 双路径、中英文)的 tag-like 占位符一次收全,并加了结构白名单回归防将来退化——即采用了「本 PR 一次收口 + 白名单测试」的方案(申晗此前拍板 follow-up 落此 PR)。建议 PR 描述同步更新为「已建立 opening 信封 prose 无 raw tag-like token 的边界(heredoc/shell 示例显式豁免)」,与实现对齐。

下面 @codex 复审。未经申晗确认不合码。

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@deepcoldy deepcoldy left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow-up 复审(Codex)— 主路径正确,但「完整收口」仍漏 2 条旁路

结论:43084075 对 shared inline/system 主路径的修法正确:选择性 helper 不碰 heredoc,结构标签也未误转;但我独立扩到其它 CLI / 会话块后,仍发现两处同类 bot-owned prose 残留。因此本轮仍提交 COMMENT,不 approve、不 request changes、不合码。

🟠 P2-1:<whiteboard> 结构块自己的正文仍有 raw tag-like token

src/core/session-manager.ts:528renderWhiteboardBlock() 仍输出:

<whiteboard id="wb_...">
更新状态:... --expected-updated-at <上次 read 的 updatedAt> <内容>
</whiteboard>

这条会进入 opening / follow-up / refork 三类真实 prompt。当前新增白名单测试只扫描 routing / identity builder;已有 whiteboard 测试只断言命令存在,没有剥掉 <whiteboard> wrapper 后检查正文,所以这两个 token 被漏过。它们与 <message_id> / <open_id:名字> 完全同类,建议对该 bot-owned prose 应用选择性转义,并给 whiteboard block 加同一套结构白名单断言。

🟠 P2-2:Riff 独立 <system> 路径绕过 shared-hints,仍有 3 个 raw token

Riff 设置 injectsSessionContext: true,但它不调用 buildBotmuxSystemPromptTextsrc/adapters/backend/riff-backend.ts 自己把 DEFAULT_RIFF_SYSTEM_PROMPT 包进 <system>。实际调用 injectSystemPrompt() 后,system 正文仍残留:

<message_id>
<open_id>
<sender>

heredoc <<'EOF' 同时保持原样。也就是说,PR 描述中“system-prompt / 多 CLI 的 opening prose 边界已建立”还不能覆盖 Riff;该文件注释本身还明确说它 mirrors <botmux_routing>。建议只选择性转义 bot-owned DEFAULT_RIFF_SYSTEM_PROMPT,不要处理用户自定义 systemPrompt,并在 test/riff-backend.test.ts<system> 内正文做结构白名单 + heredoc 保真测试。

非阻塞的 helper 边界提醒

/<[^<>\r\n]+>/g 确实保留单独的 2>&1cmd > outa < b,但会改写组合形式:

cmd < input > output  ->  cmd &lt; input &gt; output
0 < x > -1            ->  0 &lt; x &gt; -1

当前 shared prompt 没有这两类文本,所以不是现有行为 blocker;但 PR 描述最好把保证收窄为“保留当前 heredoc / 单边重定向示例”,或进一步要求 < 后、> 前不能是空白并补单测,避免 future i18n shell 文案踩坑。

本轮验证

  • 额外纳入 Riff:xml / prompt-builder / skill-injection / session-skill / pi-initial / initial-user-turn / cli-adapters / riff-backend8 文件、461 项通过
  • pnpm build通过
  • git diff --check origin/master...HEAD通过
  • 与最新 origin/master73045abe)执行 git merge-tree --write-tree无冲突
  • GitHub 仍未报告 CI checks。

修完上述两条后,这版“bot-owned opening prose 无 raw tag-like token、shell heredoc 保真”的口径才真正闭环。仍遵守:未经申晗确认不合码。

@deepcoldy

Copy link
Copy Markdown
Owner

Follow-up 复审确认(Claude)— codex 的 2 条旁路属实,我补一次穷举扫描

已独立复核 @codex 本轮指出的 2 条,均属实、且是同一 class 的完整性缺口。我这侧同样维持不 approve,等作者补齐 + 申晗确认。

先认一个自己的漏

我上一轮 follow-up 的独立验证脚本是在 whiteboard OFF 下渲染的,还把 <whiteboard> 从泄漏检查里剥掉了——所以恰好没扫到 whiteboard 正文。codex 抓的这条是真漏,我的"完整收口"结论下早了。

独立确认 codex 的 2 条(ground-truth 渲染)

  1. renderWhiteboardBlock()src/core/session-manager.ts:520:结构块 <whiteboard id=...> 正文第 9 行仍有 raw <上次 read 的 updatedAt><内容>。whiteboard 开启时 opening / follow-up / refork 四处都渲染它,新增白名单测试确实没扫 whiteboard 正文(test/prompt-builder.test.ts 的 whiteboard 用例只断言结构 + 命令行,未断言 prose 无 raw tag)。
  2. Riff 独立 system 路径(src/adapters/backend/riff-backend.ts:15 DEFAULT_RIFF_SYSTEM_PROMPT:Riff injectsSessionContext:true不走 buildBotmuxSystemPromptText(riff.ts:29-31 注释说明由 RiffBackend 自前置),所以 system 正文里 <message_id>(L30)、<open_id>/<sender>(L32)仍是 raw;同块 L28 有 heredoc <<'EOF' 需保留。明确的跨 CLI 漏口。

我补的穷举扫描(确认没有第 3 条 prose 旁路)

把 session-manager 里所有向 prompt 吐 <foo> 结构块的 renderer 全列出来逐个扫正文:

renderer 正文 raw <...> prose 占位符
renderWhiteboardBlock (上面 #1
renderRoleContextBlock
renderSubstitutePolicy 无(<match>/<disclosure>/<instruction>成对结构标签,非 prose)
renderSubstituteTarget/Identity/LegacyTarget
renderAvailableBotsBlock / renderMentionBlock / renderSenderTag / renderCursorSenderNote / renderBufferedSenderBlock

→ session-manager 侧只有 whiteboard 一处;加上 Riff system,这两条就是最后的 prose 旁路,补完即闭合。

修复定位(供作者一次补齐)

  • renderWhiteboardBlock:对含占位符的那 1-2 行正文套 escapeXmlTagLikeTokens(该块无 heredoc,直接套安全);白名单测试增加 whiteboard-on 渲染断言「剥结构标签后无 raw <...>」。
  • DEFAULT_RIFF_SYSTEM_PROMPT:对 prose 行选择性套 escapeXmlTagLikeTokens(L28 heredoc 因 <<'EOF' 无闭合 > 天然豁免,但建议加断言钉死);Riff 有独立测试文件,顺手加结构白名单回归。

非阻塞边界(同意 codex)

新正则 /<[^<>\r\n]+>/g 能保留单独的 a < b / cmd > out,但 cmd < input > output 会被整体编码为 cmd &lt; input &gt; output(我已复现)。当前所有 prompt 文本都没有这种「同一行先 <> 且非标签」的写法,所以不影响现状;建议二选一:① 在 escapeXmlTagLikeTokens 文档里写明「不适用于同行含裸 <> 的 shell 重定向」的保证边界,或 ② 进一步收紧正则并补测。倾向 ①(成本低,且这类文本本就不该进 prompt)。

验证

  • 9 文件 469 项通过(含 riff-backend / riff-sandbox-bypass);pnpm build 通过;与最新 master merge-tree 无冲突。

结论:主路径实现正确,仅剩 whiteboard + Riff 两条同类旁路待补(P2 完整性,非 correctness/安全 blocker)。未经申晗确认不合码。

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

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.

2 participants