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
@@ -1,12 +1,100 @@
1
- import { execSync } from 'node:child_process';
2
- import { spawn } from 'cross-spawn';
1
+ import { spawn, execSync } from 'node:child_process';
2
+ import { spawn as spawn$1 } from 'cross-spawn';
3
3
  import { createInterface } from 'node:readline';
4
- import { l as logger } from './logger-CUxbqfrp.mjs';
4
+ import { l as logger } from './logger-CihCDWDw.mjs';
5
5
  import { e as errorLogMetadata, c as contentLogMetadata } from './contentLogMetadata-BlWI0egu.mjs';
6
- import { i as initializeSandbox, w as wrapForMcpTransport } from './manager-BEsI4qt3.mjs';
7
- import { p as packageJson } from './package-DkI3yDe0.mjs';
8
- import { s as shouldReadCodexRateLimits, i as isUnsupportedCodexMethodError } from './rateLimitWindows-i2-530Zt.mjs';
6
+ import { i as initializeSandbox, a as wrapForMcpTransport } from './manager-2Il8XJVq.mjs';
7
+ import { p as packageJson } from './package-ayqGDKSp.mjs';
8
+ import { s as shouldReadCodexRateLimits, i as isUnsupportedCodexMethodError } from './rateLimitWindows-E5DRjfE6.mjs';
9
9
 
10
+ const CODEX_HELPER_LABEL_MAX_CHARS = 80;
11
+ function codexHelperLabel(prompt) {
12
+ if (typeof prompt !== "string") return null;
13
+ const collapsed = prompt.replace(/\s+/g, " ").trim();
14
+ if (collapsed.length === 0) return null;
15
+ if (collapsed.length <= CODEX_HELPER_LABEL_MAX_CHARS) return collapsed;
16
+ const keep = CODEX_HELPER_LABEL_MAX_CHARS - 1;
17
+ const last = collapsed.charCodeAt(keep - 1);
18
+ const end = last >= 55296 && last <= 56319 ? keep - 1 : keep;
19
+ return `${collapsed.slice(0, end)}\u2026`;
20
+ }
21
+ function codexAgentPathSegments(agentPath) {
22
+ if (typeof agentPath !== "string") return [];
23
+ return agentPath.split("/").map((segment) => segment.trim()).filter((segment) => segment.length > 0);
24
+ }
25
+ function isRootAgentPath(segments) {
26
+ return segments.length === 1 && segments[0] === "root";
27
+ }
28
+ function codexChildAnnouncements(item) {
29
+ if (item.type === "subAgentActivity") {
30
+ const threadId = item.agentThreadId;
31
+ if (typeof threadId !== "string" || threadId.length === 0) return [];
32
+ const segments = codexAgentPathSegments(item.agentPath);
33
+ if (isRootAgentPath(segments)) return [];
34
+ const leaf = segments.at(-1);
35
+ return [{ threadId, label: leaf && leaf.length > 0 ? leaf : null }];
36
+ }
37
+ if (item.type === "collabAgentToolCall") {
38
+ const receivers = Array.isArray(item.receiverThreadIds) ? item.receiverThreadIds.filter((id) => typeof id === "string" && id.length > 0) : [];
39
+ const label = item.tool === "spawnAgent" ? codexHelperLabel(item.prompt) : null;
40
+ return [...new Set(receivers)].map((threadId) => ({ threadId, label }));
41
+ }
42
+ return [];
43
+ }
44
+
45
+ function stopCodexProcess(proc, deps = {}) {
46
+ const platform = deps.platform ?? process.platform;
47
+ try {
48
+ proc.stdin?.end();
49
+ } catch {
50
+ }
51
+ if (platform === "win32") return;
52
+ try {
53
+ proc.kill("SIGTERM");
54
+ } catch {
55
+ }
56
+ }
57
+ function killCodexProcessTree(proc, deps = {}) {
58
+ if (proc.exitCode != null || proc.signalCode != null) return;
59
+ const killRoot = () => {
60
+ try {
61
+ proc.kill("SIGKILL");
62
+ } catch {
63
+ }
64
+ };
65
+ const pid = proc.pid;
66
+ if (!pid || !("exitCode" in proc)) {
67
+ killRoot();
68
+ return;
69
+ }
70
+ const platform = deps.platform ?? process.platform;
71
+ const spawn$1 = deps.spawn ?? ((command, args, options) => spawn(command, args, options));
72
+ if (platform === "win32") {
73
+ try {
74
+ const killer = spawn$1("taskkill", ["/pid", String(pid), "/t", "/f"], { stdio: "ignore", windowsHide: true });
75
+ killer.on("error", killRoot);
76
+ killer.on("exit", (code) => {
77
+ if (code !== 0) killRoot();
78
+ });
79
+ killer.unref();
80
+ } catch {
81
+ killRoot();
82
+ }
83
+ return;
84
+ }
85
+ try {
86
+ const reaper = spawn$1("pkill", ["-KILL", "-P", String(pid)], { stdio: "ignore", windowsHide: true });
87
+ reaper.on("error", () => void 0);
88
+ reaper.unref();
89
+ } catch {
90
+ }
91
+ killRoot();
92
+ }
93
+
94
+ function jsonRpcErrorCodeOf(error) {
95
+ const code = error?.jsonRpcCode;
96
+ return typeof code === "number" ? code : null;
97
+ }
10
98
  function isAppServerAvailable() {
11
99
  try {
12
100
  const version = execSync("codex --version", { encoding: "utf8", windowsHide: true }).trim();
@@ -89,7 +177,6 @@ class CodexAppServerClient {
89
177
  _turnId = null;
90
178
  threadDefaults = null;
91
179
  // Turn completion tracking for the currently active sendTurnAndWait call.
92
- // A completion event only resolves once we have seen task_started for this turn.
93
180
  pendingTurnCompletion = null;
94
181
  // Tracks in-flight interruptTurn() RPCs so sendTurnAndWait can wait for them
95
182
  // before starting a new turn (prevents stale turn/interrupt from aborting the next turn).
@@ -99,6 +186,22 @@ class CodexAppServerClient {
99
186
  idleRelease = null;
100
187
  notificationProtocol = "unknown";
101
188
  completedTurnIds = /* @__PURE__ */ new Set();
189
+ /** B-625: turns whose failure has already been forwarded, so a second failed frame is not. */
190
+ failedTurnIds = /* @__PURE__ */ new Set();
191
+ // B-625: the app-server speaks for every thread it hosts — the session's own and the
192
+ // children `spawn_agent` opens — so a lifecycle frame (turn/started, turn/completed,
193
+ // thread/status/changed, error, thread/closed) is the session's only when its `threadId`
194
+ // is ours; a frame naming no thread belongs to the thread it was asked on (Orca's rule,
195
+ // src/main/codex/codex-structured-turn-open-wait.ts). Before this, a child's
196
+ // `turn/completed` ended the parent's turn in the UI and a child's `turn/started`
197
+ // retargeted `turn/interrupt` at the child.
198
+ /** The own thread's last turn-ending `error` (willRetry=false), for a turn end that arrives without a verdict. */
199
+ lastTurnError = null;
200
+ /** Own thread reported idle/systemError while a turn was pending: Codex's `turn/completed`
201
+ * follows within a millisecond; this ends the turn if it does not (see armThreadStopGrace). */
202
+ threadStopGrace = null;
203
+ /** Last few hundred chars of the app-server's stderr: Codex's own last words when it dies. */
204
+ stderrRing = "";
102
205
  rawFileChangesByItemId = /* @__PURE__ */ new Map();
103
206
  // B-622: inactivity watchdog for the pending turn — re-armed by every
104
207
  // notification, never a cap on how long a turn may run (see sendTurnAndWait).
@@ -106,6 +209,18 @@ class CodexAppServerClient {
106
209
  // Approval / elicitation requests we are still answering: a human may take
107
210
  // longer than the watchdog to decide, and that is not Codex going silent.
108
211
  inflightServerRequests = 0;
212
+ // B-628: `turn/steer` — a message sent into the running turn (approach from Orca,
213
+ // src/main/codex/codex-structured-turn-start.ts). This app-server has no such
214
+ // method (-32601) until it is respawned; a refusal is per call (the turn it named
215
+ // ended or changed meanwhile, a review/compact turn).
216
+ steerUnsupported = false;
217
+ /** Steered inputs Codex has not echoed back as a `userMessage` item yet: clientId → turnId. */
218
+ steeredAwaitingEcho = /* @__PURE__ */ new Map();
219
+ /** Waiters for the pending turn to open (steerTurn); released when it opens or ends. */
220
+ turnOpenWaiters = [];
221
+ /** B-628: the names Codex gave the child threads it spawned (see codexChildThreads.ts). */
222
+ childThreadLabels = /* @__PURE__ */ new Map();
223
+ static MAX_CHILD_LABELS = 128;
109
224
  // Handlers set by the consumer (runCodex.ts)
110
225
  eventHandler = null;
111
226
  approvalHandler = null;
@@ -167,8 +282,73 @@ class CodexAppServerClient {
167
282
  const status = params?.turn?.status ?? params?.status ?? null;
168
283
  return typeof status === "string" && status.length > 0 ? status : null;
169
284
  }
285
+ extractThreadId(params) {
286
+ const threadId = params?.threadId ?? params?.thread_id ?? params?.thread?.id ?? null;
287
+ return typeof threadId === "string" && threadId.length > 0 ? threadId : null;
288
+ }
289
+ /** B-625: whether a lifecycle frame is about the session's own thread (see the field comment). */
290
+ isOwnThreadFrame(params) {
291
+ const threadId = this.extractThreadId(params);
292
+ return threadId === null || this._threadId === null || threadId === this._threadId;
293
+ }
294
+ /** B-628: the child thread a frame belongs to, or null for the session's own. */
295
+ childThreadIdOf(params) {
296
+ return this.isOwnThreadFrame(params) ? null : this.extractThreadId(params);
297
+ }
298
+ /**
299
+ * B-628: what a child thread's item carries so the mapper files it under the child's
300
+ * card (`subagent` → the session protocol's sub-agent envelope, as a Claude Task's
301
+ * children are) instead of in the main conversation as the session's own work.
302
+ */
303
+ childItemFields(params) {
304
+ const child = this.childThreadIdOf(params);
305
+ if (!child) return {};
306
+ const label = this.childThreadLabels.get(child);
307
+ return { subagent: child, ...label ? { subagent_label: label } : {} };
308
+ }
309
+ rememberChildLabels(item) {
310
+ for (const { threadId, label } of codexChildAnnouncements(item)) {
311
+ if (!label || this.childThreadLabels.has(threadId)) continue;
312
+ if (this.childThreadLabels.size >= CodexAppServerClient.MAX_CHILD_LABELS) {
313
+ const oldest = this.childThreadLabels.keys().next().value;
314
+ if (oldest !== void 0) this.childThreadLabels.delete(oldest);
315
+ }
316
+ this.childThreadLabels.set(threadId, label);
317
+ }
318
+ }
319
+ /**
320
+ * B-628: steered inputs the ended turn never echoed. `report` = the turn did not complete
321
+ * (interrupted, failed, app-server gone, given up): Codex never read them, tell the runner.
322
+ * A turn that *completed* drains its pending input before it ends (core/src/session/turn.rs),
323
+ * so an unechoed input there is a codex that accepted the steer but echoes no `clientId` —
324
+ * forgotten silently, because a false「not delivered」on a message the model did read would
325
+ * be worse than today's silence.
326
+ */
327
+ dropUnechoedSteers(turnId, report) {
328
+ const dropped = [];
329
+ for (const [clientId, steeredTurn] of this.steeredAwaitingEcho) {
330
+ if (turnId === null || steeredTurn === turnId) {
331
+ dropped.push(clientId);
332
+ this.steeredAwaitingEcho.delete(clientId);
333
+ }
334
+ }
335
+ if (dropped.length === 0) return;
336
+ if (!report) {
337
+ logger.debug(`[CodexAppServer] ${dropped.length} steered message(s) completed without an echo; not reported`);
338
+ return;
339
+ }
340
+ this.eventHandler?.({
341
+ type: "steer_dropped",
342
+ client_ids: dropped,
343
+ ...turnId ? { turn_id: turnId } : {}
344
+ });
345
+ }
346
+ /** Codex's last stderr lines, one line, for a message a person reads. */
347
+ stderrTail() {
348
+ return this.stderrRing.replace(/\x1b\[[0-9;]*m/g, "").replace(/\s+/g, " ").trim();
349
+ }
170
350
  shouldHandleRawNotification(method) {
171
- const isRawNotification = method === "thread/started" || method === "turn/started" || method === "turn/completed" || method === "thread/status/changed" || method === "thread/tokenUsage/updated" || method.startsWith("item/");
351
+ 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/");
172
352
  if (!isRawNotification) {
173
353
  return false;
174
354
  }
@@ -183,15 +363,21 @@ class CodexAppServerClient {
183
363
  emitRawTurnCompletion(turnId, status, error, source) {
184
364
  const aborted = status === "cancelled" || status === "canceled" || status === "aborted" || status === "interrupted";
185
365
  const failed = status === "failed" || error !== void 0 && error !== null;
366
+ this.clearThreadStopGrace();
186
367
  this.tryResolvePendingTurn(aborted, turnId, source);
187
368
  this._turnId = null;
188
369
  this.maybeRefreshRateLimits();
370
+ if (this.lastTurnError && (!turnId || !this.lastTurnError.turnId || this.lastTurnError.turnId === turnId)) {
371
+ this.lastTurnError = null;
372
+ }
189
373
  if (turnId && this.completedTurnIds.has(turnId)) {
190
- if (!failed) return;
374
+ if (!failed || this.failedTurnIds.has(turnId)) return;
191
375
  }
192
376
  if (turnId) {
193
377
  this.completedTurnIds.add(turnId);
378
+ if (failed) this.failedTurnIds.add(turnId);
194
379
  }
380
+ this.dropUnechoedSteers(turnId, aborted || failed);
195
381
  if (aborted) {
196
382
  this.eventHandler?.({
197
383
  type: "turn_aborted",
@@ -213,6 +399,10 @@ class CodexAppServerClient {
213
399
  return false;
214
400
  }
215
401
  if (method === "turn/started") {
402
+ if (!this.isOwnThreadFrame(params)) {
403
+ logger.debug(`[CodexAppServer] Child thread ${this.extractThreadId(params)} started a turn`);
404
+ return true;
405
+ }
216
406
  const turnId = this.extractTurnId(params);
217
407
  if (turnId) {
218
408
  this._turnId = turnId;
@@ -225,6 +415,15 @@ class CodexAppServerClient {
225
415
  return true;
226
416
  }
227
417
  if (method === "turn/completed") {
418
+ if (!this.isOwnThreadFrame(params)) {
419
+ logger.debug(`[CodexAppServer] Child thread ${this.extractThreadId(params)} ended a turn`);
420
+ this.eventHandler?.({
421
+ type: "subagent_turn_end",
422
+ ...this.childItemFields(params),
423
+ status: this.extractTurnStatus(params)
424
+ });
425
+ return true;
426
+ }
228
427
  this.emitRawTurnCompletion(
229
428
  this.extractTurnId(params),
230
429
  this.extractTurnStatus(params),
@@ -234,13 +433,28 @@ class CodexAppServerClient {
234
433
  return true;
235
434
  }
236
435
  if (method === "thread/status/changed") {
436
+ if (!this.isOwnThreadFrame(params)) return true;
237
437
  const statusType = params?.status?.type;
238
- if (statusType === "idle" && this.pendingTurnCompletion) {
239
- this.emitRawTurnCompletion(this._turnId, "completed", null, method);
438
+ if ((statusType === "idle" || statusType === "systemError") && this.pendingTurnCompletion?.opened) {
439
+ this.armThreadStopGrace(statusType);
440
+ } else if (statusType === "active") {
441
+ this.clearThreadStopGrace();
442
+ }
443
+ return true;
444
+ }
445
+ if (method === "thread/closed") {
446
+ if (this.isOwnThreadFrame(params) && this.pendingTurnCompletion) {
447
+ this.emitRawTurnCompletion(
448
+ this.pendingTurnCompletion.turnId ?? this._turnId,
449
+ "aborted",
450
+ { message: "Codex closed the thread before the turn ended." },
451
+ method
452
+ );
240
453
  }
241
454
  return true;
242
455
  }
243
456
  if (method === "thread/tokenUsage/updated") {
457
+ if (!this.isOwnThreadFrame(params)) return true;
244
458
  const tokenUsage = params?.tokenUsage;
245
459
  if (tokenUsage && typeof tokenUsage === "object") {
246
460
  this.eventHandler?.({
@@ -254,8 +468,15 @@ class CodexAppServerClient {
254
468
  if (!item || typeof item !== "object") {
255
469
  return method.startsWith("item/");
256
470
  }
471
+ const child = this.childItemFields(params);
257
472
  if ((method === "item/started" || method === "item/completed") && (item.type === "collabAgentToolCall" || item.type === "subAgentActivity")) {
258
- this.eventHandler?.({ type: "collab_agent_item", item });
473
+ this.rememberChildLabels(item);
474
+ this.eventHandler?.({ type: "collab_agent_item", item, ...child });
475
+ return true;
476
+ }
477
+ if (method === "item/completed" && item.type === "userMessage") {
478
+ const clientId = typeof item.clientId === "string" ? item.clientId : null;
479
+ if (clientId && !child.subagent) this.steeredAwaitingEcho.delete(clientId);
259
480
  return true;
260
481
  }
261
482
  if (method === "item/started" && item.type === "commandExecution") {
@@ -266,7 +487,8 @@ class CodexAppServerClient {
266
487
  callId,
267
488
  command: item.command,
268
489
  cwd: item.cwd,
269
- description: item.command
490
+ description: item.command,
491
+ ...child
270
492
  });
271
493
  return true;
272
494
  }
@@ -281,7 +503,8 @@ class CodexAppServerClient {
281
503
  duration_ms: item.durationMs ?? null,
282
504
  status: item.status,
283
505
  cwd: item.cwd,
284
- command: item.command
506
+ command: item.command,
507
+ ...child
285
508
  });
286
509
  return true;
287
510
  }
@@ -296,7 +519,8 @@ class CodexAppServerClient {
296
519
  type: "patch_apply_begin",
297
520
  call_id: callId,
298
521
  callId,
299
- changes: changes ?? {}
522
+ changes: changes ?? {},
523
+ ...child
300
524
  });
301
525
  return true;
302
526
  }
@@ -305,7 +529,8 @@ class CodexAppServerClient {
305
529
  type: "patch_apply_end",
306
530
  call_id: callId,
307
531
  callId,
308
- status: item.status
532
+ status: item.status,
533
+ ...child
309
534
  });
310
535
  if (callId && (item.status === "completed" || item.status === "failed" || item.status === "declined")) {
311
536
  this.rawFileChangesByItemId.delete(callId);
@@ -320,7 +545,8 @@ class CodexAppServerClient {
320
545
  type: "agent_message",
321
546
  message: text,
322
547
  item_id: item.id,
323
- phase: item.phase
548
+ phase: item.phase,
549
+ ...child
324
550
  });
325
551
  }
326
552
  return true;
@@ -366,7 +592,7 @@ class CodexAppServerClient {
366
592
  }
367
593
  logger.debug(`[CodexAppServer] Spawning: ${command} ${args.join(" ")}`);
368
594
  const epoch = ++this.processEpoch;
369
- const proc = spawn(command, args, {
595
+ const proc = spawn$1(command, args, {
370
596
  stdio: ["pipe", "pipe", "pipe"],
371
597
  env,
372
598
  windowsHide: true
@@ -382,17 +608,39 @@ class CodexAppServerClient {
382
608
  return;
383
609
  }
384
610
  this.connected = false;
611
+ const tail = this.stderrTail();
612
+ const exited = `Codex process exited (code=${code ?? "null"}${signal ? `, signal=${signal}` : ""})`;
385
613
  for (const [id, req] of this.pending) {
386
614
  if (req.epoch !== epoch) continue;
387
- req.reject(new Error(`Codex process exited (code=${code}) while waiting for ${req.method}`));
615
+ const error = new Error(`${exited} while waiting for ${req.method}`);
616
+ error.codexStderrTail = tail;
617
+ req.reject(error);
388
618
  this.pending.delete(id);
389
619
  }
620
+ if (this.pendingTurnCompletion) {
621
+ const turnId = this.pendingTurnCompletion.turnId ?? this._turnId;
622
+ this.dropUnechoedSteers(null, true);
623
+ this.eventHandler?.({
624
+ type: "turn_aborted",
625
+ reason: "process-exit",
626
+ ...turnId ? { turn_id: turnId } : {},
627
+ error: { message: `${exited}${tail ? `: ${tail}` : ""}` }
628
+ });
629
+ }
390
630
  this.resolvePendingTurn(true);
391
631
  });
632
+ this.stderrRing = "";
633
+ this.steerUnsupported = false;
634
+ this.steeredAwaitingEcho.clear();
635
+ this.childThreadLabels.clear();
392
636
  proc.stderr?.on("data", (chunk) => {
393
637
  if (this.process !== proc || this.processEpoch !== epoch) return;
394
638
  const text = chunk.toString().trim();
395
- if (text) logger.debug("[CodexAppServer:stderr]", contentLogMetadata(text));
639
+ if (text) {
640
+ logger.debug("[CodexAppServer:stderr]", contentLogMetadata(text));
641
+ this.stderrRing = `${this.stderrRing}
642
+ ${text}`.slice(-400);
643
+ }
396
644
  });
397
645
  this.readline = createInterface({ input: proc.stdout });
398
646
  this.readline.on("line", (line) => {
@@ -423,26 +671,21 @@ class CodexAppServerClient {
423
671
  logger.debug(`[CodexAppServer] Disconnecting; pid=${pid ?? "none"}`);
424
672
  this.readline?.close();
425
673
  this.readline = null;
426
- try {
427
- proc?.stdin?.end();
428
- proc?.kill("SIGTERM");
429
- } catch {
430
- }
431
- if (pid) {
432
- const killTimer = setTimeout(() => {
433
- try {
434
- process.kill(pid, 0);
435
- process.kill(pid, "SIGKILL");
436
- } catch {
437
- }
438
- }, 2e3);
439
- killTimer.unref();
674
+ if (proc) {
675
+ stopCodexProcess(proc);
676
+ if (pid) {
677
+ const killTimer = setTimeout(() => killCodexProcessTree(proc), CodexAppServerClient.FORCE_KILL_AFTER_MS);
678
+ killTimer.unref();
679
+ }
440
680
  }
441
681
  this.process = null;
442
682
  this.connected = false;
443
683
  this._turnId = null;
444
684
  this.notificationProtocol = "unknown";
445
685
  this.completedTurnIds.clear();
686
+ this.failedTurnIds.clear();
687
+ this.lastTurnError = null;
688
+ this.clearThreadStopGrace();
446
689
  if (!opts?.preserveThreadState) {
447
690
  this._threadId = null;
448
691
  this.threadDefaults = null;
@@ -452,6 +695,7 @@ class CodexAppServerClient {
452
695
  req.reject(new Error(`Codex process disconnected while waiting for ${req.method}`));
453
696
  this.pending.delete(id);
454
697
  }
698
+ this.dropUnechoedSteers(null, true);
455
699
  this.resolvePendingTurn(true);
456
700
  if (this.sandboxCleanup) {
457
701
  try {
@@ -655,20 +899,137 @@ class CodexAppServerClient {
655
899
  // ─── Turn management ────────────────────────────────────────
656
900
  /** Default grace period after interrupt before forcing a restart (ms). */
657
901
  static ABORT_GRACE_MS = 3e3;
902
+ /** How long the graceful stop gets before the process tree is killed (disconnectInternal). */
903
+ static FORCE_KILL_AFTER_MS = 2e3;
658
904
  hasPendingTurnCompletion() {
659
905
  return this.pendingTurnCompletion !== null;
660
906
  }
661
907
  resolvePendingTurn(aborted) {
662
908
  if (!this.pendingTurnCompletion) return;
663
909
  this.clearTurnWatchdog();
910
+ this.clearThreadStopGrace();
664
911
  this.pendingTurnCompletion.resolve(aborted);
665
912
  this.pendingTurnCompletion = null;
913
+ this.releaseTurnOpenWaiters();
666
914
  }
667
915
  markPendingTurnStarted(turnId) {
668
916
  if (!this.pendingTurnCompletion) return;
917
+ this.pendingTurnCompletion.opened = true;
669
918
  if (turnId) {
670
919
  this.pendingTurnCompletion.turnId = turnId;
671
920
  }
921
+ this.releaseTurnOpenWaiters();
922
+ }
923
+ // ─── B-628: steering the running turn ───────────────────────
924
+ /** How long a steer waits for the pending turn to open before the message is queued instead. */
925
+ static STEER_OPEN_WAIT_MS = 5e3;
926
+ /** A turn Codex has reported running, under an id `turn/steer` can name. */
927
+ turnIsOpen() {
928
+ return this.pendingTurnCompletion?.opened === true && this._turnId !== null;
929
+ }
930
+ releaseTurnOpenWaiters() {
931
+ const waiters = this.turnOpenWaiters;
932
+ this.turnOpenWaiters = [];
933
+ for (const release of waiters) release();
934
+ }
935
+ /**
936
+ * Waits for the pending turn to open — `turn/start` is in flight, or the runner is still
937
+ * bringing a released app-server back before sending it (B-461) — as Orca's send does
938
+ * (codex-structured-turn-open-wait.ts, 5 s). Resolves early when the turn ends instead.
939
+ */
940
+ async waitForTurnOpen(timeoutMs) {
941
+ if (this.turnIsOpen()) return true;
942
+ if (timeoutMs <= 0) return false;
943
+ await new Promise((resolve) => {
944
+ const release = () => {
945
+ clearTimeout(timer);
946
+ resolve();
947
+ };
948
+ const timer = setTimeout(release, timeoutMs);
949
+ timer.unref();
950
+ this.turnOpenWaiters.push(release);
951
+ });
952
+ return this.turnIsOpen();
953
+ }
954
+ /**
955
+ * B-628: send a message into the turn running right now (`turn/steer`, codex ≥ 0.148 /
956
+ * app-server-protocol v2 `TurnSteerParams`). Codex appends it to that turn's pending input
957
+ * and the model reads it before its next call; the turn keeps its own model, approval
958
+ * policy and sandbox — the runner applies any change on the next `turn/start`. Codex
959
+ * refuses before taking any input (expectedTurnId is a precondition), so a non-`steered`
960
+ * outcome always means "queue it for the next turn as before": `no-turn` (nothing running,
961
+ * or it ended while we waited), `unsupported` (this app-server has no `turn/steer`,
962
+ * remembered until it is respawned), `refused` (Codex declined this one), `failed`
963
+ * (transport — the turn itself is reported through its own events).
964
+ */
965
+ async steerTurn(input, opts) {
966
+ if (this.steerUnsupported) return "unsupported";
967
+ if (this.pendingInterrupt) return "no-turn";
968
+ const opened = await this.waitForTurnOpen(opts?.waitForTurnMs ?? CodexAppServerClient.STEER_OPEN_WAIT_MS);
969
+ const threadId = this._threadId;
970
+ const turnId = this._turnId;
971
+ if (!opened || !threadId || !turnId || !this.connected || this.pendingInterrupt) return "no-turn";
972
+ const params = {
973
+ threadId,
974
+ expectedTurnId: turnId,
975
+ ...input.clientId ? { clientUserMessageId: input.clientId } : {},
976
+ input: [
977
+ { type: "text", text: input.text },
978
+ ...(input.images ?? []).map((path) => ({ type: "localImage", path }))
979
+ ]
980
+ };
981
+ try {
982
+ const result = await this.request("turn/steer", params);
983
+ const steeredTurnId = typeof result?.turnId === "string" && result.turnId.length > 0 ? result.turnId : turnId;
984
+ if (input.clientId) this.steeredAwaitingEcho.set(input.clientId, steeredTurnId);
985
+ logger.debug(`[CodexAppServer] Steered a message into turn ${steeredTurnId}`);
986
+ return "steered";
987
+ } catch (error) {
988
+ const code = jsonRpcErrorCodeOf(error);
989
+ if (code === -32601) {
990
+ this.steerUnsupported = true;
991
+ logger.debug("[CodexAppServer] turn/steer is not supported by this codex; queuing instead");
992
+ return "unsupported";
993
+ }
994
+ if (code !== null) {
995
+ logger.debug("[CodexAppServer] turn/steer refused:", errorLogMetadata(error));
996
+ return "refused";
997
+ }
998
+ logger.debug("[CodexAppServer] turn/steer failed:", errorLogMetadata(error));
999
+ return "failed";
1000
+ }
1001
+ }
1002
+ /** Default wait for Codex's `turn/completed` after its thread reported idle/systemError. */
1003
+ static THREAD_STOP_GRACE_MS = 1500;
1004
+ /**
1005
+ * B-625: the own thread stopped (idle / systemError) while a turn was pending and
1006
+ * had opened. Codex's `turn/completed` follows at once in every probed release
1007
+ * (0.154.0: 1 ms); this is the net under a release that does not send it, so the
1008
+ * session never waits on the silence watchdog for a turn Codex has already left.
1009
+ */
1010
+ armThreadStopGrace(statusType) {
1011
+ this.clearThreadStopGrace();
1012
+ const graceMs = this.pendingTurnCompletion?.threadStopGraceMs ?? CodexAppServerClient.THREAD_STOP_GRACE_MS;
1013
+ this.threadStopGrace = setTimeout(() => {
1014
+ this.threadStopGrace = null;
1015
+ const pending = this.pendingTurnCompletion;
1016
+ if (!pending) return;
1017
+ const turnId = pending.turnId ?? this._turnId;
1018
+ const lastError = this.lastTurnError && (!turnId || !this.lastTurnError.turnId || this.lastTurnError.turnId === turnId) ? this.lastTurnError.error : null;
1019
+ logger.warn(`[CodexAppServer] Thread went ${statusType} and no turn/completed followed for turn ${turnId ?? "?"}; ending the turn`);
1020
+ const failed = statusType === "systemError" || lastError !== null;
1021
+ this.emitRawTurnCompletion(
1022
+ turnId,
1023
+ failed ? "failed" : "completed",
1024
+ lastError ?? (failed ? { message: "Codex reported a system error and ended the turn without a verdict." } : null),
1025
+ `thread/status/changed:${statusType}`
1026
+ );
1027
+ }, graceMs);
1028
+ }
1029
+ clearThreadStopGrace() {
1030
+ if (!this.threadStopGrace) return;
1031
+ clearTimeout(this.threadStopGrace);
1032
+ this.threadStopGrace = null;
672
1033
  }
673
1034
  tryResolvePendingTurn(aborted, turnId, source) {
674
1035
  const pending = this.pendingTurnCompletion;
@@ -771,6 +1132,8 @@ class CodexAppServerClient {
771
1132
  }
772
1133
  /** Default timeout for waiting on turn completion (ms). 10 minutes. */
773
1134
  static TURN_TIMEOUT_MS = 10 * 60 * 1e3;
1135
+ /** How much of the app-server's stderr is kept for its exit message. */
1136
+ static STDERR_TAIL_CHARS = 400;
774
1137
  /**
775
1138
  * Send a user turn and wait for it to complete (task_complete or turn_aborted).
776
1139
  * Returns { aborted: true } if the turn was aborted (user cancel, permission reject, etc.).
@@ -785,8 +1148,11 @@ class CodexAppServerClient {
785
1148
  const completion = new Promise((resolve) => {
786
1149
  this.pendingTurnCompletion = {
787
1150
  resolve,
788
- turnId: null
1151
+ turnId: null,
1152
+ opened: false,
1153
+ threadStopGraceMs: opts?.threadStopGraceMs ?? CodexAppServerClient.THREAD_STOP_GRACE_MS
789
1154
  };
1155
+ this.lastTurnError = null;
790
1156
  this.armTurnWatchdog(timeoutMs);
791
1157
  });
792
1158
  try {
@@ -794,6 +1160,7 @@ class CodexAppServerClient {
794
1160
  } catch (err) {
795
1161
  this.clearTurnWatchdog();
796
1162
  this.pendingTurnCompletion = null;
1163
+ this.releaseTurnOpenWaiters();
797
1164
  throw err;
798
1165
  }
799
1166
  const aborted = await completion;
@@ -811,6 +1178,7 @@ class CodexAppServerClient {
811
1178
  }
812
1179
  logger.warn(`[CodexAppServer] No notification from Codex for ${timeoutMs}ms \u2014 giving the turn up as aborted`);
813
1180
  const turnId = this.pendingTurnCompletion.turnId ?? this._turnId;
1181
+ this.dropUnechoedSteers(turnId, true);
814
1182
  this.eventHandler?.({
815
1183
  type: "turn_aborted",
816
1184
  reason: "timeout",
@@ -861,12 +1229,16 @@ class CodexAppServerClient {
861
1229
  logger.debug(
862
1230
  `[CodexAppServer] Clearing thread state: thread=${this._threadId ?? "none"} turn=${this._turnId ?? "none"}`
863
1231
  );
1232
+ this.dropUnechoedSteers(null, true);
864
1233
  this.resolvePendingTurn(true);
865
1234
  this._threadId = null;
866
1235
  this._turnId = null;
867
1236
  this.threadDefaults = null;
868
1237
  this.completedTurnIds.clear();
1238
+ this.failedTurnIds.clear();
1239
+ this.lastTurnError = null;
869
1240
  this.rawFileChangesByItemId.clear();
1241
+ this.childThreadLabels.clear();
870
1242
  }
871
1243
  // ─── JSON-RPC transport ─────────────────────────────────────
872
1244
  /** Default timeout for RPC requests (ms). */
@@ -947,7 +1319,9 @@ class CodexAppServerClient {
947
1319
  }
948
1320
  this.pending.delete(msg.id);
949
1321
  if (msg.error) {
950
- pending.reject(new Error(`${pending.method}: ${msg.error.message} (code=${msg.error.code})`));
1322
+ const error = new Error(`${pending.method}: ${msg.error.message} (code=${msg.error.code})`);
1323
+ if (typeof msg.error.code === "number") error.jsonRpcCode = msg.error.code;
1324
+ pending.reject(error);
951
1325
  } else {
952
1326
  pending.resolve(msg.result);
953
1327
  }
@@ -1134,6 +1508,7 @@ class CodexAppServerClient {
1134
1508
  }
1135
1509
  if (method === "thread/started" || method === "turn/started" || method === "turn/completed" || method === "thread/status/changed") {
1136
1510
  logger.debug(`[CodexAppServer] Lifecycle notification: ${method}`);
1511
+ if (!this.isOwnThreadFrame(params)) return;
1137
1512
  if (method === "turn/started") {
1138
1513
  const turnId = this.extractTurnId(params);
1139
1514
  if (turnId) {
@@ -1160,19 +1535,28 @@ class CodexAppServerClient {
1160
1535
  logger.debug(`[CodexAppServer] mcpServer startup status:`, params);
1161
1536
  return;
1162
1537
  }
1163
- if (method === "error" && params?.willRetry === true) {
1538
+ if (method === "error") {
1539
+ if (!this.isOwnThreadFrame(params)) return;
1164
1540
  const error = params?.error ?? {};
1165
- const message = typeof error.message === "string" ? error.message : "";
1166
- const info = error.codexErrorInfo;
1167
- const httpStatus = info && typeof info === "object" ? info.responseStreamDisconnected?.httpStatusCode ?? info.httpStatusCode ?? null : null;
1168
- const turnId = typeof params?.turnId === "string" ? params.turnId : null;
1169
- this.eventHandler?.({
1170
- type: "turn_retry",
1171
- message,
1172
- ...typeof httpStatus === "number" ? { http_status: httpStatus } : {},
1173
- ...turnId ? { turn_id: turnId } : {},
1174
- ...typeof error.additionalDetails === "string" ? { details: error.additionalDetails } : {}
1175
- });
1541
+ const turnId = typeof params?.turnId === "string" && params.turnId.length > 0 ? params.turnId : null;
1542
+ if (params?.willRetry === true) {
1543
+ const message = typeof error.message === "string" ? error.message : "";
1544
+ const info = error.codexErrorInfo;
1545
+ const httpStatus = info && typeof info === "object" ? info.responseStreamDisconnected?.httpStatusCode ?? info.httpStatusCode ?? null : null;
1546
+ this.eventHandler?.({
1547
+ type: "turn_retry",
1548
+ message,
1549
+ ...typeof httpStatus === "number" ? { http_status: httpStatus } : {},
1550
+ ...turnId ? { turn_id: turnId } : {},
1551
+ ...typeof error.additionalDetails === "string" ? { details: error.additionalDetails } : {}
1552
+ });
1553
+ return;
1554
+ }
1555
+ this.lastTurnError = { turnId, error };
1556
+ const pending = this.pendingTurnCompletion;
1557
+ if (pending && !pending.opened && (turnId === null || pending.turnId === null || pending.turnId === turnId)) {
1558
+ this.emitRawTurnCompletion(turnId ?? pending.turnId, "failed", error, "error:unopened-turn");
1559
+ }
1176
1560
  return;
1177
1561
  }
1178
1562
  logger.debug(`[CodexAppServer] Notification: ${method}`);