very-happy-cli 0.2.174 → 0.2.176

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 (179) hide show
  1. package/dist/{AcpBackend-MMfGUkmp.cjs → AcpBackend-9_ZI1Bv3.cjs} +3 -3
  2. package/dist/{AcpBackend-CS8TSaaH.mjs → AcpBackend-COp6D8gl.mjs} +3 -3
  3. package/dist/{AcpSessionManager-C2MqOirs.cjs → AcpSessionManager-CjdJLofq.cjs} +1 -1
  4. package/dist/{AcpSessionManager-y1Iy1mzc.mjs → AcpSessionManager-DykjzBM4.mjs} +1 -1
  5. package/dist/{BasePermissionHandler-BiWdGyL1.mjs → BasePermissionHandler-ChMkmsGD.mjs} +1 -1
  6. package/dist/{BasePermissionHandler-CJQPjMEa.cjs → BasePermissionHandler-DPvLN7aR.cjs} +1 -1
  7. package/dist/{BaseReasoningProcessor-BY9a3RTU.cjs → BaseReasoningProcessor-Dnf16mtJ.cjs} +1 -1
  8. package/dist/{BaseReasoningProcessor-2sLuoRYw.mjs → BaseReasoningProcessor-pjvLSTQF.mjs} +1 -1
  9. package/dist/{agentHome-BeAq0X3o.cjs → agentHome-BKi7tsE9.cjs} +4 -4
  10. package/dist/{agentHome-CK6SbAVu.mjs → agentHome-D61ZMLXM.mjs} +4 -4
  11. package/dist/{api-Cy_Mbe8W.cjs → api-Buc_ejoA.cjs} +10 -10
  12. package/dist/{api-rOLB1-sV.mjs → api-CbwDDXJt.mjs} +10 -10
  13. package/dist/{apiMachine-A9n14NN1.cjs → apiMachine--PXoXwKq.cjs} +14 -14
  14. package/dist/{apiMachine-VBkOChth.mjs → apiMachine-CS7t3Rg-.mjs} +15 -15
  15. package/dist/{attachmentContent-CSGb8uDJ.mjs → attachmentContent-BQYc8KpE.mjs} +2 -2
  16. package/dist/{attachmentContent-ULnsMsIL.cjs → attachmentContent-CFzzTeV9.cjs} +2 -2
  17. package/dist/{attachmentLimits-BLbji11b.mjs → attachmentLimits-Di1Z93lN.mjs} +1 -1
  18. package/dist/{attachmentLimits-D6bT2jU1.cjs → attachmentLimits-DtFqqiwR.cjs} +1 -1
  19. package/dist/{auth-CiZfkTkd.mjs → auth-B3xMoveE.mjs} +5 -5
  20. package/dist/{auth-EnJQu_vf.mjs → auth-CBkmRIGw.mjs} +8 -8
  21. package/dist/{auth-BD2z4MCH.cjs → auth-CpO3ZUMz.cjs} +5 -5
  22. package/dist/{auth--YSQ1-dG.cjs → auth-LPvsVriN.cjs} +8 -8
  23. package/dist/{auto-D_hKBFAY.mjs → auto-DHGch_zK.mjs} +16 -16
  24. package/dist/{auto-CobMGmFM.cjs → auto-Dm3EIKT7.cjs} +15 -15
  25. package/dist/{bootstrap-BYrt5zUv.cjs → bootstrap-BboDz_w2.cjs} +2 -2
  26. package/dist/{bootstrap-D1RRCJdQ.mjs → bootstrap-CxuBAkJU.mjs} +2 -2
  27. package/dist/{browser-IVfX6PQT.mjs → browser-BvARP-YR.mjs} +1 -1
  28. package/dist/{browser-i45hmvWw.cjs → browser-GQZJxD2n.cjs} +1 -1
  29. package/dist/{claudeLocal-DnPoeXP2.mjs → claudeLocal-B9EhFmD7.mjs} +6 -6
  30. package/dist/{claudeLocal-Cg8Qn4tF.cjs → claudeLocal-D-mujSX1.cjs} +5 -5
  31. package/dist/{claudeLocalLauncher-BAavVhU8.mjs → claudeLocalLauncher-Bdl-9aU_.mjs} +11 -11
  32. package/dist/{claudeLocalLauncher-CFlsscqX.cjs → claudeLocalLauncher-Tbrx6TXF.cjs} +10 -10
  33. package/dist/{claudeSessionFork-bp8LeRZ9.mjs → claudeSessionFork-D_DKvDKE.mjs} +1 -1
  34. package/dist/{claudeSessionFork-DmwPXMmz.cjs → claudeSessionFork-sOz97qqW.cjs} +1 -1
  35. package/dist/{client-DbqP92Fy.mjs → client-Cg6ABRe-.mjs} +3 -3
  36. package/dist/{client-QtWC3yii.cjs → client-D7sSU04O.cjs} +3 -3
  37. package/dist/{client-B6H6ZROd.cjs → client-D9pEOhn4.cjs} +2 -2
  38. package/dist/{client-Bi1DOVy9.mjs → client-Ryu9fllj.mjs} +2 -2
  39. package/dist/codex/happyMcpStdioBridge.cjs +15 -15
  40. package/dist/codex/happyMcpStdioBridge.mjs +15 -15
  41. package/dist/{codexAppServerClient-pnCpJlD8.mjs → codexAppServerClient-C70pJ-BB.mjs} +432 -48
  42. package/dist/{codexAppServerClient-BWB1267F.cjs → codexAppServerClient-DsIUkVnX.cjs} +428 -44
  43. package/dist/{codexCommand-u4m_X8VA.cjs → codexCommand-Aycux-B7.cjs} +162 -60
  44. package/dist/{codexCommand-BmtdCu-D.mjs → codexCommand-DWbA3pJN.mjs} +163 -61
  45. package/dist/{config-BaJCpLzN.cjs → config-BcL0bxO5.cjs} +3 -3
  46. package/dist/{config-Czc1tQDn.mjs → config-Bztmzu8z.mjs} +3 -3
  47. package/dist/{configuration-zOfO2qdB.mjs → configuration-BAwOtWFY.mjs} +1 -1
  48. package/dist/{configuration-Btwt68O2.cjs → configuration-CpBrilxV.cjs} +1 -1
  49. package/dist/{connect-DQaDa6i7.cjs → connect-BBNCMTi8.cjs} +13 -13
  50. package/dist/{connect-D21OhdsD.mjs → connect-BeWQ4MMA.mjs} +13 -13
  51. package/dist/{context-BknomZTq.cjs → context-Hk0j2Y9-.cjs} +1 -1
  52. package/dist/{context-BTX3YYdt.mjs → context-j114QJre.mjs} +1 -1
  53. package/dist/{controlClient-CbmWgWJW.mjs → controlClient-00KYAjsI.mjs} +3 -3
  54. package/dist/{controlClient-CEpaptre.cjs → controlClient-bs_YqwDl.cjs} +3 -3
  55. package/dist/{createSessionMetadata-Cgfcg0WZ.cjs → createSessionMetadata-D4Sj6H95.cjs} +2 -2
  56. package/dist/{createSessionMetadata-CM7ErnPh.mjs → createSessionMetadata-xlycGog5.mjs} +2 -2
  57. package/dist/{defaultPermissionMode-CmgCDVpL.mjs → defaultPermissionMode-B97ox9p1.mjs} +2 -2
  58. package/dist/{defaultPermissionMode-DqdkzYUY.cjs → defaultPermissionMode-CMsiaRzr.cjs} +2 -2
  59. package/dist/{deterministicJson-B3V5Ss2l.cjs → deterministicJson-aHxtYLha.cjs} +9 -9
  60. package/dist/{deterministicJson-DVvfcibH.mjs → deterministicJson-ucyv8j6b.mjs} +9 -9
  61. package/dist/{doctor-C_Xfs8Bs.cjs → doctor-BXkNXcok.cjs} +7 -7
  62. package/dist/{doctor-CidhGBde.mjs → doctor-D554c6Bg.mjs} +7 -7
  63. package/dist/{downloadTarget-C-18yOBz.mjs → downloadTarget-2_zMvt2N.mjs} +6 -6
  64. package/dist/{downloadTarget-DJKG6wVj.cjs → downloadTarget-CmdlljZW.cjs} +6 -6
  65. package/dist/{ensureDaemonRunning-Dk1yo6aK.cjs → ensureDaemonRunning-CY7iC--U.cjs} +5 -5
  66. package/dist/{ensureDaemonRunning-CWo9yDAC.mjs → ensureDaemonRunning-DJlziY-m.mjs} +5 -5
  67. package/dist/{handleResumeCommand-ApqydtLf.mjs → handleResumeCommand-CNTnXBXA.mjs} +5 -5
  68. package/dist/{handleResumeCommand-DvFAI68O.cjs → handleResumeCommand-lSVnvTNC.cjs} +5 -5
  69. package/dist/{index-D0IWz-WE.mjs → index-59hR7nVz.mjs} +3 -3
  70. package/dist/{index-CMeZ7WcS.mjs → index-CxSLrTPA.mjs} +4 -4
  71. package/dist/{index-boE-d8CM.mjs → index-DMy3OCBe.mjs} +8 -1
  72. package/dist/{index-BVC5vBcc.mjs → index-DY3I8j0W.mjs} +37 -37
  73. package/dist/{index-BZdhwBKa.cjs → index-De8L5lFK.cjs} +8 -0
  74. package/dist/{index-azi2eHdp.cjs → index-DhARRePz.cjs} +4 -4
  75. package/dist/{index-CjFzrcin.cjs → index-MwTVbeGW.cjs} +3 -3
  76. package/dist/{index-CXCoTvVd.cjs → index-jeD70GGH.cjs} +36 -36
  77. package/dist/index.cjs +2 -2
  78. package/dist/index.mjs +2 -2
  79. package/dist/{install-DayuanHz.cjs → install-DXUEtGfU.cjs} +3 -3
  80. package/dist/{install-BOPmEeSo.mjs → install-DytIJ_Ia.mjs} +3 -3
  81. package/dist/lib.cjs +12 -12
  82. package/dist/lib.mjs +12 -12
  83. package/dist/{lidState-Bv862v_R.mjs → lidState-CduPmrM0.mjs} +1 -1
  84. package/dist/{lidState-7uLoa43a.cjs → lidState-UasMvPzw.cjs} +1 -1
  85. package/dist/{localHappyAgentAuth-CfILduPd.cjs → localHappyAgentAuth-CA02WOny.cjs} +1 -1
  86. package/dist/{localHappyAgentAuth-CLP3CBMK.mjs → localHappyAgentAuth-DL8JjHTV.mjs} +1 -1
  87. package/dist/{logger-CUxbqfrp.mjs → logger-CihCDWDw.mjs} +2 -2
  88. package/dist/{logger-XjIS0lU7.cjs → logger-zoO54Zsy.cjs} +2 -2
  89. package/dist/{machineMetadata-DTxYqMdE.cjs → machineMetadata-BAKVtr-k.cjs} +3 -3
  90. package/dist/{machineMetadata-QfdvKIrL.mjs → machineMetadata-DEZcQ_CQ.mjs} +3 -3
  91. package/dist/{main-BpC52KlL.cjs → main-BIvxyX71.cjs} +44 -44
  92. package/dist/{main-DcfU9Axf.mjs → main-DyJfNlL_.mjs} +44 -44
  93. package/dist/{manager-BEsI4qt3.mjs → manager-2Il8XJVq.mjs} +1 -1
  94. package/dist/{mcp-EEwHzYYU.cjs → mcp-Dd11vHij.cjs} +14 -14
  95. package/dist/{mcp-BhUNcpoX.mjs → mcp-yoIdGe6C.mjs} +14 -14
  96. package/dist/{mirrorProtocol-BoBu65cj.mjs → mirrorProtocol-BukXB5s3.mjs} +3 -3
  97. package/dist/{mirrorProtocol-WfE5yd3v.cjs → mirrorProtocol-D-hF_SE5.cjs} +3 -3
  98. package/dist/{package-DFQUF2in.cjs → package-A7zOGx8B.cjs} +7 -7
  99. package/dist/{package-DkI3yDe0.mjs → package-ayqGDKSp.mjs} +7 -7
  100. package/dist/{peerTools-zfe59JAw.mjs → peerTools-DDMSViPu.mjs} +4 -4
  101. package/dist/{peerTools-DbI6tEk1.cjs → peerTools-dPc9hRC5.cjs} +4 -4
  102. package/dist/{persistence-BO7AS2NK.cjs → persistence-Cih2AWml.cjs} +2 -2
  103. package/dist/{persistence-CBToZ0R6.mjs → persistence-Dy3lmall.mjs} +2 -2
  104. package/dist/{piRunnerArgs-CICVlVem.cjs → piRunnerArgs-BuJgnUI5.cjs} +1 -1
  105. package/dist/{piRunnerArgs-C2us7__q.mjs → piRunnerArgs-D3z3-1VJ.mjs} +1 -1
  106. package/dist/{piRuntime-CTezrTNw.cjs → piRuntime-B-SPcWIr.cjs} +1 -1
  107. package/dist/{piRuntime-BYHZYbw6.mjs → piRuntime-kVGue-_5.mjs} +1 -1
  108. package/dist/{piTerminal-CeiWEB-T.cjs → piTerminal-DSxK5aAm.cjs} +16 -16
  109. package/dist/{piTerminal-YCRkoVWF.mjs → piTerminal-aEtVdCJ2.mjs} +16 -16
  110. package/dist/{rateLimitWindows-U7NsRvIa.cjs → rateLimitWindows-82izLQy8.cjs} +1 -1
  111. package/dist/{rateLimitWindows-i2-530Zt.mjs → rateLimitWindows-E5DRjfE6.mjs} +1 -1
  112. package/dist/{registerCommonHandlers-C_KbP5Mq.mjs → registerCommonHandlers-Bm7CMqMv.mjs} +2 -2
  113. package/dist/{registerCommonHandlers-DTeuk3Hw.cjs → registerCommonHandlers-CqO075-X.cjs} +2 -2
  114. package/dist/{registerKillSessionHandler-B7zpuzvS.mjs → registerKillSessionHandler-D02OY4vi.mjs} +6 -6
  115. package/dist/{registerKillSessionHandler-B_FqygY4.cjs → registerKillSessionHandler-WQra3T9B.cjs} +6 -6
  116. package/dist/{remoteSessionClient-Bk1Qzb5I.mjs → remoteSessionClient-Buh6NejV.mjs} +5 -5
  117. package/dist/{remoteSessionClient-D79ci1L8.cjs → remoteSessionClient-f3DYorl8.cjs} +5 -5
  118. package/dist/{reportProgressTool-oJMMqzKb.cjs → reportProgressTool-C2gn_HWD.cjs} +2 -2
  119. package/dist/{reportProgressTool-DujvMncK.mjs → reportProgressTool-C5aSoKYX.mjs} +2 -2
  120. package/dist/{run-CyiUjBZK.cjs → run-CDEmq6Zs.cjs} +34 -34
  121. package/dist/{run-DVE2Gq-E.mjs → run-Drxogp0j.mjs} +33 -33
  122. package/dist/{runClaude-BcpBew2h.mjs → runClaude-BF6U2Js7.mjs} +21 -21
  123. package/dist/{runClaude-CmAHAR3N.cjs → runClaude-yEcZTCt_.cjs} +21 -21
  124. package/dist/{runClaudeDeps-C2fTmXoo.mjs → runClaudeDeps-Bl4ndq7N.mjs} +40 -40
  125. package/dist/{runClaudeDeps-Z_RVvcuG.cjs → runClaudeDeps-CnNNFjlI.cjs} +35 -35
  126. package/dist/{runGemini-BYmVfC3b.mjs → runGemini-C3wt7iv7.mjs} +32 -32
  127. package/dist/{runGemini-Bv4nVBfj.cjs → runGemini-DDVDRNg4.cjs} +32 -32
  128. package/dist/{runOpenClaw-D5suaSgY.cjs → runOpenClaw-BzlVA_9Q.cjs} +18 -18
  129. package/dist/{runOpenClaw-msp2nzW8.mjs → runOpenClaw-CY6YHAvj.mjs} +18 -18
  130. package/dist/{sandbox-CSnsdXId.cjs → sandbox-0UNHR3hP.cjs} +4 -4
  131. package/dist/{sandbox-CH7xLX1-.mjs → sandbox-CX_wLwpq.mjs} +4 -4
  132. package/dist/selfCheck-ClVv8NK3.mjs +84 -0
  133. package/dist/{selfCheck-B4mCW5Em.cjs → selfCheck-FNwMFY8p.cjs} +34 -34
  134. package/dist/{send-DzRwXl8a.cjs → send-BQkl25g0.cjs} +8 -8
  135. package/dist/{send-lrV7t7dr.mjs → send-CpEGYict.mjs} +8 -8
  136. package/dist/{server-BAwRC79P.cjs → server-BodFZ5sW.cjs} +6 -6
  137. package/dist/{server-DBHPW2gs.mjs → server-DnINX_Lm.mjs} +4 -4
  138. package/dist/{sessionLock-ClIuWFru.cjs → sessionLock-DOj6mMXn.cjs} +3 -3
  139. package/dist/{sessionLock-D_-T15Y-.mjs → sessionLock-Ddn-fhZ0.mjs} +3 -3
  140. package/dist/{sessionModeFile-BP1s4On2.mjs → sessionModeFile-DDbonFR_.mjs} +1 -1
  141. package/dist/{sessionModeFile-BFejyKn5.cjs → sessionModeFile-DVukoSp7.cjs} +1 -1
  142. package/dist/{sessionOps-DfAtoDD5.mjs → sessionOps-B1vQ25GS.mjs} +4 -4
  143. package/dist/{sessionOps-C7wI8Ll6.cjs → sessionOps-Nryt1DZt.cjs} +4 -4
  144. package/dist/{sessionScanner-BJFPUwar.cjs → sessionScanner-D1h-cdte.cjs} +3 -3
  145. package/dist/{sessionScanner-1-ANJdrm.mjs → sessionScanner-DCr3ofhR.mjs} +3 -3
  146. package/dist/{sessions-Bi13Rn0K.cjs → sessions-D2HsTbLf.cjs} +9 -9
  147. package/dist/{sessions-Dp_X8vP2.mjs → sessions-DpAN6Ft9.mjs} +9 -9
  148. package/dist/{setupOfflineReconnection-BJCXuFad.cjs → setupOfflineReconnection-B9Tw1Ouj.cjs} +2 -2
  149. package/dist/{setupOfflineReconnection-D51KXWAP.mjs → setupOfflineReconnection-DQHEp330.mjs} +2 -2
  150. package/dist/{spawn-DeXPOMMC.mjs → spawn-CNjm1yOi.mjs} +14 -14
  151. package/dist/{spawn-BiYYv0J7.cjs → spawn-CvDZ38hi.cjs} +13 -13
  152. package/dist/{spawnAgents-BhsbJ253.mjs → spawnAgents-BjtmjBvP.mjs} +1 -1
  153. package/dist/{spawnAgents-CgMKuOhZ.cjs → spawnAgents-CCjJifAW.cjs} +1 -1
  154. package/dist/{spawnHappyCLI-CYS23qZY.mjs → spawnHappyCLI-C8Vl56wB.mjs} +1 -1
  155. package/dist/{spawnHappyCLI-CrKdy_rv.cjs → spawnHappyCLI-ahi4j6Mh.cjs} +1 -1
  156. package/dist/{specialCommands-Dkg5wrXd.cjs → specialCommands-BpqnGrOP.cjs} +3 -3
  157. package/dist/{specialCommands-BbNlcS3G.mjs → specialCommands-DfVMGXTl.mjs} +3 -3
  158. package/dist/{startFileWatcher-CgH0PknF.mjs → startFileWatcher-DXJXY4AE.mjs} +1 -1
  159. package/dist/{startFileWatcher-BJ4vTfRp.cjs → startFileWatcher-Ddn3L0u3.cjs} +1 -1
  160. package/dist/{streamRelay-DqPFsCgx.cjs → streamRelay-Cx01enfe.cjs} +1 -1
  161. package/dist/{streamRelay-C8zI7OnR.mjs → streamRelay-_qCd873P.mjs} +1 -1
  162. package/dist/{sync-MTpgBfV2.mjs → sync-BKvgkhPs.mjs} +1 -1
  163. package/dist/{sync-CGVTRg-R.cjs → sync-BgZuthoS.cjs} +1 -1
  164. package/dist/{systemPrompt-DRCJdhNo.cjs → systemPrompt-C6O1cHUg.cjs} +2 -2
  165. package/dist/{systemPrompt-BMGFnaRU.mjs → systemPrompt-DuPem6c9.mjs} +2 -2
  166. package/dist/{teams-H_F3UZkh.mjs → teams-CffMBBfU.mjs} +6 -6
  167. package/dist/{teams-0d4XG2_h.cjs → teams-DifEhhmk.cjs} +6 -6
  168. package/dist/{time-7iVn8KLQ.mjs → time-DxCTiteK.mjs} +1 -1
  169. package/dist/{time-ChuhR6SU.cjs → time-s8CaneqW.cjs} +1 -1
  170. package/dist/{titleGenerator-CLdAiwN1.mjs → titleGenerator-B5T7QSzu.mjs} +1 -1
  171. package/dist/{titleGenerator-CozWnU-t.cjs → titleGenerator-CXr7t6cf.cjs} +2 -2
  172. package/dist/{todo-BeIYBXws.cjs → todo-B41GKMcI.cjs} +6 -6
  173. package/dist/{todo-7mtNKrxn.mjs → todo-DNO7s8td.mjs} +6 -6
  174. package/dist/{tools-BSlqfWAq.mjs → tools-697R1uqn.mjs} +1 -1
  175. package/dist/{tools-9hPR5-u8.cjs → tools-lotMyxIE.cjs} +1 -1
  176. package/dist/{uninstall-Bf5MardO.cjs → uninstall-CRu96len.cjs} +3 -3
  177. package/dist/{uninstall-Uei9RnVT.mjs → uninstall-CYO7EDjO.mjs} +3 -3
  178. package/package.json +7 -7
  179. package/dist/selfCheck-WbfDqlfQ.mjs +0 -84
@@ -3,12 +3,100 @@
3
3
  var node_child_process = require('node:child_process');
4
4
  var spawn = require('cross-spawn');
5
5
  var node_readline = require('node:readline');
6
- var logger = require('./logger-XjIS0lU7.cjs');
6
+ var logger = require('./logger-zoO54Zsy.cjs');
7
7
  var contentLogMetadata = require('./contentLogMetadata-ZSFk_NjQ.cjs');
8
8
  var manager = require('./manager-HrEj442o.cjs');
9
- var _package = require('./package-DFQUF2in.cjs');
10
- var rateLimitWindows = require('./rateLimitWindows-U7NsRvIa.cjs');
9
+ var _package = require('./package-A7zOGx8B.cjs');
10
+ var rateLimitWindows = require('./rateLimitWindows-82izLQy8.cjs');
11
11
 
12
+ const CODEX_HELPER_LABEL_MAX_CHARS = 80;
13
+ function codexHelperLabel(prompt) {
14
+ if (typeof prompt !== "string") return null;
15
+ const collapsed = prompt.replace(/\s+/g, " ").trim();
16
+ if (collapsed.length === 0) return null;
17
+ if (collapsed.length <= CODEX_HELPER_LABEL_MAX_CHARS) return collapsed;
18
+ const keep = CODEX_HELPER_LABEL_MAX_CHARS - 1;
19
+ const last = collapsed.charCodeAt(keep - 1);
20
+ const end = last >= 55296 && last <= 56319 ? keep - 1 : keep;
21
+ return `${collapsed.slice(0, end)}\u2026`;
22
+ }
23
+ function codexAgentPathSegments(agentPath) {
24
+ if (typeof agentPath !== "string") return [];
25
+ return agentPath.split("/").map((segment) => segment.trim()).filter((segment) => segment.length > 0);
26
+ }
27
+ function isRootAgentPath(segments) {
28
+ return segments.length === 1 && segments[0] === "root";
29
+ }
30
+ function codexChildAnnouncements(item) {
31
+ if (item.type === "subAgentActivity") {
32
+ const threadId = item.agentThreadId;
33
+ if (typeof threadId !== "string" || threadId.length === 0) return [];
34
+ const segments = codexAgentPathSegments(item.agentPath);
35
+ if (isRootAgentPath(segments)) return [];
36
+ const leaf = segments.at(-1);
37
+ return [{ threadId, label: leaf && leaf.length > 0 ? leaf : null }];
38
+ }
39
+ if (item.type === "collabAgentToolCall") {
40
+ const receivers = Array.isArray(item.receiverThreadIds) ? item.receiverThreadIds.filter((id) => typeof id === "string" && id.length > 0) : [];
41
+ const label = item.tool === "spawnAgent" ? codexHelperLabel(item.prompt) : null;
42
+ return [...new Set(receivers)].map((threadId) => ({ threadId, label }));
43
+ }
44
+ return [];
45
+ }
46
+
47
+ function stopCodexProcess(proc, deps = {}) {
48
+ const platform = deps.platform ?? process.platform;
49
+ try {
50
+ proc.stdin?.end();
51
+ } catch {
52
+ }
53
+ if (platform === "win32") return;
54
+ try {
55
+ proc.kill("SIGTERM");
56
+ } catch {
57
+ }
58
+ }
59
+ function killCodexProcessTree(proc, deps = {}) {
60
+ if (proc.exitCode != null || proc.signalCode != null) return;
61
+ const killRoot = () => {
62
+ try {
63
+ proc.kill("SIGKILL");
64
+ } catch {
65
+ }
66
+ };
67
+ const pid = proc.pid;
68
+ if (!pid || !("exitCode" in proc)) {
69
+ killRoot();
70
+ return;
71
+ }
72
+ const platform = deps.platform ?? process.platform;
73
+ const spawn = deps.spawn ?? ((command, args, options) => node_child_process.spawn(command, args, options));
74
+ if (platform === "win32") {
75
+ try {
76
+ const killer = spawn("taskkill", ["/pid", String(pid), "/t", "/f"], { stdio: "ignore", windowsHide: true });
77
+ killer.on("error", killRoot);
78
+ killer.on("exit", (code) => {
79
+ if (code !== 0) killRoot();
80
+ });
81
+ killer.unref();
82
+ } catch {
83
+ killRoot();
84
+ }
85
+ return;
86
+ }
87
+ try {
88
+ const reaper = spawn("pkill", ["-KILL", "-P", String(pid)], { stdio: "ignore", windowsHide: true });
89
+ reaper.on("error", () => void 0);
90
+ reaper.unref();
91
+ } catch {
92
+ }
93
+ killRoot();
94
+ }
95
+
96
+ function jsonRpcErrorCodeOf(error) {
97
+ const code = error?.jsonRpcCode;
98
+ return typeof code === "number" ? code : null;
99
+ }
12
100
  function isAppServerAvailable() {
13
101
  try {
14
102
  const version = node_child_process.execSync("codex --version", { encoding: "utf8", windowsHide: true }).trim();
@@ -91,7 +179,6 @@ class CodexAppServerClient {
91
179
  _turnId = null;
92
180
  threadDefaults = null;
93
181
  // Turn completion tracking for the currently active sendTurnAndWait call.
94
- // A completion event only resolves once we have seen task_started for this turn.
95
182
  pendingTurnCompletion = null;
96
183
  // Tracks in-flight interruptTurn() RPCs so sendTurnAndWait can wait for them
97
184
  // before starting a new turn (prevents stale turn/interrupt from aborting the next turn).
@@ -101,6 +188,22 @@ class CodexAppServerClient {
101
188
  idleRelease = null;
102
189
  notificationProtocol = "unknown";
103
190
  completedTurnIds = /* @__PURE__ */ new Set();
191
+ /** B-625: turns whose failure has already been forwarded, so a second failed frame is not. */
192
+ failedTurnIds = /* @__PURE__ */ new Set();
193
+ // B-625: the app-server speaks for every thread it hosts — the session's own and the
194
+ // children `spawn_agent` opens — so a lifecycle frame (turn/started, turn/completed,
195
+ // thread/status/changed, error, thread/closed) is the session's only when its `threadId`
196
+ // is ours; a frame naming no thread belongs to the thread it was asked on (Orca's rule,
197
+ // src/main/codex/codex-structured-turn-open-wait.ts). Before this, a child's
198
+ // `turn/completed` ended the parent's turn in the UI and a child's `turn/started`
199
+ // retargeted `turn/interrupt` at the child.
200
+ /** The own thread's last turn-ending `error` (willRetry=false), for a turn end that arrives without a verdict. */
201
+ lastTurnError = null;
202
+ /** Own thread reported idle/systemError while a turn was pending: Codex's `turn/completed`
203
+ * follows within a millisecond; this ends the turn if it does not (see armThreadStopGrace). */
204
+ threadStopGrace = null;
205
+ /** Last few hundred chars of the app-server's stderr: Codex's own last words when it dies. */
206
+ stderrRing = "";
104
207
  rawFileChangesByItemId = /* @__PURE__ */ new Map();
105
208
  // B-622: inactivity watchdog for the pending turn — re-armed by every
106
209
  // notification, never a cap on how long a turn may run (see sendTurnAndWait).
@@ -108,6 +211,18 @@ class CodexAppServerClient {
108
211
  // Approval / elicitation requests we are still answering: a human may take
109
212
  // longer than the watchdog to decide, and that is not Codex going silent.
110
213
  inflightServerRequests = 0;
214
+ // B-628: `turn/steer` — a message sent into the running turn (approach from Orca,
215
+ // src/main/codex/codex-structured-turn-start.ts). This app-server has no such
216
+ // method (-32601) until it is respawned; a refusal is per call (the turn it named
217
+ // ended or changed meanwhile, a review/compact turn).
218
+ steerUnsupported = false;
219
+ /** Steered inputs Codex has not echoed back as a `userMessage` item yet: clientId → turnId. */
220
+ steeredAwaitingEcho = /* @__PURE__ */ new Map();
221
+ /** Waiters for the pending turn to open (steerTurn); released when it opens or ends. */
222
+ turnOpenWaiters = [];
223
+ /** B-628: the names Codex gave the child threads it spawned (see codexChildThreads.ts). */
224
+ childThreadLabels = /* @__PURE__ */ new Map();
225
+ static MAX_CHILD_LABELS = 128;
111
226
  // Handlers set by the consumer (runCodex.ts)
112
227
  eventHandler = null;
113
228
  approvalHandler = null;
@@ -169,8 +284,73 @@ class CodexAppServerClient {
169
284
  const status = params?.turn?.status ?? params?.status ?? null;
170
285
  return typeof status === "string" && status.length > 0 ? status : null;
171
286
  }
287
+ extractThreadId(params) {
288
+ const threadId = params?.threadId ?? params?.thread_id ?? params?.thread?.id ?? null;
289
+ return typeof threadId === "string" && threadId.length > 0 ? threadId : null;
290
+ }
291
+ /** B-625: whether a lifecycle frame is about the session's own thread (see the field comment). */
292
+ isOwnThreadFrame(params) {
293
+ const threadId = this.extractThreadId(params);
294
+ return threadId === null || this._threadId === null || threadId === this._threadId;
295
+ }
296
+ /** B-628: the child thread a frame belongs to, or null for the session's own. */
297
+ childThreadIdOf(params) {
298
+ return this.isOwnThreadFrame(params) ? null : this.extractThreadId(params);
299
+ }
300
+ /**
301
+ * B-628: what a child thread's item carries so the mapper files it under the child's
302
+ * card (`subagent` → the session protocol's sub-agent envelope, as a Claude Task's
303
+ * children are) instead of in the main conversation as the session's own work.
304
+ */
305
+ childItemFields(params) {
306
+ const child = this.childThreadIdOf(params);
307
+ if (!child) return {};
308
+ const label = this.childThreadLabels.get(child);
309
+ return { subagent: child, ...label ? { subagent_label: label } : {} };
310
+ }
311
+ rememberChildLabels(item) {
312
+ for (const { threadId, label } of codexChildAnnouncements(item)) {
313
+ if (!label || this.childThreadLabels.has(threadId)) continue;
314
+ if (this.childThreadLabels.size >= CodexAppServerClient.MAX_CHILD_LABELS) {
315
+ const oldest = this.childThreadLabels.keys().next().value;
316
+ if (oldest !== void 0) this.childThreadLabels.delete(oldest);
317
+ }
318
+ this.childThreadLabels.set(threadId, label);
319
+ }
320
+ }
321
+ /**
322
+ * B-628: steered inputs the ended turn never echoed. `report` = the turn did not complete
323
+ * (interrupted, failed, app-server gone, given up): Codex never read them, tell the runner.
324
+ * A turn that *completed* drains its pending input before it ends (core/src/session/turn.rs),
325
+ * so an unechoed input there is a codex that accepted the steer but echoes no `clientId` —
326
+ * forgotten silently, because a false「not delivered」on a message the model did read would
327
+ * be worse than today's silence.
328
+ */
329
+ dropUnechoedSteers(turnId, report) {
330
+ const dropped = [];
331
+ for (const [clientId, steeredTurn] of this.steeredAwaitingEcho) {
332
+ if (turnId === null || steeredTurn === turnId) {
333
+ dropped.push(clientId);
334
+ this.steeredAwaitingEcho.delete(clientId);
335
+ }
336
+ }
337
+ if (dropped.length === 0) return;
338
+ if (!report) {
339
+ logger.logger.debug(`[CodexAppServer] ${dropped.length} steered message(s) completed without an echo; not reported`);
340
+ return;
341
+ }
342
+ this.eventHandler?.({
343
+ type: "steer_dropped",
344
+ client_ids: dropped,
345
+ ...turnId ? { turn_id: turnId } : {}
346
+ });
347
+ }
348
+ /** Codex's last stderr lines, one line, for a message a person reads. */
349
+ stderrTail() {
350
+ return this.stderrRing.replace(/\x1b\[[0-9;]*m/g, "").replace(/\s+/g, " ").trim();
351
+ }
172
352
  shouldHandleRawNotification(method) {
173
- const isRawNotification = method === "thread/started" || method === "turn/started" || method === "turn/completed" || method === "thread/status/changed" || method === "thread/tokenUsage/updated" || method.startsWith("item/");
353
+ const isRawNotification = method === "thread/started" || method === "turn/started" || method === "turn/completed" || method === "thread/status/changed" || method === "thread/closed" || method === "thread/tokenUsage/updated" || method.startsWith("item/");
174
354
  if (!isRawNotification) {
175
355
  return false;
176
356
  }
@@ -185,15 +365,21 @@ class CodexAppServerClient {
185
365
  emitRawTurnCompletion(turnId, status, error, source) {
186
366
  const aborted = status === "cancelled" || status === "canceled" || status === "aborted" || status === "interrupted";
187
367
  const failed = status === "failed" || error !== void 0 && error !== null;
368
+ this.clearThreadStopGrace();
188
369
  this.tryResolvePendingTurn(aborted, turnId, source);
189
370
  this._turnId = null;
190
371
  this.maybeRefreshRateLimits();
372
+ if (this.lastTurnError && (!turnId || !this.lastTurnError.turnId || this.lastTurnError.turnId === turnId)) {
373
+ this.lastTurnError = null;
374
+ }
191
375
  if (turnId && this.completedTurnIds.has(turnId)) {
192
- if (!failed) return;
376
+ if (!failed || this.failedTurnIds.has(turnId)) return;
193
377
  }
194
378
  if (turnId) {
195
379
  this.completedTurnIds.add(turnId);
380
+ if (failed) this.failedTurnIds.add(turnId);
196
381
  }
382
+ this.dropUnechoedSteers(turnId, aborted || failed);
197
383
  if (aborted) {
198
384
  this.eventHandler?.({
199
385
  type: "turn_aborted",
@@ -215,6 +401,10 @@ class CodexAppServerClient {
215
401
  return false;
216
402
  }
217
403
  if (method === "turn/started") {
404
+ if (!this.isOwnThreadFrame(params)) {
405
+ logger.logger.debug(`[CodexAppServer] Child thread ${this.extractThreadId(params)} started a turn`);
406
+ return true;
407
+ }
218
408
  const turnId = this.extractTurnId(params);
219
409
  if (turnId) {
220
410
  this._turnId = turnId;
@@ -227,6 +417,15 @@ class CodexAppServerClient {
227
417
  return true;
228
418
  }
229
419
  if (method === "turn/completed") {
420
+ if (!this.isOwnThreadFrame(params)) {
421
+ logger.logger.debug(`[CodexAppServer] Child thread ${this.extractThreadId(params)} ended a turn`);
422
+ this.eventHandler?.({
423
+ type: "subagent_turn_end",
424
+ ...this.childItemFields(params),
425
+ status: this.extractTurnStatus(params)
426
+ });
427
+ return true;
428
+ }
230
429
  this.emitRawTurnCompletion(
231
430
  this.extractTurnId(params),
232
431
  this.extractTurnStatus(params),
@@ -236,13 +435,28 @@ class CodexAppServerClient {
236
435
  return true;
237
436
  }
238
437
  if (method === "thread/status/changed") {
438
+ if (!this.isOwnThreadFrame(params)) return true;
239
439
  const statusType = params?.status?.type;
240
- if (statusType === "idle" && this.pendingTurnCompletion) {
241
- this.emitRawTurnCompletion(this._turnId, "completed", null, method);
440
+ if ((statusType === "idle" || statusType === "systemError") && this.pendingTurnCompletion?.opened) {
441
+ this.armThreadStopGrace(statusType);
442
+ } else if (statusType === "active") {
443
+ this.clearThreadStopGrace();
444
+ }
445
+ return true;
446
+ }
447
+ if (method === "thread/closed") {
448
+ if (this.isOwnThreadFrame(params) && this.pendingTurnCompletion) {
449
+ this.emitRawTurnCompletion(
450
+ this.pendingTurnCompletion.turnId ?? this._turnId,
451
+ "aborted",
452
+ { message: "Codex closed the thread before the turn ended." },
453
+ method
454
+ );
242
455
  }
243
456
  return true;
244
457
  }
245
458
  if (method === "thread/tokenUsage/updated") {
459
+ if (!this.isOwnThreadFrame(params)) return true;
246
460
  const tokenUsage = params?.tokenUsage;
247
461
  if (tokenUsage && typeof tokenUsage === "object") {
248
462
  this.eventHandler?.({
@@ -256,8 +470,15 @@ class CodexAppServerClient {
256
470
  if (!item || typeof item !== "object") {
257
471
  return method.startsWith("item/");
258
472
  }
473
+ const child = this.childItemFields(params);
259
474
  if ((method === "item/started" || method === "item/completed") && (item.type === "collabAgentToolCall" || item.type === "subAgentActivity")) {
260
- this.eventHandler?.({ type: "collab_agent_item", item });
475
+ this.rememberChildLabels(item);
476
+ this.eventHandler?.({ type: "collab_agent_item", item, ...child });
477
+ return true;
478
+ }
479
+ if (method === "item/completed" && item.type === "userMessage") {
480
+ const clientId = typeof item.clientId === "string" ? item.clientId : null;
481
+ if (clientId && !child.subagent) this.steeredAwaitingEcho.delete(clientId);
261
482
  return true;
262
483
  }
263
484
  if (method === "item/started" && item.type === "commandExecution") {
@@ -268,7 +489,8 @@ class CodexAppServerClient {
268
489
  callId,
269
490
  command: item.command,
270
491
  cwd: item.cwd,
271
- description: item.command
492
+ description: item.command,
493
+ ...child
272
494
  });
273
495
  return true;
274
496
  }
@@ -283,7 +505,8 @@ class CodexAppServerClient {
283
505
  duration_ms: item.durationMs ?? null,
284
506
  status: item.status,
285
507
  cwd: item.cwd,
286
- command: item.command
508
+ command: item.command,
509
+ ...child
287
510
  });
288
511
  return true;
289
512
  }
@@ -298,7 +521,8 @@ class CodexAppServerClient {
298
521
  type: "patch_apply_begin",
299
522
  call_id: callId,
300
523
  callId,
301
- changes: changes ?? {}
524
+ changes: changes ?? {},
525
+ ...child
302
526
  });
303
527
  return true;
304
528
  }
@@ -307,7 +531,8 @@ class CodexAppServerClient {
307
531
  type: "patch_apply_end",
308
532
  call_id: callId,
309
533
  callId,
310
- status: item.status
534
+ status: item.status,
535
+ ...child
311
536
  });
312
537
  if (callId && (item.status === "completed" || item.status === "failed" || item.status === "declined")) {
313
538
  this.rawFileChangesByItemId.delete(callId);
@@ -322,7 +547,8 @@ class CodexAppServerClient {
322
547
  type: "agent_message",
323
548
  message: text,
324
549
  item_id: item.id,
325
- phase: item.phase
550
+ phase: item.phase,
551
+ ...child
326
552
  });
327
553
  }
328
554
  return true;
@@ -384,17 +610,39 @@ class CodexAppServerClient {
384
610
  return;
385
611
  }
386
612
  this.connected = false;
613
+ const tail = this.stderrTail();
614
+ const exited = `Codex process exited (code=${code ?? "null"}${signal ? `, signal=${signal}` : ""})`;
387
615
  for (const [id, req] of this.pending) {
388
616
  if (req.epoch !== epoch) continue;
389
- req.reject(new Error(`Codex process exited (code=${code}) while waiting for ${req.method}`));
617
+ const error = new Error(`${exited} while waiting for ${req.method}`);
618
+ error.codexStderrTail = tail;
619
+ req.reject(error);
390
620
  this.pending.delete(id);
391
621
  }
622
+ if (this.pendingTurnCompletion) {
623
+ const turnId = this.pendingTurnCompletion.turnId ?? this._turnId;
624
+ this.dropUnechoedSteers(null, true);
625
+ this.eventHandler?.({
626
+ type: "turn_aborted",
627
+ reason: "process-exit",
628
+ ...turnId ? { turn_id: turnId } : {},
629
+ error: { message: `${exited}${tail ? `: ${tail}` : ""}` }
630
+ });
631
+ }
392
632
  this.resolvePendingTurn(true);
393
633
  });
634
+ this.stderrRing = "";
635
+ this.steerUnsupported = false;
636
+ this.steeredAwaitingEcho.clear();
637
+ this.childThreadLabels.clear();
394
638
  proc.stderr?.on("data", (chunk) => {
395
639
  if (this.process !== proc || this.processEpoch !== epoch) return;
396
640
  const text = chunk.toString().trim();
397
- if (text) logger.logger.debug("[CodexAppServer:stderr]", contentLogMetadata.contentLogMetadata(text));
641
+ if (text) {
642
+ logger.logger.debug("[CodexAppServer:stderr]", contentLogMetadata.contentLogMetadata(text));
643
+ this.stderrRing = `${this.stderrRing}
644
+ ${text}`.slice(-400);
645
+ }
398
646
  });
399
647
  this.readline = node_readline.createInterface({ input: proc.stdout });
400
648
  this.readline.on("line", (line) => {
@@ -425,26 +673,21 @@ class CodexAppServerClient {
425
673
  logger.logger.debug(`[CodexAppServer] Disconnecting; pid=${pid ?? "none"}`);
426
674
  this.readline?.close();
427
675
  this.readline = null;
428
- try {
429
- proc?.stdin?.end();
430
- proc?.kill("SIGTERM");
431
- } catch {
432
- }
433
- if (pid) {
434
- const killTimer = setTimeout(() => {
435
- try {
436
- process.kill(pid, 0);
437
- process.kill(pid, "SIGKILL");
438
- } catch {
439
- }
440
- }, 2e3);
441
- killTimer.unref();
676
+ if (proc) {
677
+ stopCodexProcess(proc);
678
+ if (pid) {
679
+ const killTimer = setTimeout(() => killCodexProcessTree(proc), CodexAppServerClient.FORCE_KILL_AFTER_MS);
680
+ killTimer.unref();
681
+ }
442
682
  }
443
683
  this.process = null;
444
684
  this.connected = false;
445
685
  this._turnId = null;
446
686
  this.notificationProtocol = "unknown";
447
687
  this.completedTurnIds.clear();
688
+ this.failedTurnIds.clear();
689
+ this.lastTurnError = null;
690
+ this.clearThreadStopGrace();
448
691
  if (!opts?.preserveThreadState) {
449
692
  this._threadId = null;
450
693
  this.threadDefaults = null;
@@ -454,6 +697,7 @@ class CodexAppServerClient {
454
697
  req.reject(new Error(`Codex process disconnected while waiting for ${req.method}`));
455
698
  this.pending.delete(id);
456
699
  }
700
+ this.dropUnechoedSteers(null, true);
457
701
  this.resolvePendingTurn(true);
458
702
  if (this.sandboxCleanup) {
459
703
  try {
@@ -657,20 +901,137 @@ class CodexAppServerClient {
657
901
  // ─── Turn management ────────────────────────────────────────
658
902
  /** Default grace period after interrupt before forcing a restart (ms). */
659
903
  static ABORT_GRACE_MS = 3e3;
904
+ /** How long the graceful stop gets before the process tree is killed (disconnectInternal). */
905
+ static FORCE_KILL_AFTER_MS = 2e3;
660
906
  hasPendingTurnCompletion() {
661
907
  return this.pendingTurnCompletion !== null;
662
908
  }
663
909
  resolvePendingTurn(aborted) {
664
910
  if (!this.pendingTurnCompletion) return;
665
911
  this.clearTurnWatchdog();
912
+ this.clearThreadStopGrace();
666
913
  this.pendingTurnCompletion.resolve(aborted);
667
914
  this.pendingTurnCompletion = null;
915
+ this.releaseTurnOpenWaiters();
668
916
  }
669
917
  markPendingTurnStarted(turnId) {
670
918
  if (!this.pendingTurnCompletion) return;
919
+ this.pendingTurnCompletion.opened = true;
671
920
  if (turnId) {
672
921
  this.pendingTurnCompletion.turnId = turnId;
673
922
  }
923
+ this.releaseTurnOpenWaiters();
924
+ }
925
+ // ─── B-628: steering the running turn ───────────────────────
926
+ /** How long a steer waits for the pending turn to open before the message is queued instead. */
927
+ static STEER_OPEN_WAIT_MS = 5e3;
928
+ /** A turn Codex has reported running, under an id `turn/steer` can name. */
929
+ turnIsOpen() {
930
+ return this.pendingTurnCompletion?.opened === true && this._turnId !== null;
931
+ }
932
+ releaseTurnOpenWaiters() {
933
+ const waiters = this.turnOpenWaiters;
934
+ this.turnOpenWaiters = [];
935
+ for (const release of waiters) release();
936
+ }
937
+ /**
938
+ * Waits for the pending turn to open — `turn/start` is in flight, or the runner is still
939
+ * bringing a released app-server back before sending it (B-461) — as Orca's send does
940
+ * (codex-structured-turn-open-wait.ts, 5 s). Resolves early when the turn ends instead.
941
+ */
942
+ async waitForTurnOpen(timeoutMs) {
943
+ if (this.turnIsOpen()) return true;
944
+ if (timeoutMs <= 0) return false;
945
+ await new Promise((resolve) => {
946
+ const release = () => {
947
+ clearTimeout(timer);
948
+ resolve();
949
+ };
950
+ const timer = setTimeout(release, timeoutMs);
951
+ timer.unref();
952
+ this.turnOpenWaiters.push(release);
953
+ });
954
+ return this.turnIsOpen();
955
+ }
956
+ /**
957
+ * B-628: send a message into the turn running right now (`turn/steer`, codex ≥ 0.148 /
958
+ * app-server-protocol v2 `TurnSteerParams`). Codex appends it to that turn's pending input
959
+ * and the model reads it before its next call; the turn keeps its own model, approval
960
+ * policy and sandbox — the runner applies any change on the next `turn/start`. Codex
961
+ * refuses before taking any input (expectedTurnId is a precondition), so a non-`steered`
962
+ * outcome always means "queue it for the next turn as before": `no-turn` (nothing running,
963
+ * or it ended while we waited), `unsupported` (this app-server has no `turn/steer`,
964
+ * remembered until it is respawned), `refused` (Codex declined this one), `failed`
965
+ * (transport — the turn itself is reported through its own events).
966
+ */
967
+ async steerTurn(input, opts) {
968
+ if (this.steerUnsupported) return "unsupported";
969
+ if (this.pendingInterrupt) return "no-turn";
970
+ const opened = await this.waitForTurnOpen(opts?.waitForTurnMs ?? CodexAppServerClient.STEER_OPEN_WAIT_MS);
971
+ const threadId = this._threadId;
972
+ const turnId = this._turnId;
973
+ if (!opened || !threadId || !turnId || !this.connected || this.pendingInterrupt) return "no-turn";
974
+ const params = {
975
+ threadId,
976
+ expectedTurnId: turnId,
977
+ ...input.clientId ? { clientUserMessageId: input.clientId } : {},
978
+ input: [
979
+ { type: "text", text: input.text },
980
+ ...(input.images ?? []).map((path) => ({ type: "localImage", path }))
981
+ ]
982
+ };
983
+ try {
984
+ const result = await this.request("turn/steer", params);
985
+ const steeredTurnId = typeof result?.turnId === "string" && result.turnId.length > 0 ? result.turnId : turnId;
986
+ if (input.clientId) this.steeredAwaitingEcho.set(input.clientId, steeredTurnId);
987
+ logger.logger.debug(`[CodexAppServer] Steered a message into turn ${steeredTurnId}`);
988
+ return "steered";
989
+ } catch (error) {
990
+ const code = jsonRpcErrorCodeOf(error);
991
+ if (code === -32601) {
992
+ this.steerUnsupported = true;
993
+ logger.logger.debug("[CodexAppServer] turn/steer is not supported by this codex; queuing instead");
994
+ return "unsupported";
995
+ }
996
+ if (code !== null) {
997
+ logger.logger.debug("[CodexAppServer] turn/steer refused:", contentLogMetadata.errorLogMetadata(error));
998
+ return "refused";
999
+ }
1000
+ logger.logger.debug("[CodexAppServer] turn/steer failed:", contentLogMetadata.errorLogMetadata(error));
1001
+ return "failed";
1002
+ }
1003
+ }
1004
+ /** Default wait for Codex's `turn/completed` after its thread reported idle/systemError. */
1005
+ static THREAD_STOP_GRACE_MS = 1500;
1006
+ /**
1007
+ * B-625: the own thread stopped (idle / systemError) while a turn was pending and
1008
+ * had opened. Codex's `turn/completed` follows at once in every probed release
1009
+ * (0.154.0: 1 ms); this is the net under a release that does not send it, so the
1010
+ * session never waits on the silence watchdog for a turn Codex has already left.
1011
+ */
1012
+ armThreadStopGrace(statusType) {
1013
+ this.clearThreadStopGrace();
1014
+ const graceMs = this.pendingTurnCompletion?.threadStopGraceMs ?? CodexAppServerClient.THREAD_STOP_GRACE_MS;
1015
+ this.threadStopGrace = setTimeout(() => {
1016
+ this.threadStopGrace = null;
1017
+ const pending = this.pendingTurnCompletion;
1018
+ if (!pending) return;
1019
+ const turnId = pending.turnId ?? this._turnId;
1020
+ const lastError = this.lastTurnError && (!turnId || !this.lastTurnError.turnId || this.lastTurnError.turnId === turnId) ? this.lastTurnError.error : null;
1021
+ logger.logger.warn(`[CodexAppServer] Thread went ${statusType} and no turn/completed followed for turn ${turnId ?? "?"}; ending the turn`);
1022
+ const failed = statusType === "systemError" || lastError !== null;
1023
+ this.emitRawTurnCompletion(
1024
+ turnId,
1025
+ failed ? "failed" : "completed",
1026
+ lastError ?? (failed ? { message: "Codex reported a system error and ended the turn without a verdict." } : null),
1027
+ `thread/status/changed:${statusType}`
1028
+ );
1029
+ }, graceMs);
1030
+ }
1031
+ clearThreadStopGrace() {
1032
+ if (!this.threadStopGrace) return;
1033
+ clearTimeout(this.threadStopGrace);
1034
+ this.threadStopGrace = null;
674
1035
  }
675
1036
  tryResolvePendingTurn(aborted, turnId, source) {
676
1037
  const pending = this.pendingTurnCompletion;
@@ -773,6 +1134,8 @@ class CodexAppServerClient {
773
1134
  }
774
1135
  /** Default timeout for waiting on turn completion (ms). 10 minutes. */
775
1136
  static TURN_TIMEOUT_MS = 10 * 60 * 1e3;
1137
+ /** How much of the app-server's stderr is kept for its exit message. */
1138
+ static STDERR_TAIL_CHARS = 400;
776
1139
  /**
777
1140
  * Send a user turn and wait for it to complete (task_complete or turn_aborted).
778
1141
  * Returns { aborted: true } if the turn was aborted (user cancel, permission reject, etc.).
@@ -787,8 +1150,11 @@ class CodexAppServerClient {
787
1150
  const completion = new Promise((resolve) => {
788
1151
  this.pendingTurnCompletion = {
789
1152
  resolve,
790
- turnId: null
1153
+ turnId: null,
1154
+ opened: false,
1155
+ threadStopGraceMs: opts?.threadStopGraceMs ?? CodexAppServerClient.THREAD_STOP_GRACE_MS
791
1156
  };
1157
+ this.lastTurnError = null;
792
1158
  this.armTurnWatchdog(timeoutMs);
793
1159
  });
794
1160
  try {
@@ -796,6 +1162,7 @@ class CodexAppServerClient {
796
1162
  } catch (err) {
797
1163
  this.clearTurnWatchdog();
798
1164
  this.pendingTurnCompletion = null;
1165
+ this.releaseTurnOpenWaiters();
799
1166
  throw err;
800
1167
  }
801
1168
  const aborted = await completion;
@@ -813,6 +1180,7 @@ class CodexAppServerClient {
813
1180
  }
814
1181
  logger.logger.warn(`[CodexAppServer] No notification from Codex for ${timeoutMs}ms \u2014 giving the turn up as aborted`);
815
1182
  const turnId = this.pendingTurnCompletion.turnId ?? this._turnId;
1183
+ this.dropUnechoedSteers(turnId, true);
816
1184
  this.eventHandler?.({
817
1185
  type: "turn_aborted",
818
1186
  reason: "timeout",
@@ -863,12 +1231,16 @@ class CodexAppServerClient {
863
1231
  logger.logger.debug(
864
1232
  `[CodexAppServer] Clearing thread state: thread=${this._threadId ?? "none"} turn=${this._turnId ?? "none"}`
865
1233
  );
1234
+ this.dropUnechoedSteers(null, true);
866
1235
  this.resolvePendingTurn(true);
867
1236
  this._threadId = null;
868
1237
  this._turnId = null;
869
1238
  this.threadDefaults = null;
870
1239
  this.completedTurnIds.clear();
1240
+ this.failedTurnIds.clear();
1241
+ this.lastTurnError = null;
871
1242
  this.rawFileChangesByItemId.clear();
1243
+ this.childThreadLabels.clear();
872
1244
  }
873
1245
  // ─── JSON-RPC transport ─────────────────────────────────────
874
1246
  /** Default timeout for RPC requests (ms). */
@@ -949,7 +1321,9 @@ class CodexAppServerClient {
949
1321
  }
950
1322
  this.pending.delete(msg.id);
951
1323
  if (msg.error) {
952
- pending.reject(new Error(`${pending.method}: ${msg.error.message} (code=${msg.error.code})`));
1324
+ const error = new Error(`${pending.method}: ${msg.error.message} (code=${msg.error.code})`);
1325
+ if (typeof msg.error.code === "number") error.jsonRpcCode = msg.error.code;
1326
+ pending.reject(error);
953
1327
  } else {
954
1328
  pending.resolve(msg.result);
955
1329
  }
@@ -1136,6 +1510,7 @@ class CodexAppServerClient {
1136
1510
  }
1137
1511
  if (method === "thread/started" || method === "turn/started" || method === "turn/completed" || method === "thread/status/changed") {
1138
1512
  logger.logger.debug(`[CodexAppServer] Lifecycle notification: ${method}`);
1513
+ if (!this.isOwnThreadFrame(params)) return;
1139
1514
  if (method === "turn/started") {
1140
1515
  const turnId = this.extractTurnId(params);
1141
1516
  if (turnId) {
@@ -1162,19 +1537,28 @@ class CodexAppServerClient {
1162
1537
  logger.logger.debug(`[CodexAppServer] mcpServer startup status:`, params);
1163
1538
  return;
1164
1539
  }
1165
- if (method === "error" && params?.willRetry === true) {
1540
+ if (method === "error") {
1541
+ if (!this.isOwnThreadFrame(params)) return;
1166
1542
  const error = params?.error ?? {};
1167
- const message = typeof error.message === "string" ? error.message : "";
1168
- const info = error.codexErrorInfo;
1169
- const httpStatus = info && typeof info === "object" ? info.responseStreamDisconnected?.httpStatusCode ?? info.httpStatusCode ?? null : null;
1170
- const turnId = typeof params?.turnId === "string" ? params.turnId : null;
1171
- this.eventHandler?.({
1172
- type: "turn_retry",
1173
- message,
1174
- ...typeof httpStatus === "number" ? { http_status: httpStatus } : {},
1175
- ...turnId ? { turn_id: turnId } : {},
1176
- ...typeof error.additionalDetails === "string" ? { details: error.additionalDetails } : {}
1177
- });
1543
+ const turnId = typeof params?.turnId === "string" && params.turnId.length > 0 ? params.turnId : null;
1544
+ if (params?.willRetry === true) {
1545
+ const message = typeof error.message === "string" ? error.message : "";
1546
+ const info = error.codexErrorInfo;
1547
+ const httpStatus = info && typeof info === "object" ? info.responseStreamDisconnected?.httpStatusCode ?? info.httpStatusCode ?? null : null;
1548
+ this.eventHandler?.({
1549
+ type: "turn_retry",
1550
+ message,
1551
+ ...typeof httpStatus === "number" ? { http_status: httpStatus } : {},
1552
+ ...turnId ? { turn_id: turnId } : {},
1553
+ ...typeof error.additionalDetails === "string" ? { details: error.additionalDetails } : {}
1554
+ });
1555
+ return;
1556
+ }
1557
+ this.lastTurnError = { turnId, error };
1558
+ const pending = this.pendingTurnCompletion;
1559
+ if (pending && !pending.opened && (turnId === null || pending.turnId === null || pending.turnId === turnId)) {
1560
+ this.emitRawTurnCompletion(turnId ?? pending.turnId, "failed", error, "error:unopened-turn");
1561
+ }
1178
1562
  return;
1179
1563
  }
1180
1564
  logger.logger.debug(`[CodexAppServer] Notification: ${method}`);