安卓客户端登记(他们文档 02 末节):一条助手消息的 content 是 `<p><p>正文</p>`,按他们的解析契约(非贪婪)必然把 `<p>正文` 当内容 ⇒ 用户 直接看到字面 `<p>`。任何按该契约定实现的消费方都会踩。 根因(两半都证实,都在同一处): - (a) 规整器**原样放过**模型吐出的 `<p><p>X</p>`:段块正则非贪婪,把它当成 一个"已完整包裹"的块逐字保留(旧的覆盖检查用同一个正则 ⇒ 护栏本身是盲的); - (b) 规整器**亲手制造**该形状:末段漏 `</p>` 时无条件套一层 ⇒ `<p><p>X</p>`。 实证:模型吐 `<p>那现在…?`(无闭标签)经旧代码规整后与上报串**逐字节相同**; - (c) 顺带修一个幂等漏洞:落单 `</p>` 旧行为会越跑越多(`<p></p></p>`)。 修法(新增 paragraph_repair.go:配对修复 + 接线): `<p><p>X</p>`⇒`<p>X</p>`;`<p>X`⇒补 `</p>`;`<p>正文<p>`⇒合并;`<p>A</p><p>`⇒ 丢空开标签;`<p></p>`⇒整对丢;落单 `</p>`⇒丢标签正文不动。不吞正文护栏: `<p`(无 `>`)/`<p2>`/`1 < 2`/`a<b>`/代码围栏/`<action>` 内 `<p>`/自闭合 `<p/>` 一律原样(每条都有用例)。`normalizePiece` 改为"补闭不补开"——在造出 畸形的那一点上根治。 验收(主会话复跑 + 亲自变异):go build ./... + vet 绿;turnreply / gateway 全量 / channelbridge 三包绿;我用 -overlay 摘掉配对修复接线 ⇒ TestNormalizeParagraphs_* 变红。生产库只读核查:177 行 assistant 里**仅 1 行** 畸形(各消费方已容错)⇒ 不做一次性修复脚本。 ⚠️ 未部署:跑着的 :10942 仍是旧产物 ⇒ 畸形仍会产出,安卓端的容错请继续保留。
裁定(已发安卓):**能修**,走**写入侧规整** —— 不指望模型输出、不动客户端。 - 新增 internal/runtime/turnreply/paragraph.go 的 NormalizeParagraphs: 把未被 <p> 包裹的段落补上 <p>(按空行分段;<action>/<narration> 段也包进 <p>, 与正确形态 <p><action>…</action>…</p> 一致) - **幂等**守门断言:已完全包裹的文本逐字不变(防二次加工) - 落点覆盖 Web 与 IM 两条落库路径(gateway/session_turn_reply.go、channelbridge/turnreply.go、 proactive.go)⇒ 两边共用同一份规整文本 ⇒ **历史一次性变好、两个客户端零改动**、渲染口径不分叉 (测试与变异详见切片报告)
根因(agent-194,文件:行): - inbound.go 的 deliverReply 对空回复直接 return(静默)⇒ 只剩 user 消息; - runInboundTurn 报错直接 return ⇒ 失败回合连说明都没有。 修法:抽共享**纯函数**包 internal/runtime/turnreply(从网关逐字搬运、零语义改动)+ channelbridge/turnreply.go 的 turnVisibleReply(与网关 persistTurnReply 同构); 取消边界在 IM 用 context.Canceled / ctx 取消表达(渠道桥无代次概念,如实登记差异)。 测试 6 条:空回复/纯空白/轮次用尽/安全网/回合报错/取消不落库/正常逐字不变。 变异:还原旧静默行为 ⇒ 4 条红(含「出站 0 条,want 1」与英文内部诊断外泄)。