dominds 1.13.2 → 1.15.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (200) hide show
  1. package/dist/bootstrap/global-dialog-event-broadcaster.d.ts +18 -0
  2. package/dist/bootstrap/global-dialog-event-broadcaster.js +81 -0
  3. package/dist/dialog-fork.js +13 -12
  4. package/dist/dialog.d.ts +61 -50
  5. package/dist/dialog.js +284 -78
  6. package/dist/docs/dialog-system.md +12 -0
  7. package/dist/docs/dialog-system.zh.md +12 -0
  8. package/dist/docs/dominds-terminology.md +17 -0
  9. package/dist/docs/issues/global-dialog-event-broadcaster-missing.md +128 -0
  10. package/dist/docs/llm-provider-isolation.md +35 -0
  11. package/dist/docs/llm-provider-isolation.zh.md +35 -0
  12. package/dist/llm/client.d.ts +2 -1
  13. package/dist/llm/defaults.yaml +118 -4
  14. package/dist/llm/gen/anthropic.js +2 -4
  15. package/dist/llm/gen/codex.d.ts +11 -0
  16. package/dist/llm/gen/codex.js +41 -31
  17. package/dist/llm/gen/failure-classifier.js +17 -0
  18. package/dist/llm/gen/mock.js +45 -21
  19. package/dist/llm/gen/openai-compatible.d.ts +2 -0
  20. package/dist/llm/gen/openai-compatible.js +43 -38
  21. package/dist/llm/gen/openai.d.ts +3 -1
  22. package/dist/llm/gen/openai.js +888 -71
  23. package/dist/llm/gen/tool-call-context.d.ts +7 -2
  24. package/dist/llm/gen/tool-call-context.js +55 -13
  25. package/dist/llm/gen.d.ts +60 -3
  26. package/dist/llm/kernel-driver/context.js +1 -1
  27. package/dist/llm/kernel-driver/drive.js +374 -348
  28. package/dist/llm/kernel-driver/flow.js +3 -3
  29. package/dist/llm/kernel-driver/guardrails.d.ts +1 -1
  30. package/dist/llm/kernel-driver/guardrails.js +4 -4
  31. package/dist/llm/kernel-driver/runtime.js +11 -29
  32. package/dist/llm/kernel-driver/subdialog.js +56 -5
  33. package/dist/llm/kernel-driver/tellask-special.d.ts +38 -12
  34. package/dist/llm/kernel-driver/tellask-special.js +489 -180
  35. package/dist/llm/kernel-driver/types.d.ts +1 -1
  36. package/dist/persistence.d.ts +30 -62
  37. package/dist/persistence.js +978 -986
  38. package/dist/priming.js +398 -365
  39. package/dist/recovery/reply-special.js +3 -3
  40. package/dist/runtime/inter-dialog-format.d.ts +1 -1
  41. package/dist/runtime/inter-dialog-format.js +1 -1
  42. package/dist/runtime/reply-prompt-copy.js +4 -4
  43. package/dist/server/setup-routes.js +26 -5
  44. package/dist/server/snippets-routes.d.ts +1 -0
  45. package/dist/server/snippets-routes.js +20 -9
  46. package/dist/server/websocket-handler.js +58 -25
  47. package/dist/shared/utils/fbr.js +12 -8
  48. package/dist/shared/utils/inter-dialog-format.js +6 -4
  49. package/dist/team.d.ts +24 -13
  50. package/dist/team.js +123 -32
  51. package/dist/tool.d.ts +26 -0
  52. package/dist/tool.js +97 -0
  53. package/dist/tools/team_mgmt.js +18 -0
  54. package/package.json +2 -2
  55. package/webapp/dist/assets/{_basePickBy-CBOtd63g.js → _basePickBy-DsirmCgI.js} +3 -3
  56. package/webapp/dist/assets/_basePickBy-DsirmCgI.js.map +1 -0
  57. package/webapp/dist/assets/{_baseUniq-mfoKz4Wm.js → _baseUniq-tR6G8loB.js} +2 -2
  58. package/webapp/dist/assets/_baseUniq-tR6G8loB.js.map +1 -0
  59. package/webapp/dist/assets/{arc-Dq0WZLyu.js → arc-CzxpASkZ.js} +2 -2
  60. package/webapp/dist/assets/arc-CzxpASkZ.js.map +1 -0
  61. package/webapp/dist/assets/{architectureDiagram-VXUJARFQ-CNmygmp3.js → architectureDiagram-2XIMDMQ5-BSH7H5oI.js} +26 -8
  62. package/webapp/dist/assets/architectureDiagram-2XIMDMQ5-BSH7H5oI.js.map +1 -0
  63. package/webapp/dist/assets/{blockDiagram-VD42YOAC-DvE0lybt.js → blockDiagram-WCTKOSBZ-DpLIr7yO.js} +187 -170
  64. package/webapp/dist/assets/blockDiagram-WCTKOSBZ-DpLIr7yO.js.map +1 -0
  65. package/webapp/dist/assets/{c4Diagram-YG6GDRKO-CR7zJ2_u.js → c4Diagram-IC4MRINW-WuYKgWfY.js} +4 -4
  66. package/webapp/dist/assets/c4Diagram-IC4MRINW-WuYKgWfY.js.map +1 -0
  67. package/webapp/dist/assets/{channel-DrTrnYx4.js → channel-B-v9dqLN.js} +2 -2
  68. package/webapp/dist/assets/channel-B-v9dqLN.js.map +1 -0
  69. package/webapp/dist/assets/{chunk-4BX2VUAB-CVuJEIeN.js → chunk-4BX2VUAB-MtFUfKZy.js} +2 -2
  70. package/webapp/dist/assets/chunk-4BX2VUAB-MtFUfKZy.js.map +1 -0
  71. package/webapp/dist/assets/{chunk-55IACEB6-BxUoXApB.js → chunk-55IACEB6-rY9AJdzj.js} +2 -2
  72. package/webapp/dist/assets/chunk-55IACEB6-rY9AJdzj.js.map +1 -0
  73. package/webapp/dist/assets/{chunk-FMBD7UC4-TX-LVAaV.js → chunk-FMBD7UC4-B-RtOs7e.js} +2 -2
  74. package/webapp/dist/assets/chunk-FMBD7UC4-B-RtOs7e.js.map +1 -0
  75. package/webapp/dist/assets/{chunk-TZMSLE5B-Cw689yRl.js → chunk-JSJVCQXG-Da1d3uS4.js} +14 -6
  76. package/webapp/dist/assets/chunk-JSJVCQXG-Da1d3uS4.js.map +1 -0
  77. package/webapp/dist/assets/{chunk-QN33PNHL-D1uiKlOO.js → chunk-KX2RTZJC-DH9UrpuG.js} +2 -2
  78. package/webapp/dist/assets/chunk-KX2RTZJC-DH9UrpuG.js.map +1 -0
  79. package/webapp/dist/assets/{chunk-DI55MBZ5-SAhxUTqQ.js → chunk-NQ4KR5QH-CK365lrr.js} +9 -7
  80. package/webapp/dist/assets/chunk-NQ4KR5QH-CK365lrr.js.map +1 -0
  81. package/webapp/dist/assets/{chunk-QZHKN3VN-BxuV0Oba.js → chunk-QZHKN3VN-BCaWPGDm.js} +2 -2
  82. package/webapp/dist/assets/chunk-QZHKN3VN-BCaWPGDm.js.map +1 -0
  83. package/webapp/dist/assets/{chunk-B4BG7PRW-DpMa3-9L.js → chunk-WL4C6EOR-DDCnEwft.js} +171 -121
  84. package/webapp/dist/assets/chunk-WL4C6EOR-DDCnEwft.js.map +1 -0
  85. package/webapp/dist/assets/{classDiagram-2ON5EDUG-BTTGianr.js → classDiagram-VBA2DB6C-CvMBU4WA.js} +7 -6
  86. package/webapp/dist/assets/classDiagram-VBA2DB6C-CvMBU4WA.js.map +1 -0
  87. package/webapp/dist/assets/{classDiagram-v2-WZHVMYZB-BTTGianr.js → classDiagram-v2-RAHNMMFH-CvMBU4WA.js} +7 -6
  88. package/webapp/dist/assets/classDiagram-v2-RAHNMMFH-CvMBU4WA.js.map +1 -0
  89. package/webapp/dist/assets/{clone-Dk8cAI3I.js → clone-r98jR0MC.js} +2 -2
  90. package/webapp/dist/assets/clone-r98jR0MC.js.map +1 -0
  91. package/webapp/dist/assets/{cose-bilkent-S5V4N54A-BjJnzB2N.js → cose-bilkent-S5V4N54A-t6J60Ogk.js} +2 -2
  92. package/webapp/dist/assets/cose-bilkent-S5V4N54A-t6J60Ogk.js.map +1 -0
  93. package/webapp/dist/assets/cytoscape.esm-Bm8DJGmZ.js.map +1 -1
  94. package/webapp/dist/assets/{dagre-6UL2VRFP-VF-xGhAf.js → dagre-KLK3FWXG-BlqmY2DV.js} +7 -7
  95. package/webapp/dist/assets/dagre-KLK3FWXG-BlqmY2DV.js.map +1 -0
  96. package/webapp/dist/assets/defaultLocale-B2RvLBDe.js.map +1 -1
  97. package/webapp/dist/assets/{diagram-PSM6KHXK-Ba5U0oRY.js → diagram-E7M64L7V-FwCHeIUD.js} +10 -10
  98. package/webapp/dist/assets/diagram-E7M64L7V-FwCHeIUD.js.map +1 -0
  99. package/webapp/dist/assets/{diagram-QEK2KX5R-DoYCnEw_.js → diagram-IFDJBPK2-NhtmkuZG.js} +9 -8
  100. package/webapp/dist/assets/diagram-IFDJBPK2-NhtmkuZG.js.map +1 -0
  101. package/webapp/dist/assets/{diagram-S2PKOQOG-CkK4SRyE.js → diagram-P4PSJMXO-B9FcmokX.js} +8 -8
  102. package/webapp/dist/assets/diagram-P4PSJMXO-B9FcmokX.js.map +1 -0
  103. package/webapp/dist/assets/{erDiagram-Q2GNP2WA-DkI5eYww.js → erDiagram-INFDFZHY-DHKmWvtB.js} +96 -75
  104. package/webapp/dist/assets/erDiagram-INFDFZHY-DHKmWvtB.js.map +1 -0
  105. package/webapp/dist/assets/{flowDiagram-NV44I4VS-wOdPUQ7Y.js → flowDiagram-PKNHOUZH-C7Zi8I7T.js} +98 -81
  106. package/webapp/dist/assets/flowDiagram-PKNHOUZH-C7Zi8I7T.js.map +1 -0
  107. package/webapp/dist/assets/{ganttDiagram-JELNMOA3-BtRWgkUH.js → ganttDiagram-A5KZAMGK-Cv2T8tz_.js} +28 -3
  108. package/webapp/dist/assets/ganttDiagram-A5KZAMGK-Cv2T8tz_.js.map +1 -0
  109. package/webapp/dist/assets/{gitGraphDiagram-V2S2FVAM-Bsz7u1vi.js → gitGraphDiagram-K3NZZRJ6-DztaipJU.js} +38 -46
  110. package/webapp/dist/assets/gitGraphDiagram-K3NZZRJ6-DztaipJU.js.map +1 -0
  111. package/webapp/dist/assets/graph-C5yf62Vs.js +782 -0
  112. package/webapp/dist/assets/graph-C5yf62Vs.js.map +1 -0
  113. package/webapp/dist/assets/{index-xvYYeHuy.css → index-YaxF76or.css} +1 -1
  114. package/webapp/dist/assets/{index-rYmIohM_.js → index-hve5MWPs.js} +1603 -1415
  115. package/webapp/dist/assets/index-hve5MWPs.js.map +1 -0
  116. package/webapp/dist/assets/{infoDiagram-HS3SLOUP-BMaxCvH5.js → infoDiagram-LFFYTUFH-VgsbBPZP.js} +7 -7
  117. package/webapp/dist/assets/infoDiagram-LFFYTUFH-VgsbBPZP.js.map +1 -0
  118. package/webapp/dist/assets/init-ZxktEp_H.js.map +1 -1
  119. package/webapp/dist/assets/ishikawaDiagram-PHBUUO56-C7j3YWdw.js +966 -0
  120. package/webapp/dist/assets/ishikawaDiagram-PHBUUO56-C7j3YWdw.js.map +1 -0
  121. package/webapp/dist/assets/{journeyDiagram-XKPGCS4Q-ejyerzmG.js → journeyDiagram-4ABVD52K-OO8sev-Y.js} +5 -5
  122. package/webapp/dist/assets/journeyDiagram-4ABVD52K-OO8sev-Y.js.map +1 -0
  123. package/webapp/dist/assets/{kanban-definition-3W4ZIXB7-CYj35TEs.js → kanban-definition-K7BYSVSG-DiYCC1Ig.js} +5 -3
  124. package/webapp/dist/assets/kanban-definition-K7BYSVSG-DiYCC1Ig.js.map +1 -0
  125. package/webapp/dist/assets/{layout-7Ql4zmuL.js → layout-DdZSgGdu.js} +5 -5
  126. package/webapp/dist/assets/layout-DdZSgGdu.js.map +1 -0
  127. package/webapp/dist/assets/{linear-CVmgVPuZ.js → linear-7-aHtaFi.js} +2 -2
  128. package/webapp/dist/assets/linear-7-aHtaFi.js.map +1 -0
  129. package/webapp/dist/assets/{mindmap-definition-VGOIOE7T-DOpxjGVo.js → mindmap-definition-YRQLILUH-IG3I-RdD.js} +7 -5
  130. package/webapp/dist/assets/mindmap-definition-YRQLILUH-IG3I-RdD.js.map +1 -0
  131. package/webapp/dist/assets/ordinal-CxptdPJm.js.map +1 -1
  132. package/webapp/dist/assets/{pieDiagram-ADFJNKIX-CLQjpmAG.js → pieDiagram-SKSYHLDU-z68KJT5r.js} +8 -8
  133. package/webapp/dist/assets/pieDiagram-SKSYHLDU-z68KJT5r.js.map +1 -0
  134. package/webapp/dist/assets/{quadrantDiagram-AYHSOK5B-ClD_bz7z.js → quadrantDiagram-337W2JSQ-DaENWdO6.js} +3 -3
  135. package/webapp/dist/assets/quadrantDiagram-337W2JSQ-DaENWdO6.js.map +1 -0
  136. package/webapp/dist/assets/{requirementDiagram-UZGBJVZJ-DOpb-TWH.js → requirementDiagram-Z7DCOOCP-ROTFv4sa.js} +16 -6
  137. package/webapp/dist/assets/requirementDiagram-Z7DCOOCP-ROTFv4sa.js.map +1 -0
  138. package/webapp/dist/assets/{sankeyDiagram-TZEHDZUN-D8Hsj3yx.js → sankeyDiagram-WA2Y5GQK-CK7qtpzw.js} +2 -2
  139. package/webapp/dist/assets/sankeyDiagram-WA2Y5GQK-CK7qtpzw.js.map +1 -0
  140. package/webapp/dist/assets/{sequenceDiagram-WL72ISMW-CFMNjBER.js → sequenceDiagram-2WXFIKYE-R5lDySeI.js} +601 -201
  141. package/webapp/dist/assets/sequenceDiagram-2WXFIKYE-R5lDySeI.js.map +1 -0
  142. package/webapp/dist/assets/{stateDiagram-FKZM4ZOC-BQeDlw0P.js → stateDiagram-RAJIS63D-sr7msF5U.js} +9 -9
  143. package/webapp/dist/assets/stateDiagram-RAJIS63D-sr7msF5U.js.map +1 -0
  144. package/webapp/dist/assets/{stateDiagram-v2-4FDKWEC3-DscX61Rs.js → stateDiagram-v2-FVOUBMTO-X663liwS.js} +5 -5
  145. package/webapp/dist/assets/stateDiagram-v2-FVOUBMTO-X663liwS.js.map +1 -0
  146. package/webapp/dist/assets/{timeline-definition-IT6M3QCI-BcXPSTiw.js → timeline-definition-YZTLITO2-Bw0TdG26.js} +3 -3
  147. package/webapp/dist/assets/timeline-definition-YZTLITO2-Bw0TdG26.js.map +1 -0
  148. package/webapp/dist/assets/{treemap-GDKQZRPO-BBr4UV0Z.js → treemap-KZPCXAKY-D_sjKwI7.js} +37 -24
  149. package/webapp/dist/assets/treemap-KZPCXAKY-D_sjKwI7.js.map +1 -0
  150. package/webapp/dist/assets/vennDiagram-LZ73GAT5-DhlHIHid.js +2487 -0
  151. package/webapp/dist/assets/vennDiagram-LZ73GAT5-DhlHIHid.js.map +1 -0
  152. package/webapp/dist/assets/{xychartDiagram-PRI3JC2R-CS5RAtQE.js → xychartDiagram-JWTSCODW-C65ESjTc.js} +4 -4
  153. package/webapp/dist/assets/xychartDiagram-JWTSCODW-C65ESjTc.js.map +1 -0
  154. package/webapp/dist/index.html +2 -2
  155. package/webapp/dist/assets/_basePickBy-CBOtd63g.js.map +0 -1
  156. package/webapp/dist/assets/_baseUniq-mfoKz4Wm.js.map +0 -1
  157. package/webapp/dist/assets/arc-Dq0WZLyu.js.map +0 -1
  158. package/webapp/dist/assets/architectureDiagram-VXUJARFQ-CNmygmp3.js.map +0 -1
  159. package/webapp/dist/assets/blockDiagram-VD42YOAC-DvE0lybt.js.map +0 -1
  160. package/webapp/dist/assets/c4Diagram-YG6GDRKO-CR7zJ2_u.js.map +0 -1
  161. package/webapp/dist/assets/channel-DrTrnYx4.js.map +0 -1
  162. package/webapp/dist/assets/chunk-4BX2VUAB-CVuJEIeN.js.map +0 -1
  163. package/webapp/dist/assets/chunk-55IACEB6-BxUoXApB.js.map +0 -1
  164. package/webapp/dist/assets/chunk-B4BG7PRW-DpMa3-9L.js.map +0 -1
  165. package/webapp/dist/assets/chunk-DI55MBZ5-SAhxUTqQ.js.map +0 -1
  166. package/webapp/dist/assets/chunk-FMBD7UC4-TX-LVAaV.js.map +0 -1
  167. package/webapp/dist/assets/chunk-QN33PNHL-D1uiKlOO.js.map +0 -1
  168. package/webapp/dist/assets/chunk-QZHKN3VN-BxuV0Oba.js.map +0 -1
  169. package/webapp/dist/assets/chunk-TZMSLE5B-Cw689yRl.js.map +0 -1
  170. package/webapp/dist/assets/classDiagram-2ON5EDUG-BTTGianr.js.map +0 -1
  171. package/webapp/dist/assets/classDiagram-v2-WZHVMYZB-BTTGianr.js.map +0 -1
  172. package/webapp/dist/assets/clone-Dk8cAI3I.js.map +0 -1
  173. package/webapp/dist/assets/cose-bilkent-S5V4N54A-BjJnzB2N.js.map +0 -1
  174. package/webapp/dist/assets/dagre-6UL2VRFP-VF-xGhAf.js.map +0 -1
  175. package/webapp/dist/assets/diagram-PSM6KHXK-Ba5U0oRY.js.map +0 -1
  176. package/webapp/dist/assets/diagram-QEK2KX5R-DoYCnEw_.js.map +0 -1
  177. package/webapp/dist/assets/diagram-S2PKOQOG-CkK4SRyE.js.map +0 -1
  178. package/webapp/dist/assets/erDiagram-Q2GNP2WA-DkI5eYww.js.map +0 -1
  179. package/webapp/dist/assets/flowDiagram-NV44I4VS-wOdPUQ7Y.js.map +0 -1
  180. package/webapp/dist/assets/ganttDiagram-JELNMOA3-BtRWgkUH.js.map +0 -1
  181. package/webapp/dist/assets/gitGraphDiagram-V2S2FVAM-Bsz7u1vi.js.map +0 -1
  182. package/webapp/dist/assets/graph-DAMkuTbn.js +0 -425
  183. package/webapp/dist/assets/graph-DAMkuTbn.js.map +0 -1
  184. package/webapp/dist/assets/index-rYmIohM_.js.map +0 -1
  185. package/webapp/dist/assets/infoDiagram-HS3SLOUP-BMaxCvH5.js.map +0 -1
  186. package/webapp/dist/assets/journeyDiagram-XKPGCS4Q-ejyerzmG.js.map +0 -1
  187. package/webapp/dist/assets/kanban-definition-3W4ZIXB7-CYj35TEs.js.map +0 -1
  188. package/webapp/dist/assets/layout-7Ql4zmuL.js.map +0 -1
  189. package/webapp/dist/assets/linear-CVmgVPuZ.js.map +0 -1
  190. package/webapp/dist/assets/mindmap-definition-VGOIOE7T-DOpxjGVo.js.map +0 -1
  191. package/webapp/dist/assets/pieDiagram-ADFJNKIX-CLQjpmAG.js.map +0 -1
  192. package/webapp/dist/assets/quadrantDiagram-AYHSOK5B-ClD_bz7z.js.map +0 -1
  193. package/webapp/dist/assets/requirementDiagram-UZGBJVZJ-DOpb-TWH.js.map +0 -1
  194. package/webapp/dist/assets/sankeyDiagram-TZEHDZUN-D8Hsj3yx.js.map +0 -1
  195. package/webapp/dist/assets/sequenceDiagram-WL72ISMW-CFMNjBER.js.map +0 -1
  196. package/webapp/dist/assets/stateDiagram-FKZM4ZOC-BQeDlw0P.js.map +0 -1
  197. package/webapp/dist/assets/stateDiagram-v2-4FDKWEC3-DscX61Rs.js.map +0 -1
  198. package/webapp/dist/assets/timeline-definition-IT6M3QCI-BcXPSTiw.js.map +0 -1
  199. package/webapp/dist/assets/treemap-GDKQZRPO-BBr4UV0Z.js.map +0 -1
  200. package/webapp/dist/assets/xychartDiagram-PRI3JC2R-CS5RAtQE.js.map +0 -1
@@ -0,0 +1,128 @@
1
+ # Issue: `Global dialog event broadcaster missing`
2
+
3
+ ## Summary
4
+
5
+ 在没有初始化全局 dialog event broadcaster 的运行环境里,发布 `new_q4h_asked` / `q4h_answered` / `subdialog_created_evt` / `dlg_touched_evt` 会直接抛错:
6
+
7
+ ```text
8
+ Global dialog event broadcaster missing: cannot publish new_q4h_asked for dialog=<id>
9
+ ```
10
+
11
+ 这不只是噪音日志。对于 `askHuman` / Q4H 链路,这个异常会被上层当作“Q4H 注册失败”处理,导致:
12
+
13
+ - Q4H 状态文件其实已经写入
14
+ - 但 runtime 仍然走失败分支
15
+ - 追加 `stream_error_evt`
16
+ - 向当前对话注入失败型 tellask result
17
+ - 造成“持久化成功、广播缺失、业务语义却被当作失败”的错位
18
+
19
+ ## Observed In
20
+
21
+ - `tests/recovery/reply-special-after-restart.ts`
22
+ - 其它不启动完整 websocket server、但会触发 Q4H runtime 的脚本型 / test 型运行环境
23
+
24
+ ## Current Call Chain
25
+
26
+ 1. `main/llm/kernel-driver/tellask-special.ts`
27
+ `executeTellaskCall()` 在 Q4H 分支先调用 `DialogPersistence.appendQuestion4HumanState()` 落盘
28
+ 2. 同一分支随后调用 `postDialogEvent(dlg, { type: 'new_q4h_asked', ... })`
29
+ 3. `main/evt-registry.ts`
30
+ `dispatchGloballyIfNeeded()` 发现 `new_q4h_asked` 属于 global-only event
31
+ 4. 若 `globalDialogEventBroadcaster === null`,则直接 `throw`
32
+ 5. 异常被 `tellask-special.ts` 的 Q4H `try/catch` 捕获
33
+ 6. 上层误判为“Q4H register invariant violation”,转入失败补偿路径
34
+
35
+ ## Correct Contract
36
+
37
+ 这里应明确:**global dialog event broadcaster 不是可选增强,而是 runtime 必要基础设施**。
38
+
39
+ 也就是说:
40
+
41
+ - 任何支持 dialog runtime 的运行环境,在进入对话驱动 / Q4H / 子对话逻辑前,都必须先完成 broadcaster bootstrap
42
+ - WebUI server 只是其中一种 runtime;将来其它 runtime 也必须在位
43
+ - 测试运行环境同样应按 runtime bootstrap 的方式安装 broadcaster,而不是在业务 helper 里零散补丁
44
+
45
+ 因此,这个问题的根因不是“event layer 应该 graceful degrade”,而是**存在未完成 broadcaster bootstrap 的 runtime**,并且这个缺陷直到业务路径中途才暴露。
46
+
47
+ ## Why It Happened
48
+
49
+ 此前全局 broadcaster 只在 `main/server/websocket-handler.ts` 的 websocket server 初始化阶段通过 `setGlobalDialogEventBroadcaster(...)` 安装。
50
+
51
+ 但脚本 / 测试 / recovery 运行环境也会直接触发同一批 global-only event。于是形成了:
52
+
53
+ - dialog-scoped event registry 已可用
54
+ - global broadcaster 尚未安装
55
+ - `postDialogEvent()` 处理 global-only event 时立刻抛错
56
+ - 上层业务把该异常误判成 Q4H 注册失败 / 业务失败
57
+
58
+ ## Impact
59
+
60
+ ### User-visible / behavior impact
61
+
62
+ - `askHuman` 在未完成 runtime bootstrap 的环境下会被错误地当作失败处理
63
+ - 当前对话会收到失败型 tellask result,而不是保持正常 pending Q4H 语义
64
+
65
+ ### Persistence / state consistency impact
66
+
67
+ - Q4H state 已经写入,但 runtime 业务语义被标成失败
68
+ - 形成“状态已存在、当前轮回答却说失败”的不一致
69
+
70
+ ### Test / diagnostics impact
71
+
72
+ - 用例可能通过,但日志出现误导性错误
73
+ - 排障时很难第一眼区分“runtime 缺少 broadcaster bootstrap”还是“Q4H 真的注册失败”
74
+
75
+ ## Repro
76
+
77
+ 1. 在不启动 websocket server、且未安装 recording broadcaster 的脚本环境里创建 dialog
78
+ 2. 触发 `askHuman`
79
+ 3. 观察日志出现 `Global dialog event broadcaster missing`
80
+ 4. 观察上层继续记录 `Q4H register invariant violation`
81
+
82
+ ## Root Cause
83
+
84
+ 根因不是 Q4H 注册本身,而是 runtime contract 与 bootstrap 现实不一致:
85
+
86
+ - 契约上:broadcaster 是 mandatory infra
87
+ - 现实里:只有 websocket server 显式安装,其它 runtime 没有统一 bootstrap
88
+
89
+ 于是“基础设施未初始化”在业务路径中被表象成“Q4H 注册失败”。
90
+
91
+ ## Resolution Direction
92
+
93
+ 按下面原则修:
94
+
95
+ 1. 保持 event layer 的强约束:global-only event 没有 broadcaster 时仍然应 loud fail
96
+ 2. 修复点应放在 runtime bootstrap,而不是在 Q4H/子对话业务链路里做“广播失败 best-effort”
97
+ 3. 所有 runtime 入口都必须在业务逻辑前安装 broadcaster
98
+ - WebUI server:安装 websocket fanout broadcaster
99
+ - tests / script runtimes:安装 recording broadcaster,可抓取广播内容,也可在无断言需求时忽略
100
+ 4. 测试不得再通过业务 helper 临时塞 `() => {}` 绕过问题;应通过统一 runtime bootstrap 安装 recorder
101
+ 5. 若 future runtime 漏装 broadcaster,应在 bootstrap 阶段或运行环境初始化阶段尽早暴露,而不是等到 `askHuman` 中途再炸
102
+
103
+ ## Rejected Direction
104
+
105
+ 以下方向不再采用:
106
+
107
+ - 让 `dispatchGloballyIfNeeded()` graceful degrade
108
+ - 在 `askHuman` 链路里把 broadcast 当成 best-effort
109
+ - 允许测试/business helper 就地注入 noop broadcaster 掩盖 bootstrap 缺口
110
+
111
+ 这些做法都会继续模糊“mandatory infra”契约,让问题从 runtime 初始化阶段滑落到业务中途。
112
+
113
+ ## Applied Fix Direction
114
+
115
+ 当前约定下,正确修法是:
116
+
117
+ - 新增统一 broadcaster bootstrap API
118
+ - WebUI server 改为通过统一 bootstrap API 安装 websocket broadcaster
119
+ - rtws tests 改为通过统一 runner 安装 recording broadcaster
120
+ - kernel-driver helpers 不再偷偷安装 noop broadcaster,而是断言 runtime 已完成 bootstrap
121
+
122
+ ## Related Files
123
+
124
+ - `main/bootstrap/global-dialog-event-broadcaster.ts`
125
+ - `main/evt-registry.ts`
126
+ - `main/server/websocket-handler.ts`
127
+ - `tests/rtws-script-runner.ts`
128
+ - `tests/kernel-driver/helpers.ts`
@@ -0,0 +1,35 @@
1
+ # LLM Provider Isolation
2
+
3
+ ## Principle
4
+
5
+ Dominds treats each LLM provider wrapper as an isolated protocol adapter, not as a flavor of a shared "OpenAI-like" abstraction.
6
+
7
+ This means:
8
+
9
+ - `apiType: codex` owns Codex-native request fields, stream events, tool semantics, and defaults.
10
+ - `apiType: openai` owns OpenAI Responses request fields, stream events, tool semantics, and defaults.
11
+ - `apiType: openai-compatible` owns Chat Completions semantics, even when it reuses the `model_params.openai.*` namespace.
12
+
13
+ Similar field names across wrappers do not imply compatibility. For example, `reasoning_effort`, `verbosity`, `parallel_tool_calls`, and web search controls may look similar but can still differ in accepted values, payload shape, lifecycle events, validation rules, and runtime meaning.
14
+
15
+ ## Hard Rules
16
+
17
+ - A wrapper must only read its own provider namespace when building requests.
18
+ - A wrapper must only interpret its own provider-native stream events.
19
+ - A wrapper must not silently fall back to another provider's params, aliases, or event assumptions.
20
+ - Cross-provider convergence is allowed only at the driver/storage/UI boundary, after provider-native events have already been decoded into discriminated unions.
21
+
22
+ ## Why
23
+
24
+ This isolation keeps provider integrations honest:
25
+
26
+ - fewer accidental "compatible by coincidence" behaviors
27
+ - easier debugging when providers diverge
28
+ - less hidden coupling between wrappers
29
+ - safer upgrades when official APIs evolve independently
30
+
31
+ ## Current Boundary
32
+
33
+ The backend currently uses provider-specific web search event variants inside wrappers and projects them into a narrower dialog event shape in `main/llm/kernel-driver/drive.ts`.
34
+
35
+ That projection layer is intentional: it is the compatibility boundary. Wrapper code on either side should stay provider-native.
@@ -0,0 +1,35 @@
1
+ # LLM Provider 隔离原则
2
+
3
+ ## 原则
4
+
5
+ Dominds 把每个 LLM provider wrapper 视为独立的协议适配器,而不是某种“大一统 OpenAI-like 抽象”的一个变体。
6
+
7
+ 这意味着:
8
+
9
+ - `apiType: codex` 只负责 Codex 原生的请求字段、流事件、工具语义和默认行为。
10
+ - `apiType: openai` 只负责 OpenAI Responses 原生的请求字段、流事件、工具语义和默认行为。
11
+ - `apiType: openai-compatible` 虽然复用 `model_params.openai.*` 命名空间,但它负责的是 Chat Completions 语义,不是 Codex,也不是 Responses。
12
+
13
+ 不同 wrapper 下看起来同名的字段,不代表它们可以互相兼容。比如 `reasoning_effort`、`verbosity`、`parallel_tool_calls`、web search 相关开关,名字可能相似,但可接受值、请求载荷形状、流事件生命周期、校验规则和运行时含义都可能不同。
14
+
15
+ ## 强约束
16
+
17
+ - wrapper 构造请求时,只能读取自己的 provider 参数命名空间。
18
+ - wrapper 解析流事件时,只能解释自己的 provider 原生事件。
19
+ - wrapper 内禁止静默 fallback 到别的 provider 参数、别名或事件假设。
20
+ - 只有在 driver / storage / UI 边界,才允许把 provider-native 事件投影成更窄的共享形态,而且前提是 wrapper 侧已经先完成了 provider-native 解码。
21
+
22
+ ## 为什么
23
+
24
+ 这样做是为了让 provider 集成保持诚实:
25
+
26
+ - 减少“碰巧兼容”带来的隐式行为
27
+ - 在 provider 分叉时更容易排查问题
28
+ - 降低 wrapper 之间的隐藏耦合
29
+ - 在官方 API 各自演化时,升级更安全
30
+
31
+ ## 当前边界
32
+
33
+ 目前后端在 wrapper 内保留 provider-specific 的 web search 事件类型,再在 `main/llm/kernel-driver/drive.ts` 投影成较窄的 dialog 事件形态。
34
+
35
+ 这个投影层是有意设计的,它就是兼容边界。边界两侧的 wrapper 代码都应继续保持 provider-native。
@@ -1,4 +1,4 @@
1
- export type { ChatMessage, EnvironmentMsg, FuncCallMsg, FuncResultMsg, PromptingMsg, SayingMsg, TellaskCallResultMsg, TellaskCarryoverResultMsg, ThinkingMsg, TransientGuideMsg, UiOnlyMarkdownMsg, } from '@longrun-ai/kernel/types/chat-message';
1
+ export type { ChatMessage, EnvironmentMsg, FuncCallMsg, FuncResultMsg, PromptingMsg, SayingMsg, TellaskCarryoverMsg, TellaskResultMsg, ThinkingMsg, TransientGuideMsg, } from '@longrun-ai/kernel/types/chat-message';
2
2
  export interface ModelInfo {
3
3
  name?: string;
4
4
  context_length?: number;
@@ -52,6 +52,7 @@ export type ProviderApiType = 'codex' | 'anthropic' | 'mock' | 'openai' | 'opena
52
52
  export type ProviderConfig = {
53
53
  name: string;
54
54
  apiType: ProviderApiType;
55
+ apiQuirks?: string | string[];
55
56
  baseUrl: string;
56
57
  apiKeyEnvVar: string;
57
58
  tool_result_max_chars?: number;
@@ -4,6 +4,8 @@
4
4
  # - llm_retry_conservative_delay_ms: base delay for provider-classified conservative retries (default 30000; fixed for first 10 retries, then ramps by x1.5 with the normal max-delay cap).
5
5
  # - llm_retry_backoff_multiplier: exponential factor between retries (default 1.5).
6
6
  # - llm_retry_max_delay_ms: upper bound for retry delay (default 1800000 / 30m).
7
+ # - apiQuirks: optional provider/gateway quirk profile(s) for non-standard transport behavior
8
+ # and retry classification. Example: `apiQuirks: xcode.best`.
7
9
  # - tool_result_max_chars: optional transport-level cap for a single tool-result text payload
8
10
  # before Dominds projects it into the provider request. Use this when a provider/gateway enforces
9
11
  # a stricter per-item string limit than Dominds' built-in defaults.
@@ -27,6 +29,9 @@ providers:
27
29
  min: 1
28
30
  description: Maximum tokens to generate (provider-agnostic override).
29
31
  codex:
32
+ # Isolation principle:
33
+ # - These hints describe the Codex wrapper only.
34
+ # - Same-looking fields under `openai` are not aliases and are not fallback-compatible.
30
35
  reasoning_effort:
31
36
  prominent: true
32
37
  default: high
@@ -701,14 +706,23 @@ providers:
701
706
  apiKeyEnvVar: OPENAI_API_KEY
702
707
  tech_spec_url: https://platform.openai.com/docs
703
708
  api_mgmt_url: https://platform.openai.com/api-keys
709
+ # These are documentation hints for humans/agents; they are not auto-applied.
704
710
  model_param_options:
705
711
  general:
706
712
  max_tokens:
713
+ # Provider-agnostic cap on generated tokens.
707
714
  type: integer
708
715
  min: 1
709
716
  description: Maximum tokens to generate (provider-agnostic override).
710
717
  openai:
718
+ # Isolation principle:
719
+ # - These hints describe the OpenAI Responses wrapper only.
720
+ # - Same-looking fields under `codex` are not aliases and are not fallback-compatible.
711
721
  reasoning_effort:
722
+ prominent: true
723
+ default: high
724
+ # Trade latency/cost for quality on reasoning-capable models.
725
+ # Allowed: none | minimal | low | medium | high | xhigh
712
726
  type: enum
713
727
  values: [none, minimal, low, medium, high, xhigh]
714
728
  description: Reasoning effort level (when supported by the model).
@@ -717,36 +731,136 @@ providers:
717
731
  default: auto
718
732
  values: [auto, concise, detailed, none]
719
733
  description: Reasoning summary detail level (when supported by the model).
734
+ service_tier:
735
+ prominent: true
736
+ type: enum
737
+ values: [auto, default, flex, scale, priority]
738
+ description: Responses `service_tier` request parameter.
720
739
  verbosity:
740
+ prominent: true
741
+ default: medium
742
+ # Control response detail level on GPT-5 series models.
743
+ # Allowed: low | medium | high
721
744
  type: enum
722
745
  values: [low, medium, high]
723
746
  description: Response verbosity/detail level (GPT-5 series).
724
747
  temperature:
748
+ # Randomness; use 0–0.2 for tool-calling / deterministic behavior.
725
749
  type: number
726
750
  min: 0
727
751
  max: 2
728
752
  description: Sampling temperature (0–2).
729
753
  top_p:
754
+ # Nucleus sampling; usually leave unset if using temperature.
730
755
  type: number
731
756
  min: 0
732
757
  max: 1
733
758
  description: Nucleus sampling probability (0–1).
734
- json_response:
759
+ parallel_tool_calls:
735
760
  type: boolean
736
- description: Force JSON object output mode.
761
+ description: Responses `parallel_tool_calls` request parameter.
762
+ safety_identifier:
763
+ type: string
764
+ description: Responses `safety_identifier` request parameter.
765
+ text_format:
766
+ prominent: true
767
+ default: text
768
+ type: enum
769
+ values: [text, json_object, json_schema]
770
+ description: Responses `text.format.type`. Prefer `json_schema` over legacy `json_object`.
771
+ text_format_json_schema_name:
772
+ type: string
773
+ description: Required when `text_format=json_schema`; mapped to `text.format.name`.
774
+ text_format_json_schema:
775
+ type: string
776
+ description: Required when `text_format=json_schema`; JSON-encoded schema object mapped to `text.format.schema`.
777
+ text_format_json_schema_strict:
778
+ type: boolean
779
+ description: Optional `text.format.strict` when `text_format=json_schema`.
780
+ web_search_tool:
781
+ prominent: true
782
+ type: boolean
783
+ description: "Enable native Responses `tools: [{type:'web_search'}]`."
784
+ web_search_context_size:
785
+ type: enum
786
+ default: medium
787
+ values: [low, medium, high]
788
+ description: Native web_search `search_context_size`.
789
+ web_search_allowed_domains:
790
+ type: string_array
791
+ description: Native web_search `filters.allowed_domains`.
792
+ web_search_include_sources:
793
+ type: boolean
794
+ description: Include `web_search_call.action.sources` in the response payload.
737
795
  max_tokens:
738
796
  type: integer
739
797
  min: 1
740
798
  description: Provider-specific max tokens override.
741
799
  models:
742
- gpt-5.2:
743
- name: GPT-5.2
800
+ gpt-5.4:
801
+ name: GPT-5.4
802
+ optimal_max_tokens: 200000
803
+ # Caution remediation reinjection cadence in generation turns (default: 10).
804
+ caution_remediation_cadence_generations: 10
805
+ context_length: 272000
806
+ input_length: 272000
807
+ output_length: 32768
808
+ context_window: '272K'
809
+ gpt-5.4-mini:
810
+ name: GPT-5.4 Mini
811
+ optimal_max_tokens: 200000
812
+ # Caution remediation reinjection cadence in generation turns (default: 10).
813
+ caution_remediation_cadence_generations: 10
744
814
  context_length: 272000
745
815
  input_length: 272000
746
816
  output_length: 32768
747
817
  context_window: '272K'
818
+ gpt-5.3-codex:
819
+ name: GPT-5.3 Codex
820
+ optimal_max_tokens: 200000
821
+ # Caution remediation reinjection cadence in generation turns (default: 10).
822
+ caution_remediation_cadence_generations: 10
823
+ context_length: 272000
824
+ input_length: 272000
825
+ output_length: 32768
826
+ context_window: '272K'
827
+ gpt-5.3-codex-spark:
828
+ name: GPT-5.3 Codex Spark
829
+ optimal_max_tokens: 80000
830
+ # Caution remediation reinjection cadence in generation turns (default: 10).
831
+ caution_remediation_cadence_generations: 3
832
+ context_length: 128000
833
+ input_length: 128000
834
+ output_length: 32768
835
+ context_window: '128K'
748
836
  gpt-5.2-codex:
749
837
  name: GPT-5.2 Codex
838
+ optimal_max_tokens: 200000
839
+ # Caution remediation reinjection cadence in generation turns (default: 10).
840
+ caution_remediation_cadence_generations: 10
841
+ context_length: 272000
842
+ input_length: 272000
843
+ output_length: 32768
844
+ context_window: '272K'
845
+ gpt-5.2:
846
+ name: GPT-5.2
847
+ optimal_max_tokens: 200000
848
+ # Caution remediation reinjection cadence in generation turns (default: 10).
849
+ caution_remediation_cadence_generations: 10
850
+ context_length: 272000
851
+ input_length: 272000
852
+ output_length: 32768
853
+ context_window: '272K'
854
+ gpt-5.1-codex-mini:
855
+ name: GPT-5.1 Codex Mini
856
+ optimal_max_tokens: 200000
857
+ context_length: 272000
858
+ input_length: 272000
859
+ output_length: 32768
860
+ context_window: '272K'
861
+ gpt-5.1-codex-max:
862
+ name: GPT-5.1 Codex Max
863
+ optimal_max_tokens: 200000
750
864
  context_length: 272000
751
865
  input_length: 272000
752
866
  output_length: 32768
@@ -376,9 +376,7 @@ function chatMessageToContentBlocks(chatMsg) {
376
376
  return [block];
377
377
  }
378
378
  // Handle saying and thinking messages from assistant
379
- if (chatMsg.type === 'saying_msg' ||
380
- chatMsg.type === 'ui_only_markdown_msg' ||
381
- chatMsg.type === 'thinking_msg') {
379
+ if (chatMsg.type === 'saying_msg' || chatMsg.type === 'thinking_msg') {
382
380
  const block = { type: 'text', text: chatMsg.content };
383
381
  return [block];
384
382
  }
@@ -406,7 +404,7 @@ function chatMessageToContentBlocks(chatMsg) {
406
404
  return [block];
407
405
  }
408
406
  // Handle tellask call results (NOT LLM-native tool use; represented as role='user' text)
409
- if (chatMsg.type === 'tellask_result_msg' || chatMsg.type === 'tellask_carryover_result_msg') {
407
+ if (chatMsg.type === 'tellask_result_msg' || chatMsg.type === 'tellask_carryover_msg') {
410
408
  const msg = {
411
409
  type: 'text',
412
410
  text: chatMsg.content,
@@ -2,6 +2,8 @@
2
2
  * Module: llm/gen/codex
3
3
  *
4
4
  * ChatGPT Codex responses integration (streaming-only).
5
+ * Isolation principle: this wrapper owns Codex-native request/stream semantics and must not reuse
6
+ * OpenAI Responses parameter namespaces or event interpretations.
5
7
  */
6
8
  import type { ChatGptResponsesRequest } from '@longrun-ai/codex-auth';
7
9
  import type { Team } from '../../team';
@@ -9,6 +11,15 @@ import type { FuncTool } from '../../tool';
9
11
  import type { ChatMessage, ProviderConfig } from '../client';
10
12
  import type { LlmBatchResult, LlmFailureDisposition, LlmGenerator, LlmRequestContext, LlmStreamReceiver, LlmStreamResult } from '../gen';
11
13
  export declare function resolveCodexServiceTier(serviceTier: ChatGptResponsesRequest['service_tier'] | undefined): Exclude<NonNullable<ChatGptResponsesRequest['service_tier']>, 'default'> | undefined;
14
+ export declare function spliceCodexBuiltinPrompt(params: {
15
+ template: string;
16
+ defaultModel: string;
17
+ loadPrompt: (model: string) => string | null;
18
+ }): string;
19
+ export declare function resolveCodexInstructions(systemPrompt: string, options?: {
20
+ defaultModel?: string;
21
+ loadPrompt?: (model: string) => string | null;
22
+ }): string;
12
23
  export declare class CodexGen implements LlmGenerator {
13
24
  get apiType(): string;
14
25
  classifyFailure(error: unknown): LlmFailureDisposition | undefined;
@@ -35,6 +35,8 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.CodexGen = void 0;
37
37
  exports.resolveCodexServiceTier = resolveCodexServiceTier;
38
+ exports.spliceCodexBuiltinPrompt = spliceCodexBuiltinPrompt;
39
+ exports.resolveCodexInstructions = resolveCodexInstructions;
38
40
  const log_1 = require("../../log");
39
41
  const i18n_text_1 = require("../../runtime/i18n-text");
40
42
  const work_language_1 = require("../../runtime/work-language");
@@ -44,6 +46,7 @@ const tool_call_context_1 = require("./tool-call-context");
44
46
  const tool_output_limit_1 = require("./tool-output-limit");
45
47
  const log = (0, log_1.createLogger)('llm/codex');
46
48
  const codexFallbackInstructions = 'You are Codex CLI.';
49
+ const CODEX_SYSTEM_PROMPT_DIRECTIVE_PATTERN = /^([ \t]*)@codex-system-prompt(?::([A-Za-z0-9._-]+))?([ \t]*)$/gm;
47
50
  function resolveCodexServiceTier(serviceTier) {
48
51
  // The ChatGPT codex backend rejects the literal `default` tier even though some SDK typings
49
52
  // still list it. Omitting the field preserves the standard tier semantics without a 400.
@@ -112,19 +115,29 @@ function tryExtractApiReturnedModel(value) {
112
115
  const trimmed = model.trim();
113
116
  return trimmed.length > 0 ? trimmed : undefined;
114
117
  }
115
- async function resolveCodexInstructions(model, systemPrompt, loadPrompt) {
116
- const basePrompt = await loadPrompt(model);
117
- const trimmedSystemPrompt = systemPrompt.trim();
118
- if (!basePrompt) {
119
- return {
120
- instructions: trimmedSystemPrompt.length > 0 ? systemPrompt : codexFallbackInstructions,
121
- assistantPrelude: null,
122
- };
118
+ function spliceCodexBuiltinPrompt(params) {
119
+ let replaced = false;
120
+ const resolved = params.template.replace(CODEX_SYSTEM_PROMPT_DIRECTIVE_PATTERN, (_match, leading, overrideModel, trailing) => {
121
+ const selectedModel = overrideModel ?? params.defaultModel;
122
+ const prompt = params.loadPrompt(selectedModel);
123
+ if (prompt === null) {
124
+ throw new Error(`Bundled Codex prompt template not found for model: ${selectedModel}`);
125
+ }
126
+ replaced = true;
127
+ return `${leading}${prompt}${trailing}`;
128
+ });
129
+ return replaced ? resolved : params.template;
130
+ }
131
+ function resolveCodexInstructions(systemPrompt, options) {
132
+ const baseInstructions = systemPrompt.trim().length > 0 ? systemPrompt : codexFallbackInstructions;
133
+ if (options?.defaultModel === undefined || options.loadPrompt === undefined) {
134
+ return baseInstructions;
123
135
  }
124
- return {
125
- instructions: basePrompt,
126
- assistantPrelude: trimmedSystemPrompt.length > 0 ? trimmedSystemPrompt : null,
127
- };
136
+ return spliceCodexBuiltinPrompt({
137
+ template: baseInstructions,
138
+ defaultModel: options.defaultModel,
139
+ loadPrompt: options.loadPrompt,
140
+ });
128
141
  }
129
142
  function funcToolToCodex(funcTool) {
130
143
  // MCP schemas are passed through to providers. Codex tool schema types are narrower; runtime
@@ -145,16 +158,12 @@ const CODEX_JSON_RESPONSE_SCHEMA = {
145
158
  additionalProperties: true,
146
159
  };
147
160
  function resolveCodexWebSearchMode(agent) {
148
- const codexParams = agent.model_params?.codex ?? agent.model_params?.openai;
149
- return codexParams?.web_search ?? 'live';
161
+ return agent.model_params?.codex?.web_search ?? 'live';
150
162
  }
151
163
  function resolveCodexJsonResponseEnabled(agent) {
152
164
  const providerSpecific = agent.model_params?.codex?.json_response;
153
165
  if (providerSpecific !== undefined)
154
166
  return providerSpecific;
155
- const openAiSpecific = agent.model_params?.openai?.json_response;
156
- if (openAiSpecific !== undefined)
157
- return openAiSpecific;
158
167
  return agent.model_params?.json_response === true;
159
168
  }
160
169
  function buildCodexNativeTools(agent) {
@@ -168,7 +177,8 @@ function buildCodexNativeTools(agent) {
168
177
  return [webSearchTool];
169
178
  }
170
179
  function buildCodexTextControls(agent) {
171
- const codexParams = agent.model_params?.codex ?? agent.model_params?.openai;
180
+ // Provider isolation rule: the Codex wrapper only consumes `model_params.codex.*`.
181
+ const codexParams = agent.model_params?.codex;
172
182
  const text = {};
173
183
  if (codexParams && codexParams.verbosity) {
174
184
  text.verbosity = codexParams.verbosity;
@@ -184,7 +194,8 @@ function buildCodexTextControls(agent) {
184
194
  return Object.keys(text).length > 0 ? text : undefined;
185
195
  }
186
196
  function buildCodexReasoning(agent) {
187
- const codexParams = agent.model_params?.codex ?? agent.model_params?.openai;
197
+ // Provider isolation rule: do not borrow OpenAI Responses params inside the Codex wrapper.
198
+ const codexParams = agent.model_params?.codex;
188
199
  if (codexParams?.reasoning_effort === undefined && codexParams?.reasoning_summary === undefined) {
189
200
  return null;
190
201
  }
@@ -214,6 +225,7 @@ function assertNoCodexNativeToolCollisions(funcTools, nativeTools) {
214
225
  }
215
226
  function toLlmWebSearchCall(item, itemId, phase) {
216
227
  return {
228
+ source: 'codex',
217
229
  phase,
218
230
  itemId,
219
231
  status: item.status,
@@ -290,12 +302,11 @@ function chatMessageToCodexItems(msg) {
290
302
  return [messageItem('user', msg.content)];
291
303
  case 'transient_guide_msg':
292
304
  case 'saying_msg':
293
- case 'ui_only_markdown_msg':
294
305
  return [messageItem('assistant', msg.content)];
295
306
  case 'thinking_msg':
296
307
  return [thinkingMessageToCodexReasoningItem(msg)];
297
308
  case 'tellask_result_msg':
298
- case 'tellask_carryover_result_msg':
309
+ case 'tellask_carryover_msg':
299
310
  return [messageItem('user', msg.content)];
300
311
  case 'func_call_msg':
301
312
  return [
@@ -402,17 +413,13 @@ async function buildCodexInput(context, providerConfig) {
402
413
  }
403
414
  return input;
404
415
  }
405
- async function buildCodexRequest(providerConfig, agent, instructions, assistantPrelude, funcTools, requestContext, context) {
416
+ async function buildCodexRequest(providerConfig, agent, instructions, funcTools, requestContext, context) {
406
417
  if (!agent.model) {
407
418
  throw new Error(`Internal error: Model is undefined for agent '${agent.id}'`);
408
419
  }
409
- const input = [];
410
- if (assistantPrelude) {
411
- // Codex backend rejects system messages; pass extra instructions as prior assistant context.
412
- input.push(messageItem('assistant', assistantPrelude));
413
- }
414
- input.push(...(await buildCodexInput(context, providerConfig)));
415
- const codexParams = agent.model_params?.codex ?? agent.model_params?.openai;
420
+ const input = await buildCodexInput(context, providerConfig);
421
+ // Provider isolation rule: request construction must only read Codex-native params here.
422
+ const codexParams = agent.model_params?.codex;
416
423
  const parallelToolCalls = codexParams?.parallel_tool_calls ?? true;
417
424
  const reasoning = buildCodexReasoning(agent);
418
425
  const include = reasoning !== null ? ['reasoning.encrypted_content'] : [];
@@ -462,8 +469,11 @@ class CodexGen {
462
469
  if (!agent.model) {
463
470
  throw new Error(`Internal error: Model is undefined for agent '${agent.id}'`);
464
471
  }
465
- const resolvedInstructions = await resolveCodexInstructions(agent.model, systemPrompt, codexAuth.loadCodexPrompt);
466
- const payload = await buildCodexRequest(providerConfig, agent, resolvedInstructions.instructions, resolvedInstructions.assistantPrelude, funcTools, requestContext, context);
472
+ const instructions = resolveCodexInstructions(systemPrompt, {
473
+ defaultModel: agent.model,
474
+ loadPrompt: codexAuth.loadCodexPromptSync,
475
+ });
476
+ const payload = await buildCodexRequest(providerConfig, agent, instructions, funcTools, requestContext, context);
467
477
  let sayingStarted = false;
468
478
  let thinkingStarted = false;
469
479
  let sawOutputText = false;
@@ -115,6 +115,23 @@ function classifyOpenAiLikeFailure(error) {
115
115
  const lowerMessage = message.toLowerCase();
116
116
  const status = readErrorStatus(error);
117
117
  const code = readErrorCode(error);
118
+ if (code === 'OPENAI_MALFORMED_BATCH_OUTPUT_ITEM') {
119
+ return {
120
+ kind: 'fatal',
121
+ message,
122
+ status,
123
+ code,
124
+ };
125
+ }
126
+ if (code === 'XCODE_BEST_STREAM_INTERNAL_ERROR') {
127
+ return {
128
+ kind: 'retriable',
129
+ message,
130
+ status,
131
+ code,
132
+ retryStrategy: 'aggressive',
133
+ };
134
+ }
118
135
  if (status === 503 || status === 529 || isConservativeRetryMessage(lowerMessage)) {
119
136
  return {
120
137
  kind: 'retriable',