{"title":"P1 契约 v1.1 · Marker 层 4 出口穷举 + marker_error 宿主面表达(含区内散文保序 / 纯散文区二分)","content":"# 契约 v1.1 · Marker 层 4 出口穷举状态机 + `marker_error` 宿主面表达\n\n**归属**:群任务 #2「P1 修复 Trae 通道工具调用泄漏导致会话中断」· 第 3 棒\n**冻结状态**:FREEZE v1.1(版本归属自述记为 UNKNOWN——chair 明示不采信文件头归属句,只认「文件在此路径 + 正文与冻结口径一致」;正文口径经 chair 逐节对表通过)。\n**v1.1 增补(小禾 提、chair 采纳,仅新增不改既有四不变量)**:①还原成功后,标记区内**对象起点之前的可读散文按 `text` 保序先行产出**,不得静默丢弃(只禁模板 token,不禁正文);②「找不到对象起点」改为二分——区内含调用形态(`\"name\"` 且含 `parameters`/`arguments`)→ 尝试还原、失败落 `marker_error`;区内**无任何调用形态**(正文举例/讨论协议)→ 走 E4 剥离模板 token 后作 `text`,**不判错**。\n**上位输入**:测枢 冻结版 salvage 内核规格(群内 `[POSITION]`,23:22)+ 知微 证据棒(pin://36a98594… / pin://e4b33e55…)+ chair 亲读 trae-shim.mjs L609-653 与 4 出口逐行坐实\n**交汇面(唯一)**:`markerEmitParsed` 的返回契约从 `items | null` 改为 `items | errorItem`;4 个出口按此分派。\n\n---\n\n## 0. 一句话契约\n\n`trae-shim.mjs` 标记层在任何输入下,对任一标记区 payload 必须落到且只落到**三个出口之一**:\n`tool_call`(还原成功) / `marker_error`(不可还原,结构化错误) / `text`(白名单外剥离策略)。\n**不存在第四个出口,不存在原样透传 `M_BEGIN+payload+M_END` 进气泡的路径。**\n\n---\n\n## 1. 五类坏载荷判据(测枢 冻结口径,逐类可判)\n\n| # | 类别 | 判据 | 结局 |\n|---|---|---|---|\n| ① | 散文前缀 | payload 去掉前导空白后**不以 `{`/`[` 起头**,但其后**存在**对象起点 | 字符串感知扫描找到首 `{`/`[` → 尝试还原;**还原成功后区内对象起点前的可读散文按 `text` 保序先行产出(不静默丢弃)**。找不到任何对象起点时**二分**:区内含调用形态(`\"name\"` 且含 `parameters`/`arguments`)→ `marker_error`;区内无任何调用形态(正文举例/讨论协议)→ 走 E4 剥离模板 token 后作 `text`,**不判错** |\n| ② | 嵌套第二标记 | payload 内出现第二个 `M_BEGIN` | 深度计数处置,**不得**在参数内首个 `M_END` 处腰斩 |\n| ③ | 深层嵌套引号 / 反斜杠 | 参数内出现 `M_END` 字面量、`\"`、`\\` | 字符串感知 + 深度计数定位外层终止符(消除 L564 普通 `indexOf` 腰斩) |\n| ④ | 未闭合截断 | 流结束/overflow 时深度未回 0,无合法外层 `M_END` | `flushMarker` 尝试抢救;失败 → `marker_error`(exit=`flush`/`overflow`) |\n| ⑤ | 空参 / 半截参数 / 多调用只取其一 | name 抽到但 args 空、半截,或数组内后续调用被丢 | **≠ 还原成功**,落 `marker_error`,**可执行前拦截**(禁止以错误参数执行 write/bash) |\n\n**args 完整性判据(还原成功的必要条件,三者全满足)**:\n1. `name` 非空;\n2. `args` 可 `JSON.parse` 为对象,**且非降级空参**(模型**未产出** `parameters` 键 ≠ 合法空参 `{}`,须区分);\n3. **多调用全部逐个还原**,不允许只取第一个。\n\n任一不满足 → `marker_error`。\n\n---\n\n## 2. 四出口穷举(三选一分派)\n\n| 出口 | 代码位(v2 现状) | v1 契约动作 | exit 值 |\n|---|---|---|---|\n| E1 feedText 已闭合不可解析 | L575 | 删原样透传 → marker_error | feedText |\n| E2 flushMarker 未闭合 | L602 | 删原样透传 → marker_error | flush |\n| E3 超长保护连 M_END 都不补 | L579-582 | 先抢救;失败 → marker_error | overflow |\n| E4 白名单外分支 M_BEGIN+raw+M_END | L613-616 | 改为剥离模板 token 只留文本 | whitelist(产生 text) |\n\n> 口径更正留痕:chair 第三轮曾报「2 处裸透传出口」,实测为 4 处,已更正并逐行坐实。\n\n---\n\n## 3. 统一错误项结构\n\n{ type:'error', value:{ kind:'marker_unrecoverable', upstreamRaw: payload, exit:'feedText|flush|overflow', reason: '<判据名>' } }\n\n- upstreamRaw 为原始 payload 字节,仅进非展示字段。\n- 不变量:upstreamRaw 绝不进 content / delta.content。\n\n---\n\n## 4. marker_error 的宿主面表达(双通道,测枢源码实测定稿)\n\n宿主 bridge 的 readSseText 只读 choices[0]、只认 finish_reason,完全不读 frame.error。因此:\n\n1. SSE 流式:发标准 error 帧 且 发一个带 finish_reason 的终止帧(data: {\"choices\":[{\"index\":0,\"delta\":{},\"finish_reason\":\"stop\"}]})。只发 error 帧会被静默丢弃 → 空 completion → 宿主抛 EmptyCompletion。\n2. 非流式:{\"error\":{message,type,code,upstream_raw}} + HTTP 非 2xx。\n3. 不变量:upstreamRaw 只进 error 对象非展示字段。\n\n> 边界标注:源码逐行实测 + 静态推导,非端到端运行时观测。端到端交 明镜。\n\n---\n\n## 5. 入站 tools(无分支可丢)\n\ntools 唯一注入点 callTrae:655-656,callTraeWithRetry 两次 attempt 重传同一份完整 opts。shim 内不存在「续接/压缩丢 tools」的代码分支。认证点放在出站 HTTP body.tools 条数。\n\n---\n\n## 6. 本棒交付边界\n\n- 改:trae-shim.mjs 310-640 段(标记层)+ handleStream 错误出口 + handleNonStream 两条错误出口补 upstreamRaw。\n- 不动:请求侧 tools 转发(:654-656);toTraeMessages 标记回写(:290-296);17892 与 shim-standalone.mjs(独立文件/进程,零共享)。\n\n---\n\n## 8. 回归判据(交 明镜)\n\n每条向量必须补丁前红 / 补丁后绿,且带原始字节引用;恒绿判据不采信。\n1. 五类坏载荷各一向量(散文前缀 / 嵌套第二标记 / 深转义 / 未闭合截断 / 空参半截)。\n2. 专设反例:仅有 error 帧、无终止帧 → 补丁前应红(空 completion),补丁后应绿(双通道到位)。补丁后仍红 = 双通道未落地,退回。\n3. 端到端观测:气泡不出现模板 token 且 宿主感知回合失败。\n\n---\n\n## 9. 落盘自证\n\n本文件由 chair 青梧 代 小禾 落盘(小禾 未在兜底时限 23:55 前交付)。契约正文来源:测枢 冻结版 salvage 内核规格 + 知微 证据棒 + chair 亲读 shim L609-653 与 4 出口逐行坐实。小禾 原有草稿口径已并入第 1/2/3 节;如 小禾 后续补交更细版本,以「两者取其严」合流。\n"}