very-happy-cli 0.2.159 → 0.2.161

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 (259) hide show
  1. package/dist/{AcpBackend-VsfkZiVF.mjs → AcpBackend-6U6pQgx1.mjs} +4 -2
  2. package/dist/{AcpBackend-DUYFlyfN.cjs → AcpBackend-Ba98QDif.cjs} +78 -76
  3. package/dist/{AcpSessionManager-Bdv8LWXh.cjs → AcpSessionManager-BAY1Pggx.cjs} +9 -9
  4. package/dist/{AcpSessionManager-rYtVb0kN.mjs → AcpSessionManager-CoMWFVw3.mjs} +1 -1
  5. package/dist/BasePermissionHandler-2R0uH2CI.cjs +157 -0
  6. package/dist/BasePermissionHandler-BvvhBTja.mjs +155 -0
  7. package/dist/BaseReasoningProcessor-DyhO1uoU.mjs +198 -0
  8. package/dist/BaseReasoningProcessor-fsVNmCEF.cjs +200 -0
  9. package/dist/RemoteModeDisplay-D-HewyXg.mjs +150 -0
  10. package/dist/RemoteModeDisplay-NC1gPHtj.cjs +153 -0
  11. package/dist/agentAttachments-Dbw8iMCZ.cjs +20 -0
  12. package/dist/agentAttachments-M_B5qEF2.mjs +18 -0
  13. package/dist/agentGuidance-B5NATuFa.cjs +28 -0
  14. package/dist/agentGuidance-CQuNlNx8.mjs +20 -0
  15. package/dist/{agentHome-CvsJyeQB.mjs → agentHome-Ck1i1TQB.mjs} +8 -22
  16. package/dist/{agentHome-CHh4lmjM.cjs → agentHome-DvNnGphb.cjs} +14 -28
  17. package/dist/api-B2vLMS-z.cjs +3346 -0
  18. package/dist/api-SffjRQ1b.mjs +3339 -0
  19. package/dist/{types-Dh-s4Lqs.mjs → apiMachine-BpW9t8MO.mjs} +9116 -16205
  20. package/dist/{types-BbvK1Tr_.cjs → apiMachine-gFA54X7y.cjs} +9104 -16247
  21. package/dist/assistantTools-BfA0R3_m.cjs +380 -0
  22. package/dist/assistantTools-CgiTbnWb.mjs +376 -0
  23. package/dist/attachmentContent-CSGxr71v.mjs +82 -0
  24. package/dist/attachmentContent-DMMZzevR.cjs +88 -0
  25. package/dist/attachmentLimits-CzVrqgLR.cjs +354 -0
  26. package/dist/attachmentLimits-yba1uHFO.mjs +348 -0
  27. package/dist/auth-Bt4ceBBy.cjs +227 -0
  28. package/dist/auth-CYHjQ8oQ.mjs +225 -0
  29. package/dist/auth-DdyVZSo6.mjs +218 -0
  30. package/dist/auth-x4CEoysH.cjs +220 -0
  31. package/dist/authRelay-BdYkC1Po.cjs +156 -0
  32. package/dist/authRelay-DSAvg1uv.mjs +143 -0
  33. package/dist/{auto-DKmEls8Z.cjs → auto-BDXDUnjB.cjs} +70 -79
  34. package/dist/{auto-nb8GhRRy.mjs → auto-CXLlRi3a.mjs} +27 -36
  35. package/dist/backgroundTaskActivity-8PdPxrvy.mjs +149 -0
  36. package/dist/backgroundTaskActivity-CBvUdj39.cjs +157 -0
  37. package/dist/bootstrap-BNQq05jR.mjs +112 -0
  38. package/dist/bootstrap-CkgIq8N3.cjs +116 -0
  39. package/dist/browser-9pyhQr1R.mjs +20 -0
  40. package/dist/browser-BDOlKWYe.cjs +22 -0
  41. package/dist/claudeCliPath-BqkaH2oR.mjs +8 -0
  42. package/dist/claudeCliPath-U4HHmV4r.cjs +10 -0
  43. package/dist/claudeLocal-BDuGFnjJ.cjs +343 -0
  44. package/dist/claudeLocal-CQa8qnwq.mjs +339 -0
  45. package/dist/claudeLocalLauncher-Bd5I2-j6.cjs +154 -0
  46. package/dist/claudeLocalLauncher-CDqQI9ak.mjs +152 -0
  47. package/dist/cliUpdate-DsKXPFYp.cjs +120 -0
  48. package/dist/cliUpdate-Y34VGe9Y.mjs +112 -0
  49. package/dist/client-Brn-UwVk.mjs +100 -0
  50. package/dist/client-CXb1VJui.mjs +112 -0
  51. package/dist/client-C_axwRry.cjs +104 -0
  52. package/dist/client-DOBWJL3u.cjs +119 -0
  53. package/dist/codex/happyMcpStdioBridge.cjs +33 -14
  54. package/dist/codex/happyMcpStdioBridge.mjs +26 -7
  55. package/dist/codexAppServerClient-ZzhUTWPo.cjs +1090 -0
  56. package/dist/codexAppServerClient-oeQKD6Qf.mjs +1088 -0
  57. package/dist/codexCommand-C7H0NpBe.cjs +2088 -0
  58. package/dist/codexCommand-CJ82bl3l.mjs +2086 -0
  59. package/dist/codexThreadFork-BCaEMBiM.mjs +338 -0
  60. package/dist/codexThreadFork-BZqF-9bX.cjs +350 -0
  61. package/dist/{config-ClLtM6F3.mjs → config-BglnktIo.mjs} +7 -40
  62. package/dist/{config-DLx-vW3I.cjs → config-CYpL1Ahd.cjs} +26 -59
  63. package/dist/configuration-DoWFJUPm.mjs +170 -0
  64. package/dist/configuration-v9f8LM9T.cjs +181 -0
  65. package/dist/connect-hgcNjyAB.mjs +682 -0
  66. package/dist/connect-yEvjdRvx.cjs +684 -0
  67. package/dist/constants-BC4St0xd.mjs +11 -0
  68. package/dist/constants-VJ0N6WuG.cjs +17 -0
  69. package/dist/contentLogMetadata-BlWI0egu.mjs +51 -0
  70. package/dist/contentLogMetadata-ZSFk_NjQ.cjs +54 -0
  71. package/dist/context-B45vMtSZ.cjs +69 -0
  72. package/dist/context-C9GHPLEm.mjs +65 -0
  73. package/dist/controlClient-DjoKTtgK.mjs +260 -0
  74. package/dist/controlClient-fEHE85bH.cjs +279 -0
  75. package/dist/createSessionMetadata-BRLB9CNO.cjs +58 -0
  76. package/dist/createSessionMetadata-mIeehclp.mjs +54 -0
  77. package/dist/defaultPermissionMode-BnxJPVHT.mjs +4 -0
  78. package/dist/defaultPermissionMode-CxmTXUQR.cjs +7 -0
  79. package/dist/detectCLI-CSGMlV51.mjs +58 -0
  80. package/dist/detectCLI-DR9ZaN6B.cjs +61 -0
  81. package/dist/deterministicJson-DKDP_mM-.cjs +507 -0
  82. package/dist/deterministicJson-p835AdAU.mjs +502 -0
  83. package/dist/doctor-BPkYWSuq.mjs +75 -0
  84. package/dist/doctor-BeUuQXFs.mjs +391 -0
  85. package/dist/doctor-hVNnSZJn.cjs +395 -0
  86. package/dist/doctor-u46qYYKX.cjs +79 -0
  87. package/dist/doctorReadiness-BCZGcN0a.cjs +151 -0
  88. package/dist/doctorReadiness-C208SMN9.mjs +142 -0
  89. package/dist/editPaths-BVfS_FFL.mjs +68 -0
  90. package/dist/editPaths-Bqp4OFPw.cjs +73 -0
  91. package/dist/ensureDaemonRunning-CO7d1gOC.mjs +61 -0
  92. package/dist/ensureDaemonRunning-Dg9aZfjs.cjs +64 -0
  93. package/dist/handleResumeCommand-BiTPK0qd.mjs +240 -0
  94. package/dist/handleResumeCommand-DnVmy7a3.cjs +245 -0
  95. package/dist/ids-B6ZC0HGt.cjs +13 -0
  96. package/dist/ids-DU6sLx4C.mjs +10 -0
  97. package/dist/index-B6UGRQ7h.mjs +102 -0
  98. package/dist/{index-2mCaeKyf.mjs → index-CAFwetLk.mjs} +68 -38
  99. package/dist/index-Cqx-Raq6.mjs +68 -0
  100. package/dist/index-D3kI0hDR.cjs +107 -0
  101. package/dist/index-DA7yINuk.cjs +994 -0
  102. package/dist/{index-BEOMECtE.cjs → index-LIn6nCcq.cjs} +121 -91
  103. package/dist/index-Pf2cV9cn.cjs +182 -0
  104. package/dist/index-SPGPenK5.mjs +158 -0
  105. package/dist/index-gNTWtSKN.cjs +71 -0
  106. package/dist/index-qdS1friN.mjs +949 -0
  107. package/dist/index.cjs +5 -46
  108. package/dist/index.mjs +5 -46
  109. package/dist/{install-BPhJppvd.mjs → install-BO_W7VhL.mjs} +37 -37
  110. package/dist/install-Bu85vlHD.cjs +88 -0
  111. package/dist/install-ClUUl3yp.mjs +86 -0
  112. package/dist/{install-BOHrUaFM.cjs → install-Cs5qAPrB.cjs} +38 -38
  113. package/dist/{installPiTools-DuZ6jZvX.cjs → installPiTools-BKnXjrVu.cjs} +2 -3
  114. package/dist/{installPiTools-Cj7yU9Fc.mjs → installPiTools-B_CdM5_2.mjs} +1 -2
  115. package/dist/{installTerminalHooks-Cou3WUf3.cjs → installTerminalHooks-BQZbvxsZ.cjs} +5 -40
  116. package/dist/{installTerminalHooks-WeznjkuC.mjs → installTerminalHooks-By7gBMEF.mjs} +3 -38
  117. package/dist/lib.cjs +28 -19
  118. package/dist/lib.d.cts +12 -2
  119. package/dist/lib.d.mts +12 -2
  120. package/dist/lib.mjs +23 -14
  121. package/dist/lidState-C2dI0FAN.mjs +297 -0
  122. package/dist/lidState-KHHu1015.cjs +305 -0
  123. package/dist/limits-C-a7gr04.cjs +43 -0
  124. package/dist/limits-DIo7rEBR.mjs +36 -0
  125. package/dist/localHappyAgentAuth-BXiHPXdC.cjs +86 -0
  126. package/dist/localHappyAgentAuth-DCsetiyG.mjs +82 -0
  127. package/dist/logger-Bc8-u1w0.cjs +275 -0
  128. package/dist/logger-fXoNzNuB.mjs +271 -0
  129. package/dist/loginShellEnv-BkuYtExZ.cjs +182 -0
  130. package/dist/loginShellEnv-Dw8M7tkI.mjs +167 -0
  131. package/dist/machineMetadata-BHjehxJY.mjs +38 -0
  132. package/dist/machineMetadata-BibDMFVx.cjs +41 -0
  133. package/dist/main-BktM4A1K.mjs +939 -0
  134. package/dist/main-CLaX-t-b.cjs +941 -0
  135. package/dist/manager-BQ4flvQZ.mjs +101 -0
  136. package/dist/manager-Cly8xVIR.cjs +105 -0
  137. package/dist/{mcp-Dy3f_K0h.mjs → mcp-CeUd9KDr.mjs} +27 -33
  138. package/dist/{mcp-CPYbtsYG.cjs → mcp-GBfE70nk.cjs} +42 -48
  139. package/dist/messageBuffer-BWlGgR3A.mjs +71 -0
  140. package/dist/messageBuffer-fJMH6uY8.cjs +73 -0
  141. package/dist/mirrorProtocol-BNLtoWjO.mjs +170 -0
  142. package/dist/mirrorProtocol-BUtmg1ws.cjs +186 -0
  143. package/dist/oneShotPrompts-CL0dip9C.cjs +16 -0
  144. package/dist/oneShotPrompts-DT4p6RNd.mjs +12 -0
  145. package/dist/package-Cl5BEP5x.cjs +173 -0
  146. package/dist/package-uOg9SCKi.mjs +171 -0
  147. package/dist/path-B7zW-WwE.cjs +18 -0
  148. package/dist/path-D2wdaNSk.mjs +15 -0
  149. package/dist/peerTools-CIeiy9wn.cjs +283 -0
  150. package/dist/peerTools-Dz3_7Bkk.mjs +272 -0
  151. package/dist/permissionMode-CH4FoZfU.mjs +80 -0
  152. package/dist/permissionMode-CrV-XuMs.cjs +87 -0
  153. package/dist/persistence-CBNMzSa8.cjs +414 -0
  154. package/dist/persistence-oET5n61f.mjs +372 -0
  155. package/dist/piExtension.mjs +2 -1
  156. package/dist/piRunnerArgs-Bf7RUF__.cjs +66 -0
  157. package/dist/piRunnerArgs-Dk1ncwHF.mjs +59 -0
  158. package/dist/{piRuntime-NtF0YqjH.cjs → piRuntime-B_6TE9Eb.cjs} +4 -4
  159. package/dist/{piRuntime-kLZNpAAl.mjs → piRuntime-BwRdhK_N.mjs} +2 -2
  160. package/dist/{piTerminal-B0fEOV_2.mjs → piTerminal-Bap3R8Re.mjs} +32 -35
  161. package/dist/{piTerminal-9J0GAKzb.cjs → piTerminal-CagmEoez.cjs} +32 -35
  162. package/dist/previewPath-B0rBdhy1.cjs +83 -0
  163. package/dist/previewPath-CXN4Vag0.mjs +81 -0
  164. package/dist/projectPath-BTBIMB57.mjs +10 -0
  165. package/dist/projectPath-ChLhdOyt.cjs +13 -0
  166. package/dist/registerCommonHandlers-BjSV2Skz.cjs +538 -0
  167. package/dist/registerCommonHandlers-CrgvJsOy.mjs +536 -0
  168. package/dist/registerKillSessionHandler-6TC2S3lf.cjs +366 -0
  169. package/dist/registerKillSessionHandler-Cbc8Rgtd.mjs +363 -0
  170. package/dist/remoteSessionClient-BNx78NLb.cjs +634 -0
  171. package/dist/remoteSessionClient-CO7qOjR0.mjs +612 -0
  172. package/dist/resources-BdFBQi8r.mjs +117 -0
  173. package/dist/resources-JDPugcDq.cjs +120 -0
  174. package/dist/run-2l5vSt2h.mjs +5743 -0
  175. package/dist/run-D5xAEWSB.cjs +5766 -0
  176. package/dist/runClaude-BX6ixjtW.cjs +894 -0
  177. package/dist/runClaude-BgN6xo05.mjs +892 -0
  178. package/dist/runClaudeDeps-Bp20MScE.cjs +4168 -0
  179. package/dist/runClaudeDeps-COBBhD9o.mjs +4129 -0
  180. package/dist/{runGemini-F-mqx3_K.cjs → runGemini-CSMPkt0p.cjs} +202 -175
  181. package/dist/{runGemini-DV6Ouadt.mjs → runGemini-Cc2jq-uh.mjs} +58 -31
  182. package/dist/{runOpenClaw-D7MlPS5n.mjs → runOpenClaw-CIlCwdW2.mjs} +31 -32
  183. package/dist/{runOpenClaw-Qqo9j63M.cjs → runOpenClaw-YBwua4Rf.cjs} +64 -65
  184. package/dist/sandbox-C4gXLP1p.cjs +187 -0
  185. package/dist/sandbox-CNkwOtHI.mjs +180 -0
  186. package/dist/selfCheck-CU8xSDsl.cjs +87 -0
  187. package/dist/selfCheck-hJ6PCh7v.mjs +84 -0
  188. package/dist/{send-CLhYXNgy.mjs → send-CGHcKZ-w.mjs} +12 -5
  189. package/dist/{send-4qJTCAYW.cjs → send-DY9Nr-x4.cjs} +16 -9
  190. package/dist/server-II51Mimf.cjs +407 -0
  191. package/dist/server-m2b5pamZ.mjs +404 -0
  192. package/dist/sessionLock-7ePAYDOg.cjs +205 -0
  193. package/dist/sessionLock-Cw6FmbMv.mjs +197 -0
  194. package/dist/sessionModeFile-BVCV5f7E.cjs +36 -0
  195. package/dist/sessionModeFile-CDmDyRkD.mjs +32 -0
  196. package/dist/sessionOps-C_f1wKOr.mjs +760 -0
  197. package/dist/sessionOps-DJdExmBy.cjs +783 -0
  198. package/dist/sessionScanner-CaAHLyNV.mjs +206 -0
  199. package/dist/sessionScanner-D1goKdd3.cjs +209 -0
  200. package/dist/{sessions-Bh1pyddi.cjs → sessions-DGs6ALNo.cjs} +37 -28
  201. package/dist/{sessions-CzIH2rGp.mjs → sessions-DnvmWUel.mjs} +17 -8
  202. package/dist/setupOfflineReconnection-CVViqZft.mjs +72 -0
  203. package/dist/setupOfflineReconnection-R7XuR_sj.cjs +74 -0
  204. package/dist/smallFastModel-BLNgMrwR.mjs +18 -0
  205. package/dist/smallFastModel-D_uxGMo3.cjs +21 -0
  206. package/dist/{spawn-CmFOSVdW.cjs → spawn-CXTH7qg1.cjs} +61 -65
  207. package/dist/{spawn-CPIWlPOM.mjs → spawn-DNYSvFtC.mjs} +35 -40
  208. package/dist/spawnAgents-BGQiEF-w.cjs +52 -0
  209. package/dist/spawnAgents-DZzZ9jMI.mjs +45 -0
  210. package/dist/spawnHappyCLI-BhYj84rv.cjs +66 -0
  211. package/dist/spawnHappyCLI-cqWQbwsb.mjs +64 -0
  212. package/dist/specialCommands-BSqhYg5X.mjs +52 -0
  213. package/dist/specialCommands-DSsTeRej.cjs +54 -0
  214. package/dist/startFileWatcher-CKKx-8ZO.cjs +71 -0
  215. package/dist/startFileWatcher-CdegMbJj.mjs +69 -0
  216. package/dist/startupPlan-BLMbfQIH.cjs +12 -0
  217. package/dist/startupPlan-DFMyNgAs.mjs +10 -0
  218. package/dist/streamRelay-BiKw59Cc.cjs +233 -0
  219. package/dist/streamRelay-FPVbHpbM.mjs +231 -0
  220. package/dist/sync-CVk4QERO.cjs +71 -0
  221. package/dist/sync-CxD-w_40.mjs +69 -0
  222. package/dist/systemPrompt-BygJcmbi.cjs +97 -0
  223. package/dist/systemPrompt-DmtypUc7.mjs +94 -0
  224. package/dist/{teams-CEahj4cm.mjs → teams-BOC6nIPL.mjs} +9 -40
  225. package/dist/{teams-CB1R9-Z9.cjs → teams-BsvpOBfn.cjs} +14 -45
  226. package/dist/template-BaHAmH2K.cjs +129 -0
  227. package/dist/template-CiOPa2rd.mjs +121 -0
  228. package/dist/{terminalTitleSuggest-DKgX3osW.mjs → terminalTitleSuggest-CeKsX_V6.mjs} +1 -9
  229. package/dist/{terminalTitleSuggest-Dji8I0Jy.cjs → terminalTitleSuggest-DhvRFX3H.cjs} +0 -10
  230. package/dist/terminals-B_bfC9c2.cjs +250 -0
  231. package/dist/terminals-Dlg0x3nY.mjs +235 -0
  232. package/dist/time-BZNdNiIV.mjs +38 -0
  233. package/dist/time-CNRE4ChD.cjs +42 -0
  234. package/dist/titleGenerator-CFRhU-xq.cjs +172 -0
  235. package/dist/titleGenerator-ox6ptCsp.mjs +166 -0
  236. package/dist/tmuxSocket-B8Yx20sT.mjs +19 -0
  237. package/dist/tmuxSocket-D-Xt6Vn-.cjs +24 -0
  238. package/dist/{todo-DqyRI4wP.cjs → todo-B-KB7oAB.cjs} +15 -12
  239. package/dist/{todo-Cknp5D4U.mjs → todo-C5KBVbUw.mjs} +11 -8
  240. package/dist/tools-BZabNIid.mjs +177 -0
  241. package/dist/tools-BmQQUATY.mjs +60 -0
  242. package/dist/tools-CsSovyNp.cjs +180 -0
  243. package/dist/tools-lMDra4Pv.cjs +63 -0
  244. package/dist/trimIdent-4gvk9Wk6.cjs +22 -0
  245. package/dist/trimIdent-BAjmv_TG.mjs +20 -0
  246. package/dist/types-CRS-srZK.cjs +66 -0
  247. package/dist/types-CkqAACXx.mjs +64 -0
  248. package/dist/uninstall-B1WLtC1d.mjs +47 -0
  249. package/dist/uninstall-fvA5aWpU.cjs +49 -0
  250. package/package.json +7 -7
  251. package/scripts/session_hook_forwarder.cjs +9 -2
  252. package/dist/agentGuidance-BRDN1NoZ.cjs +0 -2217
  253. package/dist/agentGuidance-KgvYuF3R.mjs +0 -2145
  254. package/dist/index-BF6WfgV5.mjs +0 -19624
  255. package/dist/index-C-JazrMB.mjs +0 -52
  256. package/dist/index-blDSadUR.cjs +0 -58
  257. package/dist/index-rH3DrQ5B.cjs +0 -19688
  258. package/dist/limits-BH5CV5vR.cjs +0 -2683
  259. package/dist/limits-CjRj8IX5.mjs +0 -2558
@@ -0,0 +1,1090 @@
1
+ 'use strict';
2
+
3
+ var node_child_process = require('node:child_process');
4
+ var spawn = require('cross-spawn');
5
+ var node_readline = require('node:readline');
6
+ var logger = require('./logger-Bc8-u1w0.cjs');
7
+ var contentLogMetadata = require('./contentLogMetadata-ZSFk_NjQ.cjs');
8
+ var manager = require('./manager-Cly8xVIR.cjs');
9
+ var _package = require('./package-Cl5BEP5x.cjs');
10
+
11
+ function isAppServerAvailable() {
12
+ try {
13
+ const version = node_child_process.execSync("codex --version", { encoding: "utf8", windowsHide: true }).trim();
14
+ const match = version.match(/codex-cli\s+(\d+\.\d+\.\d+)/);
15
+ if (!match) return false;
16
+ const [, ver] = match;
17
+ const [major, minor] = ver.split(".").map(Number);
18
+ return major > 0 || minor >= 100;
19
+ } catch {
20
+ return false;
21
+ }
22
+ }
23
+ function normalizeRawFileChangeList(changes) {
24
+ if (!Array.isArray(changes)) {
25
+ return void 0;
26
+ }
27
+ const normalized = {};
28
+ for (const change of changes) {
29
+ if (!change || typeof change !== "object" || Array.isArray(change)) {
30
+ continue;
31
+ }
32
+ const path = typeof change.path === "string" ? change.path : null;
33
+ if (!path) {
34
+ continue;
35
+ }
36
+ const entry = {};
37
+ const changeRecord = change;
38
+ const kind = changeRecord.kind && typeof changeRecord.kind === "object" && !Array.isArray(changeRecord.kind) ? changeRecord.kind : null;
39
+ const type = typeof changeRecord.type === "string" ? changeRecord.type : typeof kind?.type === "string" ? kind.type : null;
40
+ const movePath = changeRecord.move_path ?? kind?.move_path ?? null;
41
+ if (kind) {
42
+ entry.kind = kind;
43
+ } else if (type) {
44
+ entry.kind = { type, move_path: movePath };
45
+ }
46
+ const diff = typeof changeRecord.diff === "string" ? changeRecord.diff : typeof changeRecord.unified_diff === "string" ? changeRecord.unified_diff : null;
47
+ if (diff !== null) {
48
+ entry.diff = diff;
49
+ }
50
+ if (changeRecord.add && typeof changeRecord.add === "object" && !Array.isArray(changeRecord.add)) {
51
+ entry.add = changeRecord.add;
52
+ }
53
+ if (changeRecord.modify && typeof changeRecord.modify === "object" && !Array.isArray(changeRecord.modify)) {
54
+ entry.modify = changeRecord.modify;
55
+ }
56
+ if (changeRecord.delete && typeof changeRecord.delete === "object" && !Array.isArray(changeRecord.delete)) {
57
+ entry.delete = changeRecord.delete;
58
+ }
59
+ const content = typeof changeRecord.content === "string" ? changeRecord.content : null;
60
+ if (type === "add" && content !== null) {
61
+ entry.add = { content };
62
+ }
63
+ if (type === "delete" && content !== null) {
64
+ entry.delete = { content };
65
+ }
66
+ const oldContent = typeof changeRecord.oldContent === "string" ? changeRecord.oldContent : typeof changeRecord.old_content === "string" ? changeRecord.old_content : null;
67
+ const newContent = typeof changeRecord.newContent === "string" ? changeRecord.newContent : typeof changeRecord.new_content === "string" ? changeRecord.new_content : null;
68
+ if ((oldContent !== null || newContent !== null) && type !== "add" && type !== "delete") {
69
+ entry.modify = {
70
+ old_content: oldContent ?? "",
71
+ new_content: newContent ?? ""
72
+ };
73
+ }
74
+ normalized[path] = entry;
75
+ }
76
+ return Object.keys(normalized).length > 0 ? normalized : void 0;
77
+ }
78
+ class CodexAppServerClient {
79
+ process = null;
80
+ readline = null;
81
+ nextId = 1;
82
+ pending = /* @__PURE__ */ new Map();
83
+ processEpoch = 0;
84
+ connected = false;
85
+ sandboxConfig;
86
+ sandboxCleanup = null;
87
+ sandboxEnabled = false;
88
+ // Session state
89
+ _threadId = null;
90
+ _turnId = null;
91
+ threadDefaults = null;
92
+ // Turn completion tracking for the currently active sendTurnAndWait call.
93
+ // A completion event only resolves once we have seen task_started for this turn.
94
+ pendingTurnCompletion = null;
95
+ // Tracks in-flight interruptTurn() RPCs so sendTurnAndWait can wait for them
96
+ // before starting a new turn (prevents stale turn/interrupt from aborting the next turn).
97
+ pendingInterrupt = null;
98
+ // In-flight idle release (B-461); ensureConnected() waits for it so a message
99
+ // arriving mid-release cannot spawn a second app-server against a dying one.
100
+ idleRelease = null;
101
+ notificationProtocol = "unknown";
102
+ completedTurnIds = /* @__PURE__ */ new Set();
103
+ rawFileChangesByItemId = /* @__PURE__ */ new Map();
104
+ // Handlers set by the consumer (runCodex.ts)
105
+ eventHandler = null;
106
+ approvalHandler = null;
107
+ constructor(sandboxConfig) {
108
+ this.sandboxConfig = sandboxConfig;
109
+ }
110
+ get threadId() {
111
+ return this._threadId;
112
+ }
113
+ get turnId() {
114
+ return this._turnId;
115
+ }
116
+ setEventHandler(handler) {
117
+ this.eventHandler = handler;
118
+ }
119
+ setApprovalHandler(handler) {
120
+ this.approvalHandler = handler;
121
+ }
122
+ extractTurnId(params) {
123
+ const turnId = params?.turn?.id ?? params?.turnId ?? params?.turn_id ?? null;
124
+ return typeof turnId === "string" && turnId.length > 0 ? turnId : null;
125
+ }
126
+ extractTurnStatus(params) {
127
+ const status = params?.turn?.status ?? params?.status ?? null;
128
+ return typeof status === "string" && status.length > 0 ? status : null;
129
+ }
130
+ shouldHandleRawNotification(method) {
131
+ const isRawNotification = method === "thread/started" || method === "turn/started" || method === "turn/completed" || method === "thread/status/changed" || method === "thread/tokenUsage/updated" || method.startsWith("item/");
132
+ if (!isRawNotification) {
133
+ return false;
134
+ }
135
+ if (this.notificationProtocol === "legacy") {
136
+ return false;
137
+ }
138
+ if (this.notificationProtocol === "unknown") {
139
+ this.notificationProtocol = "raw";
140
+ }
141
+ return true;
142
+ }
143
+ emitRawTurnCompletion(turnId, status, error, source) {
144
+ const aborted = status === "cancelled" || status === "canceled" || status === "aborted" || status === "interrupted";
145
+ this.tryResolvePendingTurn(aborted, turnId, source);
146
+ this._turnId = null;
147
+ if (turnId && this.completedTurnIds.has(turnId)) {
148
+ return;
149
+ }
150
+ if (turnId) {
151
+ this.completedTurnIds.add(turnId);
152
+ }
153
+ if (aborted) {
154
+ this.eventHandler?.({
155
+ type: "turn_aborted",
156
+ ...turnId ? { turn_id: turnId } : {},
157
+ ...status ? { status } : {},
158
+ ...error !== void 0 && error !== null ? { error } : {}
159
+ });
160
+ return;
161
+ }
162
+ this.eventHandler?.({
163
+ type: "task_complete",
164
+ ...turnId ? { turn_id: turnId } : {},
165
+ ...status ? { status } : {},
166
+ ...error !== void 0 && error !== null ? { error } : {}
167
+ });
168
+ }
169
+ handleRawNotification(method, params) {
170
+ if (!this.shouldHandleRawNotification(method)) {
171
+ return false;
172
+ }
173
+ if (method === "turn/started") {
174
+ const turnId = this.extractTurnId(params);
175
+ if (turnId) {
176
+ this._turnId = turnId;
177
+ }
178
+ this.markPendingTurnStarted(turnId);
179
+ this.eventHandler?.({
180
+ type: "task_started",
181
+ ...turnId ? { turn_id: turnId } : {}
182
+ });
183
+ return true;
184
+ }
185
+ if (method === "turn/completed") {
186
+ this.emitRawTurnCompletion(
187
+ this.extractTurnId(params),
188
+ this.extractTurnStatus(params),
189
+ params?.turn?.error ?? params?.error,
190
+ method
191
+ );
192
+ return true;
193
+ }
194
+ if (method === "thread/status/changed") {
195
+ const statusType = params?.status?.type;
196
+ if (statusType === "idle" && this.pendingTurnCompletion) {
197
+ this.emitRawTurnCompletion(this._turnId, "completed", null, method);
198
+ }
199
+ return true;
200
+ }
201
+ if (method === "thread/tokenUsage/updated") {
202
+ const tokenUsage = params?.tokenUsage;
203
+ if (tokenUsage && typeof tokenUsage === "object") {
204
+ this.eventHandler?.({
205
+ type: "token_count",
206
+ ...tokenUsage
207
+ });
208
+ }
209
+ return true;
210
+ }
211
+ const item = params?.item;
212
+ if (!item || typeof item !== "object") {
213
+ return method.startsWith("item/");
214
+ }
215
+ if ((method === "item/started" || method === "item/completed") && (item.type === "collabAgentToolCall" || item.type === "subAgentActivity")) {
216
+ this.eventHandler?.({ type: "collab_agent_item", item });
217
+ return true;
218
+ }
219
+ if (method === "item/started" && item.type === "commandExecution") {
220
+ const callId = typeof item.id === "string" ? item.id : "";
221
+ this.eventHandler?.({
222
+ type: "exec_command_begin",
223
+ call_id: callId,
224
+ callId,
225
+ command: item.command,
226
+ cwd: item.cwd,
227
+ description: item.command
228
+ });
229
+ return true;
230
+ }
231
+ if (method === "item/completed" && item.type === "commandExecution") {
232
+ const callId = typeof item.id === "string" ? item.id : "";
233
+ this.eventHandler?.({
234
+ type: "exec_command_end",
235
+ call_id: callId,
236
+ callId,
237
+ output: item.aggregatedOutput ?? "",
238
+ exit_code: item.exitCode ?? null,
239
+ duration_ms: item.durationMs ?? null,
240
+ status: item.status,
241
+ cwd: item.cwd,
242
+ command: item.command
243
+ });
244
+ return true;
245
+ }
246
+ if (item.type === "fileChange") {
247
+ const callId = typeof item.id === "string" ? item.id : "";
248
+ const changes = normalizeRawFileChangeList(item.changes);
249
+ if (callId && changes) {
250
+ this.rawFileChangesByItemId.set(callId, changes);
251
+ }
252
+ if (method === "item/started") {
253
+ this.eventHandler?.({
254
+ type: "patch_apply_begin",
255
+ call_id: callId,
256
+ callId,
257
+ changes: changes ?? {}
258
+ });
259
+ return true;
260
+ }
261
+ if (method === "item/completed") {
262
+ this.eventHandler?.({
263
+ type: "patch_apply_end",
264
+ call_id: callId,
265
+ callId,
266
+ status: item.status
267
+ });
268
+ if (callId && (item.status === "completed" || item.status === "failed" || item.status === "declined")) {
269
+ this.rawFileChangesByItemId.delete(callId);
270
+ }
271
+ return true;
272
+ }
273
+ }
274
+ if (method === "item/completed" && item.type === "agentMessage") {
275
+ const text = typeof item.text === "string" ? item.text : "";
276
+ if (text.length > 0) {
277
+ this.eventHandler?.({
278
+ type: "agent_message",
279
+ message: text,
280
+ item_id: item.id,
281
+ phase: item.phase
282
+ });
283
+ }
284
+ if (item.phase === "final_answer" && this.pendingTurnCompletion) {
285
+ this.emitRawTurnCompletion(
286
+ this.extractTurnId(params),
287
+ "completed",
288
+ null,
289
+ `${method}:final_answer`
290
+ );
291
+ }
292
+ return true;
293
+ }
294
+ return method.startsWith("item/");
295
+ }
296
+ // ─── Lifecycle ──────────────────────────────────────────────
297
+ async connect() {
298
+ if (this.connected) return;
299
+ if (!isAppServerAvailable()) {
300
+ throw new Error(
301
+ "Codex CLI is not installed\n\nPlease install Codex CLI using one of these methods:\n\nOption 1 - npm (recommended):\n npm install -g @openai/codex\n\nOption 2 - Homebrew (macOS):\n brew install --cask codex\n\nAlternatively, use Claude Code:\n very-happy claude"
302
+ );
303
+ }
304
+ let command = "codex";
305
+ let args = ["app-server", "--listen", "stdio://"];
306
+ this.sandboxEnabled = false;
307
+ if (this.sandboxConfig?.enabled && process.platform !== "win32") {
308
+ try {
309
+ this.sandboxCleanup = await manager.initializeSandbox(this.sandboxConfig, process.cwd());
310
+ const wrapped = await manager.wrapForMcpTransport("codex", ["app-server", "--listen", "stdio://"]);
311
+ command = wrapped.command;
312
+ args = wrapped.args;
313
+ this.sandboxEnabled = true;
314
+ logger.logger.info(`[CodexAppServer] Sandbox enabled`);
315
+ } catch (error) {
316
+ logger.logger.warn("[CodexAppServer] Failed to initialize sandbox; continuing without.", error);
317
+ this.sandboxCleanup = null;
318
+ }
319
+ }
320
+ const env = {};
321
+ for (const [key, value] of Object.entries(process.env)) {
322
+ if (typeof value === "string") env[key] = value;
323
+ }
324
+ const filter = "codex_core::rollout::list=off";
325
+ if (!env.RUST_LOG) {
326
+ env.RUST_LOG = filter;
327
+ } else if (!env.RUST_LOG.includes("codex_core::rollout::list=")) {
328
+ env.RUST_LOG += `,${filter}`;
329
+ }
330
+ if (this.sandboxEnabled) {
331
+ env.CODEX_SANDBOX = "seatbelt";
332
+ }
333
+ logger.logger.debug(`[CodexAppServer] Spawning: ${command} ${args.join(" ")}`);
334
+ const epoch = ++this.processEpoch;
335
+ const proc = spawn.spawn(command, args, {
336
+ stdio: ["pipe", "pipe", "pipe"],
337
+ env,
338
+ windowsHide: true
339
+ });
340
+ this.process = proc;
341
+ proc.on("error", (err) => {
342
+ logger.logger.debug("[CodexAppServer] Process error:", err);
343
+ });
344
+ proc.on("exit", (code, signal) => {
345
+ logger.logger.debug(`[CodexAppServer] Process exited: code=${code} signal=${signal}`);
346
+ if (this.process !== proc || this.processEpoch !== epoch) {
347
+ logger.logger.debug("[CodexAppServer] Ignoring stale process exit");
348
+ return;
349
+ }
350
+ this.connected = false;
351
+ for (const [id, req] of this.pending) {
352
+ if (req.epoch !== epoch) continue;
353
+ req.reject(new Error(`Codex process exited (code=${code}) while waiting for ${req.method}`));
354
+ this.pending.delete(id);
355
+ }
356
+ this.resolvePendingTurn(true);
357
+ });
358
+ proc.stderr?.on("data", (chunk) => {
359
+ if (this.process !== proc || this.processEpoch !== epoch) return;
360
+ const text = chunk.toString().trim();
361
+ if (text) logger.logger.debug("[CodexAppServer:stderr]", contentLogMetadata.contentLogMetadata(text));
362
+ });
363
+ this.readline = node_readline.createInterface({ input: proc.stdout });
364
+ this.readline.on("line", (line) => {
365
+ if (this.process !== proc || this.processEpoch !== epoch) return;
366
+ this.handleLine(line, epoch);
367
+ });
368
+ const initParams = {
369
+ clientInfo: {
370
+ name: "happy-codex",
371
+ title: "Happy Codex Client",
372
+ version: _package.packageJson.version
373
+ },
374
+ capabilities: {
375
+ experimentalApi: true
376
+ }
377
+ };
378
+ await this.request("initialize", initParams);
379
+ this.notify("initialized");
380
+ this.connected = true;
381
+ logger.logger.debug("[CodexAppServer] Connected and initialized");
382
+ }
383
+ async disconnectInternal(opts) {
384
+ if (!this.connected && !this.process) return;
385
+ const proc = this.process;
386
+ const pid = proc?.pid;
387
+ const epoch = this.processEpoch;
388
+ logger.logger.debug(`[CodexAppServer] Disconnecting; pid=${pid ?? "none"}`);
389
+ this.readline?.close();
390
+ this.readline = null;
391
+ try {
392
+ proc?.stdin?.end();
393
+ proc?.kill("SIGTERM");
394
+ } catch {
395
+ }
396
+ if (pid) {
397
+ const killTimer = setTimeout(() => {
398
+ try {
399
+ process.kill(pid, 0);
400
+ process.kill(pid, "SIGKILL");
401
+ } catch {
402
+ }
403
+ }, 2e3);
404
+ killTimer.unref();
405
+ }
406
+ this.process = null;
407
+ this.connected = false;
408
+ this._turnId = null;
409
+ this.notificationProtocol = "unknown";
410
+ this.completedTurnIds.clear();
411
+ if (!opts?.preserveThreadState) {
412
+ this._threadId = null;
413
+ this.threadDefaults = null;
414
+ }
415
+ for (const [id, req] of this.pending) {
416
+ if (req.epoch !== epoch) continue;
417
+ req.reject(new Error(`Codex process disconnected while waiting for ${req.method}`));
418
+ this.pending.delete(id);
419
+ }
420
+ this.resolvePendingTurn(true);
421
+ if (this.sandboxCleanup) {
422
+ try {
423
+ await this.sandboxCleanup();
424
+ } catch {
425
+ }
426
+ this.sandboxCleanup = null;
427
+ }
428
+ this.sandboxEnabled = false;
429
+ logger.logger.debug("[CodexAppServer] Disconnected");
430
+ }
431
+ async disconnect() {
432
+ await this.disconnectInternal();
433
+ }
434
+ /** True while an app-server child process is owned by this client. */
435
+ get hasProcess() {
436
+ return this.process !== null;
437
+ }
438
+ /**
439
+ * Drop the app-server process while the session is idle, keeping the thread
440
+ * id and defaults so the next turn can `thread/resume` it (B-461). Refuses —
441
+ * returning false — whenever anything is still in flight: an active turn, a
442
+ * pending interrupt, or any outstanding RPC. See codexIdleRelease.ts.
443
+ */
444
+ async releaseIdleProcess() {
445
+ if (this.idleRelease) return this.idleRelease;
446
+ if (!this.connected && !this.process) return false;
447
+ if (this.pendingTurnCompletion || this.pendingInterrupt || this.pending.size > 0) {
448
+ logger.logger.debug("[CodexAppServer] Idle release skipped: work still in flight");
449
+ return false;
450
+ }
451
+ const run = (async () => {
452
+ const threadId = this._threadId;
453
+ logger.logger.debug(`[CodexAppServer] Releasing idle app-server; thread=${threadId ?? "none"} kept for resume`);
454
+ await this.disconnectInternal({ preserveThreadState: !!threadId });
455
+ return true;
456
+ })();
457
+ this.idleRelease = run;
458
+ try {
459
+ return await run;
460
+ } finally {
461
+ if (this.idleRelease === run) this.idleRelease = null;
462
+ }
463
+ }
464
+ /**
465
+ * Make sure an app-server is running and, if a thread is remembered, resumed
466
+ * in it. Cheap no-op while connected. Covers both the idle release above and
467
+ * an app-server that exited on its own between turns.
468
+ */
469
+ async ensureConnected() {
470
+ if (this.idleRelease) await this.idleRelease;
471
+ if (this.connected) return;
472
+ const threadId = this._threadId;
473
+ if (this.process) {
474
+ await this.disconnectInternal({ preserveThreadState: !!threadId });
475
+ }
476
+ await this.connect();
477
+ if (threadId) {
478
+ await this.resumeThread({ threadId });
479
+ }
480
+ }
481
+ buildThreadConfig(mcpServers) {
482
+ return mcpServers ? { mcp_servers: mcpServers } : null;
483
+ }
484
+ rememberThreadDefaults(opts) {
485
+ this.threadDefaults = {
486
+ model: opts.model,
487
+ cwd: opts.cwd,
488
+ approvalPolicy: opts.approvalPolicy,
489
+ sandbox: opts.sandbox,
490
+ mcpServers: opts.mcpServers
491
+ };
492
+ }
493
+ // ─── Thread management ──────────────────────────────────────
494
+ async startThread(opts) {
495
+ await this.ensureConnected();
496
+ const params = {
497
+ model: opts.model ?? null,
498
+ modelProvider: null,
499
+ profile: null,
500
+ cwd: opts.cwd ?? process.cwd(),
501
+ approvalPolicy: opts.approvalPolicy ?? null,
502
+ sandbox: opts.sandbox ?? null,
503
+ config: this.buildThreadConfig(opts.mcpServers),
504
+ baseInstructions: null,
505
+ developerInstructions: null,
506
+ compactPrompt: null,
507
+ includeApplyPatchTool: null,
508
+ experimentalRawEvents: false,
509
+ persistExtendedHistory: true
510
+ };
511
+ const result = await this.request("thread/start", params);
512
+ this._threadId = result.thread.id;
513
+ this._turnId = null;
514
+ this.rememberThreadDefaults(opts);
515
+ logger.logger.debug("[CodexAppServer] Thread started:", this._threadId);
516
+ return { threadId: result.thread.id, model: result.model };
517
+ }
518
+ async resumeThread(opts) {
519
+ const threadId = opts?.threadId ?? this._threadId;
520
+ if (!threadId) {
521
+ throw new Error("No thread available to resume.");
522
+ }
523
+ await this.ensureConnected();
524
+ const defaults = this.threadDefaults ?? {};
525
+ const params = {
526
+ threadId,
527
+ model: opts?.model ?? defaults.model ?? null,
528
+ modelProvider: null,
529
+ cwd: opts?.cwd ?? defaults.cwd ?? process.cwd(),
530
+ approvalPolicy: opts?.approvalPolicy ?? defaults.approvalPolicy ?? null,
531
+ sandbox: opts?.sandbox ?? defaults.sandbox ?? null,
532
+ config: this.buildThreadConfig(opts?.mcpServers ?? defaults.mcpServers),
533
+ baseInstructions: null,
534
+ developerInstructions: null,
535
+ persistExtendedHistory: true
536
+ };
537
+ const result = await this.request("thread/resume", params);
538
+ this._threadId = result.thread.id;
539
+ this._turnId = null;
540
+ this.rememberThreadDefaults({
541
+ model: opts?.model ?? defaults.model,
542
+ cwd: opts?.cwd ?? defaults.cwd,
543
+ approvalPolicy: opts?.approvalPolicy ?? defaults.approvalPolicy,
544
+ sandbox: opts?.sandbox ?? defaults.sandbox,
545
+ mcpServers: opts?.mcpServers ?? defaults.mcpServers
546
+ });
547
+ logger.logger.debug("[CodexAppServer] Thread resumed:", this._threadId);
548
+ return { threadId: result.thread.id, model: result.model };
549
+ }
550
+ async forkThread(opts) {
551
+ await this.ensureConnected();
552
+ const defaults = this.threadDefaults ?? {};
553
+ const params = {
554
+ threadId: opts.threadId,
555
+ model: opts.model ?? defaults.model ?? null,
556
+ modelProvider: null,
557
+ cwd: opts.cwd ?? defaults.cwd ?? process.cwd(),
558
+ approvalPolicy: opts.approvalPolicy ?? defaults.approvalPolicy ?? null,
559
+ sandbox: opts.sandbox ?? defaults.sandbox ?? null,
560
+ config: this.buildThreadConfig(opts.mcpServers ?? defaults.mcpServers),
561
+ baseInstructions: null,
562
+ developerInstructions: null,
563
+ ephemeral: false,
564
+ threadSource: null
565
+ };
566
+ const result = await this.request("thread/fork", params);
567
+ this._threadId = result.thread.id;
568
+ this._turnId = null;
569
+ this.rememberThreadDefaults({
570
+ model: opts.model ?? defaults.model,
571
+ cwd: opts.cwd ?? defaults.cwd,
572
+ approvalPolicy: opts.approvalPolicy ?? defaults.approvalPolicy,
573
+ sandbox: opts.sandbox ?? defaults.sandbox,
574
+ mcpServers: opts.mcpServers ?? defaults.mcpServers
575
+ });
576
+ logger.logger.debug("[CodexAppServer] Thread forked:", opts.threadId, "->", this._threadId);
577
+ return { threadId: result.thread.id, model: result.model, thread: result.thread };
578
+ }
579
+ async readThread(opts) {
580
+ await this.ensureConnected();
581
+ const params = {
582
+ threadId: opts.threadId,
583
+ includeTurns: opts.includeTurns ?? true
584
+ };
585
+ return await this.request("thread/read", params);
586
+ }
587
+ async rollbackThread(opts) {
588
+ await this.ensureConnected();
589
+ const params = {
590
+ threadId: opts.threadId,
591
+ numTurns: opts.numTurns
592
+ };
593
+ return await this.request("thread/rollback", params);
594
+ }
595
+ async injectItems(opts) {
596
+ await this.ensureConnected();
597
+ const params = {
598
+ threadId: opts.threadId,
599
+ items: opts.items
600
+ };
601
+ return await this.request("thread/inject_items", params);
602
+ }
603
+ async reconnectAndResumeThread() {
604
+ const threadId = this._threadId;
605
+ await this.disconnectInternal({ preserveThreadState: !!threadId });
606
+ await this.connect();
607
+ if (!threadId) {
608
+ return false;
609
+ }
610
+ try {
611
+ await this.resumeThread({ threadId });
612
+ return true;
613
+ } catch (error) {
614
+ logger.logger.warn("[CodexAppServer] Failed to resume thread after reconnect", error);
615
+ this._threadId = null;
616
+ this.threadDefaults = null;
617
+ return false;
618
+ }
619
+ }
620
+ // ─── Turn management ────────────────────────────────────────
621
+ /** Default grace period after interrupt before forcing a restart (ms). */
622
+ static ABORT_GRACE_MS = 3e3;
623
+ hasPendingTurnCompletion() {
624
+ return this.pendingTurnCompletion !== null;
625
+ }
626
+ resolvePendingTurn(aborted) {
627
+ if (!this.pendingTurnCompletion) return;
628
+ this.pendingTurnCompletion.resolve(aborted);
629
+ this.pendingTurnCompletion = null;
630
+ }
631
+ markPendingTurnStarted(turnId) {
632
+ if (!this.pendingTurnCompletion) return;
633
+ if (turnId) {
634
+ this.pendingTurnCompletion.turnId = turnId;
635
+ }
636
+ }
637
+ tryResolvePendingTurn(aborted, turnId, source) {
638
+ const pending = this.pendingTurnCompletion;
639
+ if (!pending) return;
640
+ if (pending.turnId && turnId && pending.turnId !== turnId) {
641
+ logger.logger.debug(
642
+ `[CodexAppServer] Ignoring ${source} for turn ${turnId}; awaiting ${pending.turnId}`
643
+ );
644
+ return;
645
+ }
646
+ this.resolvePendingTurn(aborted);
647
+ }
648
+ async waitForTurnCompletion(timeoutMs) {
649
+ if (!this.hasPendingTurnCompletion()) {
650
+ return true;
651
+ }
652
+ const deadline = Date.now() + Math.max(0, timeoutMs);
653
+ while (this.hasPendingTurnCompletion()) {
654
+ if (Date.now() >= deadline) {
655
+ return false;
656
+ }
657
+ await new Promise((resolve) => setTimeout(resolve, 25));
658
+ }
659
+ return true;
660
+ }
661
+ /**
662
+ * Request turn interruption and optionally force-restart the app-server if
663
+ * the turn does not settle within a short grace period.
664
+ */
665
+ async abortTurnWithFallback(opts) {
666
+ const hadActiveTurn = this.hasPendingTurnCompletion();
667
+ if (!hadActiveTurn) {
668
+ return { hadActiveTurn: false, aborted: false, forcedRestart: false, resumedThread: false };
669
+ }
670
+ await this.interruptTurn();
671
+ const gracePeriodMs = opts?.gracePeriodMs ?? CodexAppServerClient.ABORT_GRACE_MS;
672
+ const settled = await this.waitForTurnCompletion(gracePeriodMs);
673
+ if (settled) {
674
+ return { hadActiveTurn: true, aborted: true, forcedRestart: false, resumedThread: false };
675
+ }
676
+ const shouldForceRestart = opts?.forceRestartOnTimeout ?? true;
677
+ if (!shouldForceRestart) {
678
+ return { hadActiveTurn: true, aborted: false, forcedRestart: false, resumedThread: false };
679
+ }
680
+ logger.logger.warn(`[CodexAppServer] interrupt did not settle turn in ${gracePeriodMs}ms; force-restarting app-server`);
681
+ const pendingTurnId = this.pendingTurnCompletion?.turnId ?? this._turnId;
682
+ if (this.pendingTurnCompletion) {
683
+ this.eventHandler?.({
684
+ type: "turn_aborted",
685
+ reason: "interrupted",
686
+ ...pendingTurnId ? { turn_id: pendingTurnId } : {},
687
+ forced_restart: true
688
+ });
689
+ }
690
+ const resumedThread = await this.reconnectAndResumeThread();
691
+ return { hadActiveTurn: true, aborted: true, forcedRestart: true, resumedThread };
692
+ }
693
+ /**
694
+ * Send a user turn and wait for it to complete.
695
+ * Returns when task_complete or turn_aborted is received.
696
+ */
697
+ async sendTurn(prompt, opts) {
698
+ if (!this._threadId) {
699
+ throw new Error("No active thread. Call startThread first.");
700
+ }
701
+ await this.ensureConnected();
702
+ const input = [
703
+ { type: "text", text: prompt },
704
+ ...(opts?.images ?? []).map((path) => ({ type: "localImage", path }))
705
+ ];
706
+ const params = {
707
+ threadId: this._threadId,
708
+ input
709
+ };
710
+ if (opts?.cwd) params.cwd = opts.cwd;
711
+ if (opts?.approvalPolicy) params.approvalPolicy = opts.approvalPolicy;
712
+ if (opts?.model) params.model = opts.model;
713
+ if (opts?.effort) params.effort = opts.effort;
714
+ if (opts?.sandbox) {
715
+ switch (opts.sandbox) {
716
+ case "workspace-write":
717
+ params.sandboxPolicy = { type: "workspaceWrite" };
718
+ break;
719
+ case "danger-full-access":
720
+ params.sandboxPolicy = { type: "dangerFullAccess" };
721
+ break;
722
+ case "read-only":
723
+ params.sandboxPolicy = { type: "readOnly" };
724
+ break;
725
+ }
726
+ }
727
+ const result = await this.request("turn/start", params);
728
+ const turnId = result?.turn?.id;
729
+ if (typeof turnId === "string" && turnId.length > 0) {
730
+ this._turnId = turnId;
731
+ if (this.pendingTurnCompletion) {
732
+ this.pendingTurnCompletion.turnId = turnId;
733
+ }
734
+ }
735
+ }
736
+ /** Default timeout for waiting on turn completion (ms). 10 minutes. */
737
+ static TURN_TIMEOUT_MS = 10 * 60 * 1e3;
738
+ /**
739
+ * Send a user turn and wait for it to complete (task_complete or turn_aborted).
740
+ * Returns { aborted: true } if the turn was aborted (user cancel, permission reject, etc.).
741
+ */
742
+ async sendTurnAndWait(prompt, opts) {
743
+ if (this.pendingInterrupt) {
744
+ await this.pendingInterrupt;
745
+ await new Promise((resolve) => setTimeout(resolve, 0));
746
+ }
747
+ await this.ensureConnected();
748
+ const timeoutMs = opts?.turnTimeoutMs ?? CodexAppServerClient.TURN_TIMEOUT_MS;
749
+ let timer = null;
750
+ const completion = new Promise((resolve) => {
751
+ this.pendingTurnCompletion = {
752
+ resolve,
753
+ turnId: null
754
+ };
755
+ timer = setTimeout(() => {
756
+ if (this.pendingTurnCompletion) {
757
+ logger.logger.warn(`[CodexAppServer] Turn timed out after ${timeoutMs}ms \u2014 treating as abort`);
758
+ this.resolvePendingTurn(true);
759
+ }
760
+ }, timeoutMs);
761
+ });
762
+ try {
763
+ await this.sendTurn(prompt, opts);
764
+ } catch (err) {
765
+ if (timer) clearTimeout(timer);
766
+ this.pendingTurnCompletion = null;
767
+ throw err;
768
+ }
769
+ const aborted = await completion;
770
+ if (timer) clearTimeout(timer);
771
+ return { aborted };
772
+ }
773
+ async interruptTurn() {
774
+ if (!this._threadId) return;
775
+ if (!this._turnId) {
776
+ logger.logger.debug("[CodexAppServer] interruptTurn: no active turnId, skipping");
777
+ return;
778
+ }
779
+ const params = {
780
+ threadId: this._threadId,
781
+ turnId: this._turnId
782
+ };
783
+ const doInterrupt = async () => {
784
+ try {
785
+ await this.request("turn/interrupt", params);
786
+ } catch (err) {
787
+ logger.logger.debug("[CodexAppServer] interruptTurn error (may be expected):", err);
788
+ } finally {
789
+ this.pendingInterrupt = null;
790
+ }
791
+ };
792
+ this.pendingInterrupt = doInterrupt();
793
+ return this.pendingInterrupt;
794
+ }
795
+ // ─── State queries ──────────────────────────────────────────
796
+ hasActiveThread() {
797
+ return this._threadId !== null;
798
+ }
799
+ clearThreadState() {
800
+ logger.logger.debug(
801
+ `[CodexAppServer] Clearing thread state: thread=${this._threadId ?? "none"} turn=${this._turnId ?? "none"}`
802
+ );
803
+ this.resolvePendingTurn(true);
804
+ this._threadId = null;
805
+ this._turnId = null;
806
+ this.threadDefaults = null;
807
+ this.completedTurnIds.clear();
808
+ this.rawFileChangesByItemId.clear();
809
+ }
810
+ // ─── JSON-RPC transport ─────────────────────────────────────
811
+ /** Default timeout for RPC requests (ms). */
812
+ static REQUEST_TIMEOUT_MS = 3e4;
813
+ async listModels() {
814
+ const models = [];
815
+ let cursor = null;
816
+ const seenCursors = /* @__PURE__ */ new Set();
817
+ do {
818
+ const response = await this.request("model/list", { limit: 100, cursor, includeHidden: false });
819
+ models.push(...response.data.filter((model) => !model.hidden));
820
+ cursor = response.nextCursor ?? null;
821
+ if (cursor && seenCursors.has(cursor)) throw new Error("Repeated model/list cursor");
822
+ if (cursor) seenCursors.add(cursor);
823
+ } while (cursor);
824
+ return models;
825
+ }
826
+ request(method, params, timeoutMs) {
827
+ const timeout = timeoutMs ?? CodexAppServerClient.REQUEST_TIMEOUT_MS;
828
+ return new Promise((resolve, reject) => {
829
+ if (!this.process?.stdin?.writable) {
830
+ reject(new Error(`Cannot send ${method}: stdin not writable`));
831
+ return;
832
+ }
833
+ const id = this.nextId++;
834
+ const timer = setTimeout(() => {
835
+ this.pending.delete(id);
836
+ reject(new Error(`${method} timed out after ${timeout}ms (id=${id})`));
837
+ }, timeout);
838
+ this.pending.set(id, {
839
+ resolve: (result) => {
840
+ clearTimeout(timer);
841
+ resolve(result);
842
+ },
843
+ reject: (err) => {
844
+ clearTimeout(timer);
845
+ reject(err);
846
+ },
847
+ method,
848
+ epoch: this.processEpoch
849
+ });
850
+ const msg = { jsonrpc: "2.0", id, method, params };
851
+ const line = JSON.stringify(msg) + "\n";
852
+ logger.logger.debug(`[CodexAppServer] \u2192 ${method} (id=${id})`);
853
+ this.process.stdin.write(line);
854
+ });
855
+ }
856
+ notify(method, params) {
857
+ if (!this.process?.stdin?.writable) return;
858
+ const msg = { jsonrpc: "2.0", method, params };
859
+ this.process.stdin.write(JSON.stringify(msg) + "\n");
860
+ logger.logger.debug(`[CodexAppServer] \u2192 ${method} (notification)`);
861
+ }
862
+ respond(id, result) {
863
+ if (!this.process?.stdin?.writable) return;
864
+ const msg = { jsonrpc: "2.0", id, result };
865
+ this.process.stdin.write(JSON.stringify(msg) + "\n");
866
+ logger.logger.debug(`[CodexAppServer] \u2192 response (id=${id})`);
867
+ }
868
+ handleLine(line, sourceEpoch = this.processEpoch) {
869
+ if (sourceEpoch !== this.processEpoch) {
870
+ return;
871
+ }
872
+ if (!line.trim()) return;
873
+ let msg;
874
+ try {
875
+ msg = JSON.parse(line);
876
+ } catch {
877
+ logger.logger.debug("[CodexAppServer] Non-JSON line received:", contentLogMetadata.contentLogMetadata(line));
878
+ return;
879
+ }
880
+ if (msg.id != null && (msg.result !== void 0 || msg.error !== void 0)) {
881
+ const pending = this.pending.get(msg.id);
882
+ if (pending) {
883
+ if (pending.epoch !== sourceEpoch) {
884
+ logger.logger.debug(`[CodexAppServer] Ignoring response from stale epoch for id=${msg.id}`);
885
+ return;
886
+ }
887
+ this.pending.delete(msg.id);
888
+ if (msg.error) {
889
+ pending.reject(new Error(`${pending.method}: ${msg.error.message} (code=${msg.error.code})`));
890
+ } else {
891
+ pending.resolve(msg.result);
892
+ }
893
+ }
894
+ return;
895
+ }
896
+ if (msg.id != null && msg.method) {
897
+ this.handleServerRequest(msg.id, msg.method, msg.params).catch((err) => {
898
+ logger.logger.debug("[CodexAppServer] Error handling server request:", contentLogMetadata.errorLogMetadata(err));
899
+ });
900
+ return;
901
+ }
902
+ if (msg.method) {
903
+ this.handleNotification(msg.method, msg.params);
904
+ return;
905
+ }
906
+ logger.logger.debug("[CodexAppServer] Unhandled message:", contentLogMetadata.contentLogMetadata(msg));
907
+ }
908
+ /**
909
+ * Map our internal ReviewDecision to the wire format the server expects.
910
+ * Server uses: accept, acceptForSession, decline, cancel
911
+ * Our handler uses: approved, approved_for_session, denied, abort
912
+ */
913
+ /**
914
+ * Map our internal ReviewDecision to the wire format codex expects.
915
+ * v2 methods (item/*) use: accept/acceptForSession/decline/cancel
916
+ * Legacy methods (execCommandApproval/applyPatchApproval) use: approved/approved_for_session/denied/abort
917
+ */
918
+ mapDecisionToWire(decision, legacy) {
919
+ if (typeof decision === "string") {
920
+ if (legacy) {
921
+ return decision;
922
+ }
923
+ switch (decision) {
924
+ case "approved":
925
+ return "accept";
926
+ case "approved_for_session":
927
+ return "acceptForSession";
928
+ case "denied":
929
+ return "decline";
930
+ case "abort":
931
+ return "cancel";
932
+ default:
933
+ return "decline";
934
+ }
935
+ }
936
+ if ("approved_execpolicy_amendment" in decision) {
937
+ return decision;
938
+ }
939
+ return legacy ? "denied" : "decline";
940
+ }
941
+ parseToolNameFromElicitationMessage(message) {
942
+ if (typeof message !== "string") {
943
+ return null;
944
+ }
945
+ const match = message.match(/tool "([^"]+)"/i);
946
+ return match?.[1] ?? null;
947
+ }
948
+ mapDecisionToMcpElicitationResponse(decision, params) {
949
+ if (typeof decision === "string") {
950
+ switch (decision) {
951
+ case "approved":
952
+ case "approved_for_session":
953
+ return {
954
+ action: "accept",
955
+ content: params?.mode === "form" ? {} : null,
956
+ _meta: null
957
+ };
958
+ case "abort":
959
+ return {
960
+ action: "cancel",
961
+ content: null,
962
+ _meta: null
963
+ };
964
+ case "denied":
965
+ default:
966
+ return {
967
+ action: "decline",
968
+ content: null,
969
+ _meta: null
970
+ };
971
+ }
972
+ }
973
+ return {
974
+ action: "decline",
975
+ content: null,
976
+ _meta: null
977
+ };
978
+ }
979
+ async handleServerRequest(id, method, params) {
980
+ if (method === "mcpServer/elicitation/request") {
981
+ const toolName = this.parseToolNameFromElicitationMessage(params?.message) ?? params?.serverName ?? "McpTool";
982
+ const decision = await this.handleApproval({
983
+ type: "mcp",
984
+ callId: `${params?.serverName ?? "mcp"}:${id}`,
985
+ toolName,
986
+ input: params?._meta?.tool_params ?? {},
987
+ serverName: params?.serverName,
988
+ message: params?.message
989
+ });
990
+ this.respond(id, this.mapDecisionToMcpElicitationResponse(decision, params));
991
+ return;
992
+ }
993
+ if (method === "item/commandExecution/requestApproval" || method === "execCommandApproval") {
994
+ const legacy = method === "execCommandApproval";
995
+ const callId = params.itemId ?? params.callId ?? String(id);
996
+ const decision = await this.handleApproval({
997
+ type: "exec",
998
+ callId,
999
+ command: params.command != null ? [params.command] : [],
1000
+ cwd: params.cwd,
1001
+ reason: params.reason
1002
+ });
1003
+ this.respond(id, { decision: this.mapDecisionToWire(decision, legacy) });
1004
+ return;
1005
+ }
1006
+ if (method === "item/fileChange/requestApproval" || method === "applyPatchApproval") {
1007
+ const legacy = method === "applyPatchApproval";
1008
+ const callId = params.itemId ?? params.callId ?? String(id);
1009
+ const decision = await this.handleApproval({
1010
+ type: "patch",
1011
+ callId,
1012
+ fileChanges: params.fileChanges ?? (typeof callId === "string" ? this.rawFileChangesByItemId.get(callId) : void 0),
1013
+ reason: params.reason
1014
+ });
1015
+ this.respond(id, { decision: this.mapDecisionToWire(decision, legacy) });
1016
+ return;
1017
+ }
1018
+ logger.logger.debug(`[CodexAppServer] Unknown server request: ${method}`);
1019
+ this.respond(id, {});
1020
+ }
1021
+ async handleApproval(params) {
1022
+ if (this.approvalHandler) {
1023
+ try {
1024
+ return await this.approvalHandler(params);
1025
+ } catch (err) {
1026
+ logger.logger.debug("[CodexAppServer] Approval handler error:", err);
1027
+ return "denied";
1028
+ }
1029
+ }
1030
+ return "denied";
1031
+ }
1032
+ handleNotification(method, params) {
1033
+ if (method === "codex/event" || method.startsWith("codex/event/")) {
1034
+ this.notificationProtocol = "legacy";
1035
+ const msg = params?.msg;
1036
+ if (msg) {
1037
+ if (msg.type === "task_started" && msg.turn_id) {
1038
+ this._turnId = msg.turn_id;
1039
+ }
1040
+ if (msg.type === "task_started") {
1041
+ this.markPendingTurnStarted(msg.turn_id ?? msg.turnId ?? null);
1042
+ }
1043
+ this.eventHandler?.(msg);
1044
+ if (msg.type === "task_complete" || msg.type === "turn_aborted") {
1045
+ const turnId = msg.turn_id ?? msg.turnId ?? null;
1046
+ if (turnId) {
1047
+ this.completedTurnIds.add(turnId);
1048
+ }
1049
+ this.tryResolvePendingTurn(
1050
+ msg.type === "turn_aborted",
1051
+ turnId,
1052
+ `codex/event/${msg.type}`
1053
+ );
1054
+ this._turnId = null;
1055
+ }
1056
+ }
1057
+ return;
1058
+ }
1059
+ if (this.handleRawNotification(method, params)) {
1060
+ logger.logger.debug(`[CodexAppServer] Raw notification: ${method}`);
1061
+ return;
1062
+ }
1063
+ if (method === "thread/started" || method === "turn/started" || method === "turn/completed" || method === "thread/status/changed") {
1064
+ logger.logger.debug(`[CodexAppServer] Lifecycle notification: ${method}`);
1065
+ if (method === "turn/started") {
1066
+ const turnId = this.extractTurnId(params);
1067
+ if (turnId) {
1068
+ this._turnId = turnId;
1069
+ }
1070
+ this.markPendingTurnStarted(turnId);
1071
+ }
1072
+ if (method === "turn/completed") {
1073
+ this.emitRawTurnCompletion(
1074
+ this.extractTurnId(params),
1075
+ this.extractTurnStatus(params),
1076
+ params?.turn?.error ?? params?.error,
1077
+ method
1078
+ );
1079
+ }
1080
+ return;
1081
+ }
1082
+ if (method === "mcpServer/startupStatus/updated") {
1083
+ logger.logger.debug(`[CodexAppServer] mcpServer startup status:`, params);
1084
+ return;
1085
+ }
1086
+ logger.logger.debug(`[CodexAppServer] Notification: ${method}`);
1087
+ }
1088
+ }
1089
+
1090
+ exports.CodexAppServerClient = CodexAppServerClient;