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,894 @@
1
+ 'use strict';
2
+
3
+ var os = require('node:os');
4
+ var sessionLock = require('./sessionLock-7ePAYDOg.cjs');
5
+ var node_crypto = require('node:crypto');
6
+ var api = require('./api-B2vLMS-z.cjs');
7
+ var logger = require('./logger-Bc8-u1w0.cjs');
8
+ var _package = require('./package-Cl5BEP5x.cjs');
9
+ var persistence = require('./persistence-CBNMzSa8.cjs');
10
+ var createSessionMetadata = require('./createSessionMetadata-BRLB9CNO.cjs');
11
+ var index = require('./index-DA7yINuk.cjs');
12
+ var configuration = require('./configuration-v9f8LM9T.cjs');
13
+ var controlClient = require('./controlClient-fEHE85bH.cjs');
14
+ var machineMetadata = require('./machineMetadata-BibDMFVx.cjs');
15
+ var projectPath = require('./projectPath-ChLhdOyt.cjs');
16
+ var path = require('node:path');
17
+ var permissionMode = require('./permissionMode-CrV-XuMs.cjs');
18
+ var authRelay = require('./authRelay-BdYkC1Po.cjs');
19
+ var promises = require('node:fs/promises');
20
+ var defaultPermissionMode = require('./defaultPermissionMode-CxmTXUQR.cjs');
21
+ var attachmentContent = require('./attachmentContent-DMMZzevR.cjs');
22
+ var startupPlan = require('./startupPlan-BLMbfQIH.cjs');
23
+ require('node:fs');
24
+ require('axios');
25
+ require('node:events');
26
+ require('socket.io-client');
27
+ require('./attachmentLimits-CzVrqgLR.cjs');
28
+ require('zod');
29
+ require('./limits-C-a7gr04.cjs');
30
+ require('./time-CNRE4ChD.cjs');
31
+ require('./lidState-KHHu1015.cjs');
32
+ require('os');
33
+ require('child_process');
34
+ require('./registerCommonHandlers-BjSV2Skz.cjs');
35
+ require('util');
36
+ require('fs/promises');
37
+ require('crypto');
38
+ require('path');
39
+ require('cross-spawn');
40
+ require('@paralleldrive/cuid2');
41
+ require('./sync-CVk4QERO.cjs');
42
+ require('./backgroundTaskActivity-CBvUdj39.cjs');
43
+ require('./contentLogMetadata-ZSFk_NjQ.cjs');
44
+ require('chalk');
45
+ require('node:util');
46
+ require('./detectCLI-DR9ZaN6B.cjs');
47
+ require('fs');
48
+ require('./localHappyAgentAuth-BXiHPXdC.cjs');
49
+ require('tweetnacl');
50
+ require('url');
51
+
52
+ const DEFAULT_CLAUDE_MODEL = void 0;
53
+ const DEFAULT_CLAUDE_EFFORT = void 0;
54
+ const importRunClaudeDeps = () => Promise.resolve().then(function () { return require('./runClaudeDeps-Bp20MScE.cjs'); });
55
+ async function runClaude(credentials, options = {}) {
56
+ logger.logger.debug(`[CLAUDE] ===== CLAUDE MODE STARTING =====`);
57
+ logger.logger.debug(`[CLAUDE] This is the Claude agent, NOT Gemini`);
58
+ options = { ...options, claudeEnvVars: { ...options.claudeEnvVars, HAPPY_MANAGED: "1" } };
59
+ const prewarmSpawnEnv = {};
60
+ for (const key of ["HAPPY_CLAUDE_AUTH_STATUS", "HAPPY_FIRST_MESSAGE_FROM_CLI"]) {
61
+ prewarmSpawnEnv[key] = process.env[key];
62
+ delete process.env[key];
63
+ }
64
+ const workingDirectory = process.cwd();
65
+ const isAssistantVariant = process.env.HAPPY_SESSION_VARIANT === "assistant";
66
+ if (isAssistantVariant) {
67
+ try {
68
+ const { bootstrapAssistantHome } = await Promise.resolve().then(function () { return require('./bootstrap-CkgIq8N3.cjs'); });
69
+ await bootstrapAssistantHome();
70
+ } catch (error) {
71
+ logger.logger.debug("[START] Assistant home bootstrap failed:", error);
72
+ }
73
+ }
74
+ logger.logger.debug(`[START] Happy process started pid=${process.pid} node=${process.version} cwd=${workingDirectory}`);
75
+ logger.logger.debug(`[START] Options: startedBy=${options.startedBy}, startingMode=${options.startingMode}`);
76
+ if (options.startedBy === "daemon" && options.startingMode === "local") {
77
+ throw new Error("Daemon-spawned sessions cannot use local/interactive mode. Use --happy-starting-mode remote or spawn sessions directly from terminal.");
78
+ }
79
+ let depsLoadPromise = null;
80
+ const loadDeps = () => {
81
+ if (!depsLoadPromise) {
82
+ depsLoadPromise = importRunClaudeDeps();
83
+ depsLoadPromise.catch(() => {
84
+ });
85
+ }
86
+ return depsLoadPromise;
87
+ };
88
+ const reconnectRequested = Boolean(
89
+ process.env.HAPPY_RECONNECT_SESSION_ID && process.env.HAPPY_RECONNECT_ENCRYPTION_KEY && process.env.HAPPY_RECONNECT_ENCRYPTION_VARIANT
90
+ );
91
+ const startupPlan$1 = startupPlan.planClaudeStartup({ startedBy: options.startedBy, reconnect: reconnectRequested });
92
+ api.connectionState.setBackend("Claude");
93
+ const api$1 = await api.ApiClient.create(credentials);
94
+ let state = {};
95
+ const settings = await persistence.readSettings();
96
+ let machineId = settings?.machineId;
97
+ const sandboxConfig = options.noSandbox ? void 0 : settings?.sandboxConfig;
98
+ const sandboxEnabled = Boolean(sandboxConfig?.enabled);
99
+ const initialPermissionMode = permissionMode.applySandboxPermissionPolicy(
100
+ permissionMode.resolveInitialClaudePermissionMode(options.permissionMode ?? defaultPermissionMode.DEFAULT_CLAUDE_PERMISSION_MODE, options.claudeArgs),
101
+ sandboxEnabled
102
+ );
103
+ const dangerouslySkipPermissions = initialPermissionMode === "bypassPermissions" || initialPermissionMode === "yolo" || sandboxEnabled || Boolean(options.claudeArgs?.includes("--dangerously-skip-permissions"));
104
+ if (!machineId) {
105
+ console.error(`[START] No machine ID found in settings, which is unexpected since authAndSetupMachineIfNeeded should have created it. Please report this issue on https://github.com/Mereithhh/very-happy/issues`);
106
+ process.exit(1);
107
+ }
108
+ logger.logger.debug(`Using machineId: ${machineId}`);
109
+ const sessionTag = node_crypto.randomUUID();
110
+ if (startupPlan$1.registerMachine) {
111
+ await api$1.getOrCreateMachine({
112
+ machineId,
113
+ metadata: machineMetadata.getInitialMachineMetadata()
114
+ });
115
+ }
116
+ const forkedFromSessionId = process.env.HAPPY_FORKED_FROM_SESSION_ID;
117
+ const forkedFromMessageId = process.env.HAPPY_FORKED_FROM_MESSAGE_ID;
118
+ const importedFromClaudeSessionId = process.env.HAPPY_IMPORTED_FROM_CLAUDE_SESSION_ID;
119
+ const importTitle = process.env.HAPPY_IMPORT_TITLE;
120
+ const originTags = createSessionMetadata.spawnOriginTags(process.env, "claude");
121
+ let metadata = {
122
+ path: workingDirectory,
123
+ host: os.hostname(),
124
+ version: _package.packageJson.version,
125
+ os: os.platform(),
126
+ machineId,
127
+ homeDir: os.homedir(),
128
+ happyHomeDir: configuration.configuration.happyHomeDir,
129
+ happyLibDir: projectPath.projectPath(),
130
+ happyToolsDir: path.resolve(projectPath.projectPath(), "tools", "unpacked"),
131
+ startedFromDaemon: options.startedBy === "daemon",
132
+ hostPid: process.pid,
133
+ startedBy: options.startedBy || "terminal",
134
+ // Initialize lifecycle state
135
+ lifecycleState: "running",
136
+ lifecycleStateSince: Date.now(),
137
+ flavor: "claude",
138
+ ...process.env.VH_TEAM_OPERATION_ID ? { teamOperationId: process.env.VH_TEAM_OPERATION_ID } : {},
139
+ // Advertise composer support before the first SDK response. Otherwise
140
+ // a brand-new session cannot pick a PDF until after sending once.
141
+ attachmentKinds: [...attachmentContent.CLAUDE_ATTACHMENT_KINDS],
142
+ queueCancellation: true,
143
+ capabilities: ["claude-steer-v1", "claude-live-permission-v1", "claude-live-permission-v2", "claude-btw-v1", "claude-runtime-controls-v1", "claude-opus-5-5-v1", index.PROMPT_QUEUE_CAPABILITY],
144
+ // Effective mode this process enforces. Kept current by
145
+ // publishPermissionMode below; the web renders it instead of guessing.
146
+ permissionMode: permissionMode.mapToClaudeMode(initialPermissionMode ?? "default"),
147
+ sandbox: sandboxConfig?.enabled ? sandboxConfig : null,
148
+ dangerouslySkipPermissions,
149
+ ...forkedFromSessionId ? { parentSessionId: forkedFromSessionId } : {},
150
+ ...forkedFromMessageId ? { forkedFromMessageId } : {},
151
+ ...importedFromClaudeSessionId ? { importedFromClaudeSessionId } : {},
152
+ ...importTitle ? { summary: { text: importTitle, updatedAt: Date.now() } } : {},
153
+ ...isAssistantVariant ? { variant: "assistant" } : {},
154
+ // B-091/B-303: dispatched sessions (daemon spawn RPC exports
155
+ // HAPPY_SPAWNED_BY, B-069) are born tagged with their spawn origin so
156
+ // every list shows where the work came from. NOT on the meta-agent
157
+ // itself — that one is the variant above and never joins the lists at
158
+ // all. (Shared helper: the other flavors get the same tag via
159
+ // createSessionMetadata.)
160
+ ...originTags ? { tags: originTags } : {}
161
+ };
162
+ const reconnectSessionId = process.env.HAPPY_RECONNECT_SESSION_ID;
163
+ const reconnectKeyBase64 = process.env.HAPPY_RECONNECT_ENCRYPTION_KEY;
164
+ const reconnectVariant = process.env.HAPPY_RECONNECT_ENCRYPTION_VARIANT;
165
+ const reconnectSeq = process.env.HAPPY_RECONNECT_SEQ;
166
+ const reconnectMetadataVersion = process.env.HAPPY_RECONNECT_METADATA_VERSION;
167
+ const reconnectAgentStateVersion = process.env.HAPPY_RECONNECT_AGENT_STATE_VERSION;
168
+ let response;
169
+ if (reconnectSessionId && reconnectKeyBase64 && reconnectVariant) {
170
+ logger.logger.debug(`[START] Reconnecting to existing session ${reconnectSessionId}`);
171
+ response = {
172
+ id: reconnectSessionId,
173
+ seq: parseInt(reconnectSeq || "0", 10),
174
+ encryptionKey: authRelay.decodeBase64(reconnectKeyBase64),
175
+ encryptionVariant: reconnectVariant,
176
+ metadata,
177
+ metadataVersion: parseInt(reconnectMetadataVersion || "0", 10),
178
+ agentState: state,
179
+ agentStateVersion: parseInt(reconnectAgentStateVersion || "0", 10)
180
+ };
181
+ } else {
182
+ response = await api$1.getOrCreateSession({ tag: sessionTag, metadata, state });
183
+ }
184
+ if (!response) {
185
+ let offlineSessionId = null;
186
+ const { claudeLocal } = await Promise.resolve().then(function () { return require('./claudeLocal-BDuGFnjJ.cjs'); });
187
+ const { createSessionScanner: createSessionScanner2 } = await loadDeps();
188
+ const reconnection = api.startOfflineReconnection({
189
+ serverUrl: configuration.configuration.serverUrl,
190
+ onReconnected: async () => {
191
+ const resp = await api$1.getOrCreateSession({ tag: node_crypto.randomUUID(), metadata, state });
192
+ if (!resp) throw new Error("Server unavailable");
193
+ const session2 = api$1.sessionSyncClient(resp);
194
+ const scanner = await createSessionScanner2({
195
+ sessionId: null,
196
+ workingDirectory,
197
+ onMessage: (msg) => session2.sendClaudeSessionMessage(msg)
198
+ });
199
+ if (offlineSessionId) scanner.onNewSession(offlineSessionId);
200
+ return { session: session2, scanner };
201
+ },
202
+ onNotify: console.log,
203
+ onCleanup: () => {
204
+ }
205
+ });
206
+ try {
207
+ await claudeLocal({
208
+ path: workingDirectory,
209
+ sessionId: null,
210
+ onSessionFound: (id) => {
211
+ offlineSessionId = id;
212
+ },
213
+ onThinkingChange: () => {
214
+ },
215
+ abort: new AbortController().signal,
216
+ claudeEnvVars: options.claudeEnvVars,
217
+ claudeArgs: options.claudeArgs,
218
+ mcpServers: {},
219
+ allowedTools: [],
220
+ sandboxConfig
221
+ });
222
+ } finally {
223
+ reconnection.cancel();
224
+ }
225
+ process.exit(0);
226
+ }
227
+ logger.logger.debug(`Session created: ${response.id}`);
228
+ await sessionLock.claimSessionOrExit(response.id, { takeover: !!reconnectSessionId, flavor: "claude" });
229
+ if (reconnectSessionId && !await api$1.reactivateSession(response.id)) {
230
+ throw new Error(`Failed to reactivate archived session ${response.id}`);
231
+ }
232
+ let reconnectSeeded = false;
233
+ if (reconnectSessionId) {
234
+ const snapshot = await api$1.getSession(response.id, response.encryptionKey, response.encryptionVariant);
235
+ if (snapshot.ok) {
236
+ response = sessionLock.applyServerSnapshot(response, snapshot.session);
237
+ reconnectSeeded = true;
238
+ logger.logger.debug(`[START] Reconnect seeded from server: seq=${snapshot.session.seq} metadataVersion=${snapshot.session.metadataVersion}`);
239
+ } else {
240
+ response = sessionLock.withoutServerSnapshot(response);
241
+ logger.logger.debug(`[START] Reconnect without server snapshot (${snapshot.reason}); skipping existing messages`);
242
+ }
243
+ }
244
+ const earlySession = startupPlan$1.sessionClientBeforeWebhook ? api$1.sessionSyncClient(response) : null;
245
+ try {
246
+ logger.logger.debug(`[START] Reporting session ${response.id} to daemon`);
247
+ const result = await controlClient.notifyDaemonSessionStarted(response.id, metadata, {
248
+ encryptionKey: authRelay.encodeBase64(response.encryptionKey),
249
+ encryptionVariant: response.encryptionVariant,
250
+ seq: response.seq,
251
+ metadataVersion: response.metadataVersion,
252
+ agentStateVersion: response.agentStateVersion
253
+ });
254
+ if (result.error) {
255
+ logger.logger.debug(`[START] Failed to report to daemon (may not be running):`, result.error);
256
+ } else {
257
+ logger.logger.debug(`[START] Reported session ${response.id} to daemon`);
258
+ }
259
+ } catch (error) {
260
+ logger.logger.debug("[START] Failed to report to daemon (may not be running):", error);
261
+ }
262
+ loadDeps();
263
+ if (!startupPlan$1.registerMachine) {
264
+ void Promise.resolve().then(() => api$1.getOrCreateMachine({ machineId, metadata: machineMetadata.getInitialMachineMetadata() })).catch((error) => logger.logger.debug("[START] Background machine registration failed:", error));
265
+ }
266
+ let deps;
267
+ try {
268
+ deps = await loadDeps();
269
+ } catch (error) {
270
+ logger.logger.warn(`[START] Failed to load the Claude runner modules for session ${response.id}; marking it offline and exiting: ${error instanceof Error ? error.message : String(error)}`);
271
+ try {
272
+ await earlySession?.close();
273
+ } catch {
274
+ }
275
+ try {
276
+ await api$1.deactivateSession(response.id);
277
+ } catch {
278
+ }
279
+ process.exit(1);
280
+ }
281
+ const {
282
+ loop,
283
+ MessageQueue2,
284
+ claudeModeHash,
285
+ parseSpecialCommand,
286
+ getEnvironmentInfo,
287
+ startHappyServer,
288
+ startHookServer,
289
+ EditReportThrottle,
290
+ extractClaudeEditPaths,
291
+ reportSessionEditToDaemon,
292
+ generateHookSettingsFile,
293
+ cleanupHookSettingsFile,
294
+ registerKillSessionHandler,
295
+ SessionExitGate,
296
+ exitIntentFromArchiveFlag,
297
+ settleSessionOnExit,
298
+ registerSideQuestionHandler,
299
+ writeSideQuestionSettingsFile,
300
+ claudeCheckSession,
301
+ createSessionScanner,
302
+ getProjectPath,
303
+ RawJSONLinesSchema,
304
+ TitleGenerator,
305
+ BoardAnalyzer,
306
+ FileRateLimiter,
307
+ createSelfReportState,
308
+ withAssistantDenylist,
309
+ contentLogMetadata,
310
+ resolveMessageMode,
311
+ claudePrewarmEligibility,
312
+ claudePrewarmSessionEligibility,
313
+ prewarmCacheFile,
314
+ readPrewarmCache,
315
+ writePrewarmCache,
316
+ prewarmPredictionMeta,
317
+ evaluatePrewarmPrediction,
318
+ acquirePrewarmSlot,
319
+ prewarmSlotDir,
320
+ PrewarmHookGate,
321
+ createClaudePrewarmLease,
322
+ disposeAllClaudePrewarms,
323
+ formatPrewarmLine
324
+ } = deps;
325
+ logger.logger.debugLargeJson("[START] Happy process started", getEnvironmentInfo());
326
+ const session = earlySession ?? api$1.sessionSyncClient(response, reconnectSeeded ? { initialSeq: response.seq } : void 0);
327
+ if (reconnectSessionId) {
328
+ session.suppressNextArchiveSignal();
329
+ if (!reconnectSeeded) session.skipExistingMessages();
330
+ void session.cancelUndeliveredQueuedInputs();
331
+ session.updateMetadata((meta) => ({
332
+ ...sessionLock.mergeReconnectMetadata(meta, metadata, Date.now()),
333
+ queueCancellation: true,
334
+ // B-262 batch 2 (B5): a reconnected process re-publishes the mode
335
+ // IT enforces; the server copy may still carry the previous
336
+ // process's value.
337
+ permissionMode: permissionMode.mapToClaudeMode(initialPermissionMode ?? "default")
338
+ }));
339
+ }
340
+ const forkClaudeSessionId = process.env.HAPPY_FORK_CLAUDE_SESSION_ID;
341
+ if (!reconnectSessionId && forkClaudeSessionId) {
342
+ const jsonlPath = path.join(getProjectPath(workingDirectory), `${forkClaudeSessionId}.jsonl`);
343
+ try {
344
+ const file = await promises.readFile(jsonlPath, "utf-8");
345
+ const lines = file.split("\n");
346
+ let backfilled = 0;
347
+ for (const line of lines) {
348
+ if (line.trim().length === 0) continue;
349
+ let parsed;
350
+ try {
351
+ parsed = JSON.parse(line);
352
+ } catch {
353
+ continue;
354
+ }
355
+ const result = RawJSONLinesSchema.safeParse(parsed);
356
+ if (!result.success) continue;
357
+ session.sendClaudeSessionMessage(result.data);
358
+ backfilled += 1;
359
+ }
360
+ logger.logger.debug(`[FORK BACKFILL] Replayed ${backfilled} historical messages from ${jsonlPath}`);
361
+ session.updateMetadata((meta) => ({ ...meta, claudeSessionId: forkClaudeSessionId }));
362
+ } catch (error) {
363
+ logger.logger.debug(`[FORK BACKFILL] Failed to read ${jsonlPath}:`, error);
364
+ }
365
+ }
366
+ const recentAppPromptsMaxAgeMs = 5 * 60 * 1e3;
367
+ const recentAppPrompts = [];
368
+ const promptDedupeKey = (text) => attachmentContent.stripAttachmentManifest(text).trim();
369
+ const recordAppPrompt = (text) => {
370
+ const now = Date.now();
371
+ recentAppPrompts.push({ text: promptDedupeKey(text), addedAt: now });
372
+ const cutoff = now - recentAppPromptsMaxAgeMs;
373
+ while (recentAppPrompts.length > 0 && recentAppPrompts[0].addedAt < cutoff) {
374
+ recentAppPrompts.shift();
375
+ }
376
+ };
377
+ const consumeAppPrompt = (text) => {
378
+ const cutoff = Date.now() - recentAppPromptsMaxAgeMs;
379
+ const key = promptDedupeKey(text);
380
+ for (let i = 0; i < recentAppPrompts.length; i++) {
381
+ const entry = recentAppPrompts[i];
382
+ if (entry.addedAt < cutoff) continue;
383
+ if (entry.text === key) {
384
+ recentAppPrompts.splice(i, 1);
385
+ return true;
386
+ }
387
+ }
388
+ return false;
389
+ };
390
+ let currentRunMode = options.startingMode ?? "local";
391
+ const titleGenerator = new TitleGenerator(session);
392
+ const boardSelfReport = createSelfReportState();
393
+ const boardAnalyzer = new BoardAnalyzer(session, {
394
+ enabled: settings?.boardLlm === true,
395
+ selfReportState: boardSelfReport,
396
+ fetchTasks: async () => {
397
+ const item = await api$1.kvGet("vh.board-tasks.v1");
398
+ if (!item) return null;
399
+ try {
400
+ const parsed = JSON.parse(Buffer.from(item.value, "base64").toString("utf8"));
401
+ if (!Array.isArray(parsed.tasks)) return null;
402
+ return parsed.tasks.filter((t) => typeof t?.id === "string" && typeof t?.title === "string" && t.status === "open").map((t) => ({ id: t.id, title: t.title }));
403
+ } catch {
404
+ return null;
405
+ }
406
+ },
407
+ rateLimiter: new FileRateLimiter(path.resolve(configuration.configuration.happyHomeDir, "board-analyzer-rate.json"))
408
+ });
409
+ session.on("claude-session-message", (body) => boardAnalyzer.noteClaudeMessage(body));
410
+ session.on("turn-ended", () => boardAnalyzer.onTurnEnd());
411
+ const editThrottle = new EditReportThrottle();
412
+ session.on("claude-session-message", (body) => {
413
+ try {
414
+ for (const edit of editThrottle.take(extractClaudeEditPaths(body))) {
415
+ reportSessionEditToDaemon({ sessionId: response.id, path: edit.path, tool: edit.tool, cwd: workingDirectory, ...edit.at !== void 0 ? { at: edit.at } : {} });
416
+ }
417
+ } catch (error) {
418
+ logger.logger.debug("[START] edit report failed:", error);
419
+ }
420
+ });
421
+ options = { ...options, claudeEnvVars: { ...options.claudeEnvVars, VH_PEER_SESSION_ID: response.id } };
422
+ const initialScannerSessionId = forkClaudeSessionId ?? (metadata.claudeSessionId ?? null);
423
+ const remoteScanner = await createSessionScanner({
424
+ sessionId: initialScannerSessionId,
425
+ workingDirectory,
426
+ onMessage: (raw) => {
427
+ if (currentRunMode !== "remote") return;
428
+ if (raw.type !== "user") return;
429
+ if (raw.isSidechain) return;
430
+ const content = raw.message?.content;
431
+ if (typeof content !== "string") return;
432
+ if (content.trim().length === 0) return;
433
+ if (consumeAppPrompt(content)) return;
434
+ titleGenerator.maybeGenerate(content);
435
+ boardAnalyzer.noteUserMessage(content);
436
+ session.sendClaudeSessionMessage(raw);
437
+ }
438
+ });
439
+ const happyServer = await startHappyServer(session, { assistant: isAssistantVariant, selfReportState: boardSelfReport });
440
+ logger.logger.debug(`[START] Happy MCP server started at ${happyServer.url}`);
441
+ let currentSession = null;
442
+ const prewarmHookGate = new PrewarmHookGate();
443
+ let scannerAnnouncedSessionId = null;
444
+ const announceSessionToScanner = (sessionId) => {
445
+ if (sessionId === scannerAnnouncedSessionId) return;
446
+ scannerAnnouncedSessionId = sessionId;
447
+ void Promise.resolve(remoteScanner.onNewSession(sessionId, { treatExistingAsProcessed: true })).catch((error) => logger.logger.debug("[START] remote scanner onNewSession failed:", error));
448
+ };
449
+ const hookServer = await startHookServer({
450
+ onSessionHook: (sessionId, data, hookMeta) => {
451
+ if (!prewarmHookGate.accepts(hookMeta?.source)) {
452
+ logger.logger.debug(`[START] Session hook from an unadopted warm Claude process ignored (${hookMeta?.source})`);
453
+ return;
454
+ }
455
+ logger.logger.debug(`[START] Session hook received: ${sessionId}`, contentLogMetadata(data));
456
+ announceSessionToScanner(sessionId);
457
+ if (currentSession) {
458
+ const previousSessionId = currentSession.sessionId;
459
+ if (previousSessionId !== sessionId) {
460
+ logger.logger.debug(`[START] Claude session ID changed: ${previousSessionId} -> ${sessionId}`);
461
+ currentSession.onSessionFound(sessionId);
462
+ }
463
+ }
464
+ }
465
+ });
466
+ logger.logger.debug(`[START] Hook server started on port ${hookServer.port}`);
467
+ const hookSettingsPath = generateHookSettingsFile(hookServer.port);
468
+ logger.logger.debug(`[START] Generated hook settings file: ${hookSettingsPath}`);
469
+ const logPath = logger.logger.logFilePath;
470
+ logger.logger.infoDeveloper(`Session: ${response.id}`);
471
+ logger.logger.infoDeveloper(`Logs: ${logPath}`);
472
+ session.updateAgentState((currentState) => ({
473
+ ...currentState,
474
+ controlledByUser: options.startingMode !== "remote"
475
+ }));
476
+ const messageQueue = new MessageQueue2(claudeModeHash);
477
+ const promptQueueDrain = session.attachPromptQueueDrain({
478
+ isIdle: () => messageQueue.size() === 0 && messageQueue.isWaiting()
479
+ });
480
+ messageQueue.setOnWait(() => promptQueueDrain.onIdle());
481
+ messageQueue.setOnDiscard((entries) => {
482
+ const byReason = /* @__PURE__ */ new Map();
483
+ for (const entry of entries) {
484
+ if (!entry.sourceId) continue;
485
+ const list = byReason.get(entry.reason) ?? [];
486
+ list.push(entry.sourceId);
487
+ byReason.set(entry.reason, list);
488
+ }
489
+ for (const [reason, keys] of byReason) session.sendQueueCancelReason(keys, reason);
490
+ });
491
+ session.rpcHandlerManager.registerHandler("cancelQueuedMessage", async (params) => ({
492
+ removed: messageQueue.cancelPending(
493
+ typeof params?.localKey === "string" ? params.localKey : void 0,
494
+ typeof params?.text === "string" ? params.text : void 0
495
+ )
496
+ }));
497
+ let currentPermissionMode = initialPermissionMode;
498
+ let publishedPermissionMode = permissionMode.mapToClaudeMode(initialPermissionMode ?? "default");
499
+ const publishPermissionMode = (mode) => {
500
+ currentPermissionMode = mode;
501
+ const published = permissionMode.mapToClaudeMode(mode ?? "default");
502
+ if (published === publishedPermissionMode) return;
503
+ publishedPermissionMode = published;
504
+ session.updateMetadata((meta) => ({ ...meta, permissionMode: published }));
505
+ };
506
+ const publishEffectivePermissionMode = (effective) => {
507
+ const decision = permissionMode.reconcilePublishedPermissionMode({
508
+ intent: permissionMode.mapToClaudeMode(currentPermissionMode ?? "default"),
509
+ published: publishedPermissionMode,
510
+ effective
511
+ });
512
+ if (!decision) return;
513
+ if (decision.mismatch) {
514
+ logger.logger.warn(`[loop] Claude Code enforces permission mode '${effective}' but this process asked for '${permissionMode.mapToClaudeMode(currentPermissionMode ?? "default")}' \u2014 check ~/.claude settings (permissions.deny/ask/defaultMode/disableBypassPermissionsMode). Publishing the effective mode.`);
515
+ }
516
+ if (decision.publish !== publishedPermissionMode) {
517
+ publishedPermissionMode = decision.publish;
518
+ session.updateMetadata((meta) => ({ ...meta, permissionMode: decision.publish }));
519
+ }
520
+ };
521
+ let lastExplicitModeSwitchAt = 0;
522
+ let currentModel = options.model ?? DEFAULT_CLAUDE_MODEL;
523
+ let currentFallbackModel = void 0;
524
+ let currentCustomSystemPrompt = void 0;
525
+ let currentAppendSystemPrompt = void 0;
526
+ let currentAllowedTools = void 0;
527
+ let currentDisallowedTools = withAssistantDenylist(void 0, isAssistantVariant);
528
+ let currentEffort = DEFAULT_CLAUDE_EFFORT;
529
+ registerSideQuestionHandler(session.rpcHandlerManager, {
530
+ getClaudeSessionId: () => {
531
+ const id = currentSession?.sessionId ?? null;
532
+ return id && claudeCheckSession(id, workingDirectory) ? id : null;
533
+ },
534
+ getModel: () => currentModel,
535
+ cwd: workingDirectory,
536
+ getEnv: () => options.claudeEnvVars,
537
+ settingsPath: writeSideQuestionSettingsFile(configuration.configuration.happyHomeDir),
538
+ // B-482: in remote mode the launcher parks the live Query's /btw entry
539
+ // on the session while the Query is alive; local mode never sets it.
540
+ getLiveQuery: () => currentSession?.getSideQuestionLive() ?? null
541
+ });
542
+ const resetCurrentModeDefaults = () => {
543
+ currentModel = options.model ?? DEFAULT_CLAUDE_MODEL;
544
+ currentFallbackModel = void 0;
545
+ currentCustomSystemPrompt = void 0;
546
+ currentAppendSystemPrompt = void 0;
547
+ currentAllowedTools = void 0;
548
+ currentDisallowedTools = withAssistantDenylist(void 0, isAssistantVariant);
549
+ currentEffort = DEFAULT_CLAUDE_EFFORT;
550
+ logger.logger.debug("[loop] Reset current mode defaults after abort");
551
+ };
552
+ const currentModeState = () => ({
553
+ permissionMode: currentPermissionMode,
554
+ model: currentModel,
555
+ fallbackModel: currentFallbackModel,
556
+ customSystemPrompt: currentCustomSystemPrompt,
557
+ appendSystemPrompt: currentAppendSystemPrompt,
558
+ allowedTools: currentAllowedTools,
559
+ disallowedTools: currentDisallowedTools,
560
+ effort: currentEffort
561
+ });
562
+ const prewarmCachePath = prewarmCacheFile(configuration.configuration.happyHomeDir);
563
+ let prewarmCache = readPrewarmCache(prewarmCachePath);
564
+ const prewarmSessionInput = {
565
+ env: { ...process.env, ...prewarmSpawnEnv },
566
+ startedBy: options.startedBy,
567
+ startingMode: options.startingMode,
568
+ claudeArgs: options.claudeArgs,
569
+ setting: settings?.claudePrewarm
570
+ };
571
+ const prewarmCtx = () => ({ sandboxEnabled, isAssistantVariant, staleModeSnapshot: false });
572
+ const predictFirstMessageMode = () => prewarmCache ? resolveMessageMode(currentModeState(), prewarmPredictionMeta(prewarmCache), prewarmCtx()).mode : null;
573
+ let firstMessageScored = false;
574
+ const scoreFirstMessage = (meta, predicted, actual) => {
575
+ if (firstMessageScored) return;
576
+ firstMessageScored = true;
577
+ if (meta?.sentFrom !== "web") return;
578
+ if (!claudePrewarmSessionEligibility({ ...prewarmSessionInput, env: { ...process.env, ...prewarmSpawnEnv } }).eligible) return;
579
+ const { outcome, next } = evaluatePrewarmPrediction({ predicted, actual, meta, previous: prewarmCache });
580
+ logger.logger.debug(`[CLAUDE PREWARM] prediction ${outcome}${next ? ` consecutiveMisses=${next.consecutiveMisses}` : ""}`);
581
+ if (next && writePrewarmCache(prewarmCachePath, next)) prewarmCache = next;
582
+ };
583
+ let userMessageSeen = false;
584
+ const prewarmTagFiles = /* @__PURE__ */ new Set();
585
+ const createPrewarm = () => {
586
+ const eligibility = claudePrewarmEligibility({
587
+ ...prewarmSessionInput,
588
+ env: { ...process.env, ...prewarmSpawnEnv },
589
+ cache: prewarmCache
590
+ });
591
+ if (!eligibility.eligible) {
592
+ logger.logger.debug(formatPrewarmLine("skipped", eligibility.reason));
593
+ return null;
594
+ }
595
+ if (userMessageSeen) {
596
+ logger.logger.debug(formatPrewarmLine("skipped", "message-already-received"));
597
+ return null;
598
+ }
599
+ const slot = acquirePrewarmSlot(prewarmSlotDir(configuration.configuration.happyHomeDir));
600
+ if (!slot) {
601
+ logger.logger.debug(formatPrewarmLine("skipped", "concurrency-limit"));
602
+ return null;
603
+ }
604
+ const predicted = predictFirstMessageMode();
605
+ if (!predicted) {
606
+ slot.release();
607
+ return null;
608
+ }
609
+ const tag = prewarmHookGate.newTag();
610
+ let tagHookSettingsPath;
611
+ try {
612
+ tagHookSettingsPath = generateHookSettingsFile(hookServer.port, tag);
613
+ } catch (error) {
614
+ slot.release();
615
+ logger.logger.debug(formatPrewarmLine("skipped", "hook-settings"));
616
+ return null;
617
+ }
618
+ prewarmTagFiles.add(tagHookSettingsPath);
619
+ return createClaudePrewarmLease({
620
+ mode: predicted,
621
+ hookSettingsPath: tagHookSettingsPath,
622
+ tag,
623
+ gate: prewarmHookGate,
624
+ slot,
625
+ // Only removed on discard: an adopted process keeps reading its
626
+ // settings file (later SessionStart events, e.g. /compact); it goes
627
+ // with the other hook files at exit.
628
+ cleanupHookSettings: () => {
629
+ cleanupHookSettingsFile(tagHookSettingsPath);
630
+ prewarmTagFiles.delete(tagHookSettingsPath);
631
+ },
632
+ log: (line) => logger.logger.debug(line)
633
+ });
634
+ };
635
+ const cleanupPrewarm = () => {
636
+ disposeAllClaudePrewarms("shutdown");
637
+ for (const file of prewarmTagFiles) cleanupHookSettingsFile(file);
638
+ prewarmTagFiles.clear();
639
+ };
640
+ session.onFileEvent((fileEvent) => {
641
+ const ev = fileEvent.content.data.ev;
642
+ logger.logger.debug(`[loop] File event received: ${ev.name} (${ev.size} bytes, ref: ${ev.ref})`);
643
+ const downloadPromise = (async () => {
644
+ try {
645
+ const decrypted = await session.downloadAndDecryptAttachment(ev.ref);
646
+ if (!decrypted) {
647
+ logger.logger.debug(`[loop] Failed to decrypt attachment: ${ev.name}`);
648
+ return null;
649
+ }
650
+ logger.logger.debug(`[loop] Attachment decrypted: ${ev.name} (${decrypted.length} bytes)`);
651
+ return { data: decrypted, mimeType: ev.mimeType ?? "image/jpeg", name: ev.name };
652
+ } catch (error) {
653
+ logger.logger.debug(`[loop] Failed to download attachment: ${ev.name}`, { error });
654
+ return null;
655
+ }
656
+ })();
657
+ session.trackAttachmentDownload(downloadPromise);
658
+ });
659
+ session.onUserMessage(async (message) => {
660
+ userMessageSeen = true;
661
+ if (message?.content?.text) {
662
+ titleGenerator.maybeGenerate(message.content.text);
663
+ boardAnalyzer.noteUserMessage(message.content.text);
664
+ }
665
+ const attachmentsForThisMessage = await session.drainAttachmentsForUserMessage();
666
+ const messageCreatedAt = message.createdAt;
667
+ const staleModeSnapshot = message.meta?.permissionMode !== void 0 && typeof messageCreatedAt === "number" && messageCreatedAt < lastExplicitModeSwitchAt;
668
+ if (staleModeSnapshot) {
669
+ logger.logger.debug(`[loop] Ignoring stale meta.permissionMode=${message.meta?.permissionMode} (message ${messageCreatedAt} predates explicit switch ${lastExplicitModeSwitchAt}); keeping ${currentPermissionMode}`);
670
+ }
671
+ const previousPermissionMode = currentPermissionMode;
672
+ const predictedFirstMode = firstMessageScored ? null : predictFirstMessageMode();
673
+ const resolved = resolveMessageMode(currentModeState(), message.meta, { sandboxEnabled, isAssistantVariant, staleModeSnapshot });
674
+ if ("publishPermissionMode" in resolved) {
675
+ publishPermissionMode(resolved.publishPermissionMode);
676
+ const ignoredDefaultDowngrade = (previousPermissionMode === "bypassPermissions" || previousPermissionMode === "yolo") && message.meta?.permissionMode === "default" && currentPermissionMode === previousPermissionMode;
677
+ logger.logger.debug(ignoredDefaultDowngrade ? `[loop] Ignoring permission mode downgrade from ${previousPermissionMode} to default` : `[loop] Permission mode updated from user message to: ${currentPermissionMode}`);
678
+ }
679
+ currentModel = resolved.next.model;
680
+ currentFallbackModel = resolved.next.fallbackModel;
681
+ currentCustomSystemPrompt = resolved.next.customSystemPrompt;
682
+ currentAppendSystemPrompt = resolved.next.appendSystemPrompt;
683
+ currentAllowedTools = resolved.next.allowedTools;
684
+ currentDisallowedTools = resolved.next.disallowedTools;
685
+ currentEffort = resolved.next.effort;
686
+ if (resolved.ignoredEffort !== void 0) {
687
+ logger.logger.debug(`[loop] Ignoring invalid effort from user message: ${resolved.ignoredEffort}`);
688
+ }
689
+ logger.logger.debug(`[loop] Message mode: permission=${resolved.mode.permissionMode} model=${resolved.mode.model || "default"} effort=${resolved.mode.effort ?? "default"} appendSystemPrompt=${resolved.mode.appendSystemPrompt ? "set" : "none"} customSystemPrompt=${resolved.mode.customSystemPrompt ? "set" : "none"}`);
690
+ scoreFirstMessage(message.meta, predictedFirstMode, resolved.mode);
691
+ const resolvedMode = resolved.mode;
692
+ const specialCommand = parseSpecialCommand(message.content.text);
693
+ if (specialCommand.type === "compact") {
694
+ logger.logger.debug("[start] Detected /compact command");
695
+ const enhancedMode2 = resolvedMode;
696
+ messageQueue.pushIsolateAndClear(specialCommand.originalMessage || message.content.text, enhancedMode2, attachmentsForThisMessage, message.localKey);
697
+ logger.logger.debug("[start] /compact command pushed to queue:", {
698
+ ...contentLogMetadata(message.content.text),
699
+ attachmentCount: attachmentsForThisMessage?.length ?? 0,
700
+ hasMetadata: Boolean(message.meta)
701
+ });
702
+ return;
703
+ }
704
+ if (specialCommand.type === "clear") {
705
+ logger.logger.debug("[start] Detected /clear command");
706
+ const enhancedMode2 = resolvedMode;
707
+ messageQueue.pushIsolateAndClear(specialCommand.originalMessage || message.content.text, enhancedMode2, attachmentsForThisMessage, message.localKey);
708
+ logger.logger.debug("[start] /clear command pushed to queue:", {
709
+ ...contentLogMetadata(message.content.text),
710
+ attachmentCount: attachmentsForThisMessage?.length ?? 0,
711
+ hasMetadata: Boolean(message.meta)
712
+ });
713
+ return;
714
+ }
715
+ if (specialCommand.type === "mcp" || specialCommand.type === "skills") {
716
+ if (currentRunMode === "local") {
717
+ logger.logger.debug(`[start] /${specialCommand.type} in local mode \u2014 passing through to Claude Code`);
718
+ } else {
719
+ logger.logger.debug(`[start] Detected /${specialCommand.type} command in remote mode`);
720
+ const metadata2 = session.getMetadata();
721
+ let responseText;
722
+ if (specialCommand.type === "mcp") {
723
+ const servers = metadata2?.mcpServers;
724
+ if (servers && servers.length > 0) {
725
+ responseText = "**MCP Servers**\n\n" + servers.map((s) => `- **${s.name}** \u2014 ${s.status}`).join("\n");
726
+ } else {
727
+ responseText = "No MCP servers configured. Session may still be initializing \u2014 try again after sending a message.";
728
+ }
729
+ } else {
730
+ const skills = metadata2?.skills ?? metadata2?.slashCommands;
731
+ if (skills && skills.length > 0) {
732
+ responseText = "**Available Skills**\n\n" + skills.map((s) => `- /${s}`).join("\n");
733
+ } else {
734
+ responseText = "No skills available. Session may still be initializing \u2014 try again after sending a message.";
735
+ }
736
+ }
737
+ session.sendClaudeSessionMessage({
738
+ type: "assistant",
739
+ uuid: node_crypto.randomUUID(),
740
+ parentUuid: null,
741
+ isSidechain: false,
742
+ sessionId: session.sessionId || "unknown",
743
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
744
+ message: {
745
+ role: "assistant",
746
+ model: "system",
747
+ content: [{ type: "text", text: responseText }]
748
+ }
749
+ });
750
+ return;
751
+ }
752
+ }
753
+ const enhancedMode = resolvedMode;
754
+ if (message.meta?.delivery === "steer" && currentRunMode === "remote") {
755
+ const accepted = await currentSession?.trySteer({
756
+ message: message.content.text,
757
+ mode: enhancedMode,
758
+ attachments: attachmentsForThisMessage
759
+ }) === true;
760
+ if (accepted) {
761
+ logger.logger.debug("User message steered into current turn:", {
762
+ ...contentLogMetadata(message.content.text),
763
+ attachmentCount: attachmentsForThisMessage?.length ?? 0
764
+ });
765
+ return;
766
+ }
767
+ logger.logger.debug("[start] Steer arrived without a compatible live turn; falling back to queue");
768
+ }
769
+ messageQueue.push(message.content.text, enhancedMode, attachmentsForThisMessage, message.localKey);
770
+ logger.logger.debug("User message pushed to queue:", {
771
+ ...contentLogMetadata(message.content.text),
772
+ attachmentCount: attachmentsForThisMessage?.length ?? 0,
773
+ hasMetadata: Boolean(message.meta)
774
+ });
775
+ });
776
+ const exitGate = new SessionExitGate();
777
+ const cleanup = async (opts = { archive: true }) => {
778
+ const intent = exitGate.claim(exitIntentFromArchiveFlag(opts.archive));
779
+ if (intent === null) {
780
+ logger.logger.debug(`[START] Termination (archive=${opts.archive ?? true}) ignored: cleanup already running as ${exitGate.current}`);
781
+ return;
782
+ }
783
+ logger.logger.debug(`[START] Received termination signal, cleaning up (archive=${intent === "archive"})...`);
784
+ promptQueueDrain.close();
785
+ cleanupPrewarm();
786
+ try {
787
+ if (session) {
788
+ currentSession?.cleanup();
789
+ await settleSessionOnExit(session.sessionId, intent, {
790
+ updateMetadata: (handler) => session.updateMetadata(handler),
791
+ sendSessionDeath: () => session.sendSessionDeath(),
792
+ archiveSession: (id) => api$1.archiveSession(id),
793
+ deactivateSession: (id) => api$1.deactivateSession(id),
794
+ log: (message, ...args) => logger.logger.debug(message, ...args)
795
+ });
796
+ await session.flush();
797
+ await session.close();
798
+ }
799
+ happyServer.stop();
800
+ hookServer.stop();
801
+ cleanupHookSettingsFile(hookSettingsPath);
802
+ await remoteScanner.cleanup();
803
+ logger.logger.debug("[START] Cleanup complete, exiting");
804
+ process.exit(0);
805
+ } catch (error) {
806
+ logger.logger.debug("[START] Error during cleanup:", error);
807
+ process.exit(1);
808
+ }
809
+ };
810
+ process.on("SIGTERM", () => {
811
+ void cleanup({ archive: false });
812
+ });
813
+ process.on("SIGINT", () => {
814
+ void cleanup({ archive: false });
815
+ });
816
+ process.on("uncaughtException", (error) => {
817
+ logger.logger.debug("[START] Uncaught exception:", error);
818
+ if (currentSession?.isAborting()) {
819
+ logger.logger.debug("[START] Uncaught exception during abort teardown \u2014 swallowing, not archiving");
820
+ return;
821
+ }
822
+ void cleanup({ archive: true });
823
+ });
824
+ process.on("unhandledRejection", (reason) => {
825
+ logger.logger.debug("[START] Unhandled rejection:", reason);
826
+ if (currentSession?.isAborting()) {
827
+ logger.logger.debug("[START] Unhandled rejection during abort teardown \u2014 swallowing, not archiving");
828
+ return;
829
+ }
830
+ void cleanup({ archive: true });
831
+ });
832
+ registerKillSessionHandler(session.rpcHandlerManager, () => cleanup({ archive: true }), session);
833
+ const exitCode = await loop({
834
+ path: workingDirectory,
835
+ model: options.model,
836
+ permissionMode: initialPermissionMode,
837
+ startingMode: options.startingMode,
838
+ messageQueue,
839
+ api: api$1,
840
+ allowedTools: happyServer.toolNames.map((toolName) => `mcp__happy__${toolName}`),
841
+ onModeChange: (newMode) => {
842
+ currentRunMode = newMode;
843
+ session.sendSessionEvent({ type: "switch", mode: newMode });
844
+ session.updateAgentState((currentState) => ({
845
+ ...currentState,
846
+ controlledByUser: newMode === "local"
847
+ }));
848
+ },
849
+ onSessionReady: (sessionInstance) => {
850
+ currentSession = sessionInstance;
851
+ sessionInstance.addSessionFoundCallback?.(announceSessionToScanner);
852
+ },
853
+ // B-515: lease for a warm Claude process, taken once by the first
854
+ // remote launch (null when not eligible).
855
+ claudePrewarm: createPrewarm,
856
+ // B-355: remember exactly what the SDK is about to write to the Claude
857
+ // transcript, so the JSONL scanner below recognises it as ours.
858
+ onPromptFinalized: recordAppPrompt,
859
+ onAbort: resetCurrentModeDefaults,
860
+ onPermissionModeChange: (mode) => {
861
+ lastExplicitModeSwitchAt = Date.now();
862
+ publishPermissionMode(mode);
863
+ logger.logger.debug(`[loop] Permission mode updated from session RPC / approval to: ${mode}`);
864
+ },
865
+ onEffectivePermissionMode: (mode) => publishEffectivePermissionMode(mode),
866
+ mcpServers: {
867
+ "happy": {
868
+ type: "http",
869
+ url: happyServer.url
870
+ }
871
+ },
872
+ session,
873
+ claudeEnvVars: options.claudeEnvVars,
874
+ claudeArgs: options.claudeArgs,
875
+ sandboxConfig,
876
+ hookSettingsPath,
877
+ jsRuntime: options.jsRuntime
878
+ });
879
+ currentSession?.cleanup();
880
+ cleanupPrewarm();
881
+ session.sendSessionDeath();
882
+ logger.logger.debug("Waiting for socket to flush...");
883
+ await session.flush();
884
+ logger.logger.debug("Closing session...");
885
+ await session.close();
886
+ happyServer.stop();
887
+ logger.logger.debug("Stopped Happy MCP server");
888
+ hookServer.stop();
889
+ cleanupHookSettingsFile(hookSettingsPath);
890
+ logger.logger.debug("Stopped Hook server and cleaned up settings file");
891
+ process.exit(exitCode);
892
+ }
893
+
894
+ exports.runClaude = runClaude;