very-happy-cli 0.2.180 → 0.2.182

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 (202) hide show
  1. package/dist/{AcpBackend-B8Zi30Zh.mjs → AcpBackend-DLk3gMYH.mjs} +3 -3
  2. package/dist/{AcpBackend-DfRDtMcG.cjs → AcpBackend-JA5eu8Y0.cjs} +3 -3
  3. package/dist/{AcpSessionManager-CASKOnOj.cjs → AcpSessionManager-DjYidq4C.cjs} +1 -1
  4. package/dist/{AcpSessionManager-i8Rab0xT.mjs → AcpSessionManager-OrCww-Qv.mjs} +1 -1
  5. package/dist/{BasePermissionHandler-BlM_tfej.cjs → BasePermissionHandler-D3PMKLBS.cjs} +1 -1
  6. package/dist/{BasePermissionHandler-26fKbRCm.mjs → BasePermissionHandler-u-neo-iN.mjs} +1 -1
  7. package/dist/{acpSessionResume-CR5pmgVo.cjs → acpSessionResume-Dxo8222T.cjs} +1 -1
  8. package/dist/{acpSessionResume-B9l54P-X.mjs → acpSessionResume-u2TwX5Jd.mjs} +1 -1
  9. package/dist/{agentHome-C7Sb7-Tz.cjs → agentHome-CP1tCBXz.cjs} +4 -4
  10. package/dist/{agentHome-CmGy0NWv.mjs → agentHome-D3hNrnoA.mjs} +4 -4
  11. package/dist/{api-DM7b1OKj.cjs → api-CZWcG7uh.cjs} +10 -10
  12. package/dist/{api-Hov8p7CA.mjs → api-C_YSQnsi.mjs} +10 -10
  13. package/dist/{apiMachine-CwGEhQDT.mjs → apiMachine-C_GA5Bm-.mjs} +59 -32
  14. package/dist/{apiMachine-DkGRj-Ai.cjs → apiMachine-DPGrED5C.cjs} +60 -33
  15. package/dist/{attachmentContent-CIn4iPEk.mjs → attachmentContent-Ckzyb1Uy.mjs} +2 -2
  16. package/dist/{attachmentContent-CEm13_gF.cjs → attachmentContent-I_dJIevE.cjs} +2 -2
  17. package/dist/{auth-Dawj7pP5.cjs → auth-BKnE7uv7.cjs} +8 -8
  18. package/dist/{auth-BG1DY0gj.mjs → auth-CGgpiJ5-.mjs} +5 -5
  19. package/dist/{auth-rggxMpEE.mjs → auth-DOXMI-xp.mjs} +8 -8
  20. package/dist/{auth-CBXnm-Mj.cjs → auth-Lw_utSMV.cjs} +5 -5
  21. package/dist/{auto-CbYOfoPS.cjs → auto-CIgFHIyQ.cjs} +18 -18
  22. package/dist/{auto-CXXLXGeL.mjs → auto-Z56mMZnH.mjs} +18 -18
  23. package/dist/{bootstrap-Dt5I1SOl.mjs → bootstrap-D3vlOROj.mjs} +2 -2
  24. package/dist/{bootstrap-DoA7okld.cjs → bootstrap-DsXp74wv.cjs} +2 -2
  25. package/dist/{browser-DUNetFT4.mjs → browser-CUxGoATV.mjs} +1 -1
  26. package/dist/{browser-DZ6qRtfH.cjs → browser-CZlA9AkO.cjs} +1 -1
  27. package/dist/{claudeLocal-OHWQigCl.mjs → claudeLocal-FnTUgwCi.mjs} +6 -6
  28. package/dist/{claudeLocal--DNyMOSv.cjs → claudeLocal-oELvwiZL.cjs} +6 -6
  29. package/dist/{claudeLocalLauncher-BStvfa7M.mjs → claudeLocalLauncher-CO5YHCzi.mjs} +11 -11
  30. package/dist/{claudeLocalLauncher-BhvPbcyH.cjs → claudeLocalLauncher-QsVXEVdE.cjs} +11 -11
  31. package/dist/{claudeSessionFork-Be16vzGc.cjs → claudeSessionFork-BA4GE5AL.cjs} +1 -1
  32. package/dist/{claudeSessionFork-GAVWhiKG.mjs → claudeSessionFork-Cspr6QW0.mjs} +1 -1
  33. package/dist/{client-D4uPb1uu.mjs → client-BGfmSbWc.mjs} +44 -9
  34. package/dist/{client-BWjaHKtj.cjs → client-CDuW4MS9.cjs} +2 -2
  35. package/dist/{client-CnB7kRCT.mjs → client-Ctbw05Xj.mjs} +2 -2
  36. package/dist/{client-DJplzPjD.cjs → client-DK8CzUry.cjs} +2 -2
  37. package/dist/{client-Ca5Pcgy_.mjs → client-o-meK7-a.mjs} +2 -2
  38. package/dist/{client-D_loZ9IK.cjs → client-xnWiHqdb.cjs} +44 -8
  39. package/dist/codex/happyMcpStdioBridge.cjs +21 -20
  40. package/dist/codex/happyMcpStdioBridge.mjs +21 -20
  41. package/dist/{codexAppServerClient-CO4G6OS4.mjs → codexAppServerClient-BlsOZLul.mjs} +3 -3
  42. package/dist/{codexAppServerClient-PE-12AsW.cjs → codexAppServerClient-DhNDTU_i.cjs} +3 -3
  43. package/dist/{codexCommand-B_xUH79E.mjs → codexCommand-BGx5NMQR.mjs} +49 -46
  44. package/dist/{codexCommand-BNoD1t1C.cjs → codexCommand-DJDPfpIs.cjs} +49 -46
  45. package/dist/{codexTermination-Dx48ma6e.mjs → codexTermination-CVhhwHZU.mjs} +1 -1
  46. package/dist/{codexTermination-BmBbizFK.cjs → codexTermination-CqPMuokw.cjs} +1 -1
  47. package/dist/{config-CtwEUxUP.cjs → config-CHpxzXBL.cjs} +3 -3
  48. package/dist/{config-DSu0Blos.mjs → config-CWAe08-Q.mjs} +3 -3
  49. package/dist/{configuration-CYO-kPOS.cjs → configuration-B-CugKJ3.cjs} +1 -1
  50. package/dist/{configuration-PxzEJumr.mjs → configuration-YXw_Dcsb.mjs} +1 -1
  51. package/dist/{connect-Dq7wndzR.cjs → connect-BmHvMXH2.cjs} +13 -13
  52. package/dist/{connect-BQrg3TB6.mjs → connect-CPeJXHha.mjs} +13 -13
  53. package/dist/{context-6TP-iz_c.cjs → context-BtIVqHI5.cjs} +1 -1
  54. package/dist/{context-CJGRRGWW.mjs → context-Drzs_Z38.mjs} +1 -1
  55. package/dist/{controlClient-DoeYovwP.mjs → controlClient-BL4hb4l3.mjs} +3 -3
  56. package/dist/{controlClient-Bqv5v5OM.cjs → controlClient-D0kGwKYx.cjs} +3 -3
  57. package/dist/{createSessionMetadata-DDIChCx8.mjs → createSessionMetadata-DvGDsnsn.mjs} +2 -2
  58. package/dist/{createSessionMetadata-xz767pcG.cjs → createSessionMetadata-L499emJn.cjs} +2 -2
  59. package/dist/{defaultPermissionMode-i5KzdK09.mjs → defaultPermissionMode-BLfxu-Yz.mjs} +2 -2
  60. package/dist/{defaultPermissionMode-PkZGsQDk.cjs → defaultPermissionMode-MXpZlXud.cjs} +2 -2
  61. package/dist/{deterministicJson-CTEJDqtS.mjs → deterministicJson-B_UAbmQ4.mjs} +87 -24
  62. package/dist/{deterministicJson-xqA6DaOm.cjs → deterministicJson-CfLkBRvU.cjs} +87 -22
  63. package/dist/{doctor-BrgSlfXn.cjs → doctor-DMvlzmnE.cjs} +8 -7
  64. package/dist/{doctor-IctjVh9T.mjs → doctor-bKScPtZu.mjs} +8 -8
  65. package/dist/{downloadTarget-CEb9che-.cjs → downloadTarget-D33BFvew.cjs} +6 -6
  66. package/dist/{downloadTarget-jbOz1egC.mjs → downloadTarget-J9j37eFG.mjs} +6 -6
  67. package/dist/{ensureDaemonRunning-D-HyjGIX.cjs → ensureDaemonRunning-2IqWROJp.cjs} +5 -5
  68. package/dist/{ensureDaemonRunning-b1e8LCSM.mjs → ensureDaemonRunning-DLvwhpNg.mjs} +5 -5
  69. package/dist/fileStore-5qpetZS0.mjs +146 -0
  70. package/dist/fileStore-7qhnbs6N.cjs +152 -0
  71. package/dist/fsMkdir-0K3yENml.cjs +56 -0
  72. package/dist/fsMkdir-DumgLTzR.mjs +53 -0
  73. package/dist/{handleResumeCommand-DpaZvm2c.mjs → handleResumeCommand-CjtjQ_I7.mjs} +5 -5
  74. package/dist/{handleResumeCommand-BvWVuRwH.cjs → handleResumeCommand-Dp2Y-p6V.cjs} +5 -5
  75. package/dist/{index-C6Vn5Eac.mjs → index-BI0h2Zks.mjs} +54 -6
  76. package/dist/{index-CMf5VbzG.mjs → index-BOT0Tg5E.mjs} +4 -4
  77. package/dist/{index-C64Ufj4D.cjs → index-B_aNaW9w.cjs} +4 -4
  78. package/dist/{index-F2UejqYs.cjs → index-CZC7egJA.cjs} +3 -3
  79. package/dist/{index-Bsvbgh9e.mjs → index-Cllzqoes.mjs} +4 -4
  80. package/dist/{index-B2p2Mut7.cjs → index-DYVIbhAJ.cjs} +78 -60
  81. package/dist/{index-wCbtF4BK.mjs → index-D_LvOWUN.mjs} +3 -3
  82. package/dist/{index-DmTlDRSL.cjs → index-DymOv3kK.cjs} +4 -4
  83. package/dist/{index-BUNnpGFF.mjs → index-kA46KMxN.mjs} +79 -61
  84. package/dist/{index-Dv0Q48P3.cjs → index-oh8XEozJ.cjs} +57 -6
  85. package/dist/index.cjs +2 -2
  86. package/dist/index.mjs +2 -2
  87. package/dist/{install-C0ANcEAW.cjs → install-BAa_Xmof.cjs} +3 -3
  88. package/dist/{install-CdzSV7qO.mjs → install-C46mkr9a.mjs} +3 -3
  89. package/dist/{install-BX3lv3NO.mjs → install-D0inLCv-.mjs} +1 -1
  90. package/dist/{install-BXvf2E3o.cjs → install-D2NCbeJ-.cjs} +1 -1
  91. package/dist/lib.cjs +12 -12
  92. package/dist/lib.d.cts +40 -1
  93. package/dist/lib.d.mts +40 -1
  94. package/dist/lib.mjs +12 -12
  95. package/dist/{lidState-bBrmaQd2.mjs → lidState-D8Iuvjgl.mjs} +1 -1
  96. package/dist/{lidState-DWGnm2H4.cjs → lidState-DcPvwniz.cjs} +1 -1
  97. package/dist/{localHappyAgentAuth-Dn1jYDpt.mjs → localHappyAgentAuth-D3rQDRd9.mjs} +1 -1
  98. package/dist/{localHappyAgentAuth-DZHQnIW9.cjs → localHappyAgentAuth-SD6GM97y.cjs} +1 -1
  99. package/dist/{logger-CphW5nUE.cjs → logger-DK1ytCz6.cjs} +2 -2
  100. package/dist/{logger-BUngm5Xg.mjs → logger-DpqihN1f.mjs} +2 -2
  101. package/dist/{machineMetadata-BXN_bKU3.mjs → machineMetadata-D4E3qoIG.mjs} +13 -4
  102. package/dist/{machineMetadata-BQm9YTcI.cjs → machineMetadata-IJc6_iyx.cjs} +13 -3
  103. package/dist/{main-DGkdYfEq.mjs → main-CpUmw7hm.mjs} +45 -45
  104. package/dist/{main-B2UV51nK.cjs → main-DUXh2rgW.cjs} +45 -45
  105. package/dist/{mcp-CbTtbs0f.mjs → mcp-66g1TJ8P.mjs} +17 -15
  106. package/dist/{mcp-BFvAEIRz.cjs → mcp-D5f3Vd-z.cjs} +17 -15
  107. package/dist/{mirrorProtocol-HKPivs5o.cjs → mirrorProtocol-DkWOcZh9.cjs} +16 -153
  108. package/dist/{mirrorProtocol-BR4luBC0.mjs → mirrorProtocol-bsCUcaCY.mjs} +19 -151
  109. package/dist/{package-KhLqBDX9.cjs → package-BfeqAzyJ.cjs} +8 -8
  110. package/dist/{package-7Ld1YMhh.mjs → package-CKj8zndE.mjs} +8 -8
  111. package/dist/{panels-Njf6GelF.cjs → panels-2PXsw9-d.cjs} +5 -5
  112. package/dist/{panels-C7IVqpJk.mjs → panels-r8LmaBs6.mjs} +5 -5
  113. package/dist/{peerTools-DQNlrcqM.cjs → peerTools-BVp1z0T_.cjs} +5 -5
  114. package/dist/{peerTools-BOlDRyhi.mjs → peerTools-D_id4d41.mjs} +5 -5
  115. package/dist/{persistence-9BMYspxG.cjs → persistence-BdpwWXKh.cjs} +2 -2
  116. package/dist/{persistence-BFUOHW5h.mjs → persistence-jOEuAFTS.mjs} +2 -2
  117. package/dist/pi-CqyoZMjI.mjs +380 -0
  118. package/dist/pi-DlsrPCTa.cjs +400 -0
  119. package/dist/{piRunnerArgs-H2pgpA5z.mjs → piRunnerArgs-68I5F_M1.mjs} +1 -1
  120. package/dist/{piRunnerArgs-DGaq6VYM.cjs → piRunnerArgs-vxCHKnSA.cjs} +1 -1
  121. package/dist/{piRuntime-CT5xv_3h.cjs → piRuntime-C3Dg5uni.cjs} +2 -2
  122. package/dist/{piRuntime-CdN0JAsa.mjs → piRuntime-DgLIard1.mjs} +2 -2
  123. package/dist/{piTerminal-0FBtXTHK.cjs → piTerminal-C135BcLP.cjs} +20 -18
  124. package/dist/{piTerminal-C4m9YXAV.mjs → piTerminal-DJL0w-uA.mjs} +20 -18
  125. package/dist/{prompt-lgF9IE82.cjs → prompt-Bof2aQkI.cjs} +7 -2
  126. package/dist/{prompt-BOouDaTM.mjs → prompt-QHuFx9j6.mjs} +7 -2
  127. package/dist/{pushNotifications-DCiBEVeC.mjs → pushNotifications-CMmRsbKv.mjs} +12 -4
  128. package/dist/{pushNotifications-DI92havG.cjs → pushNotifications-CyQVDThC.cjs} +12 -4
  129. package/dist/{query-BETKBrnz.cjs → query-CK--1fbL.cjs} +1 -1
  130. package/dist/{query-ywZbQUzR.mjs → query-CPXnDURZ.mjs} +1 -1
  131. package/dist/{rateLimitWindows-BkfuUB1a.mjs → rateLimitWindows-Cqa4AGaB.mjs} +1 -1
  132. package/dist/{rateLimitWindows-YRIYVdo4.cjs → rateLimitWindows-DJxnNw-X.cjs} +1 -1
  133. package/dist/{registerCommonHandlers-C7a_dUWs.cjs → registerCommonHandlers-DbNoM39q.cjs} +2 -2
  134. package/dist/{registerCommonHandlers-CTkCvjxt.mjs → registerCommonHandlers-K8dICQO2.mjs} +2 -2
  135. package/dist/{registerKillSessionHandler-CIsvBrVe.cjs → registerKillSessionHandler-DiS_go8d.cjs} +6 -6
  136. package/dist/{registerKillSessionHandler-CaqqRYq6.mjs → registerKillSessionHandler-O1iriOMO.mjs} +6 -6
  137. package/dist/{remoteSessionClient-PP-mOKmw.mjs → remoteSessionClient-BlURPulk.mjs} +5 -5
  138. package/dist/{remoteSessionClient-D843fkzL.cjs → remoteSessionClient-CtV9mDko.cjs} +5 -5
  139. package/dist/{reportProgressTool-14AXr959.cjs → reportProgressTool-DlD_xjzU.cjs} +4 -4
  140. package/dist/{reportProgressTool-ZOCNjWHr.mjs → reportProgressTool-nCExrHZH.mjs} +4 -4
  141. package/dist/{resources-CvIlkzpP.cjs → resources-BPyN59Iv.cjs} +23 -2
  142. package/dist/{resources-y9jfyO-_.mjs → resources-CH1iP6Cv.mjs} +22 -3
  143. package/dist/{run-CAOCYYfT.cjs → run-BBNrcCfI.cjs} +621 -594
  144. package/dist/{run-Dajw2E5j.mjs → run-drtwHYkO.mjs} +557 -530
  145. package/dist/{runClaude-CRRKt-tm.cjs → runClaude-B3KSeby4.cjs} +23 -22
  146. package/dist/{runClaude-C6oxWYbp.mjs → runClaude-CFp27Tyi.mjs} +23 -22
  147. package/dist/{runClaudeDeps-B1ysqTvx.cjs → runClaudeDeps-C_C9TCI0.cjs} +43 -41
  148. package/dist/{runClaudeDeps-BcZ68cX3.mjs → runClaudeDeps-DMcv93lA.mjs} +48 -46
  149. package/dist/{runGemini-D7yKc4wD.mjs → runGemini-Bc6SY-hd.mjs} +40 -37
  150. package/dist/{runGemini-DtKFXqf-.cjs → runGemini-DzdSBAr9.cjs} +40 -37
  151. package/dist/{runOpenClaw-C8ilyLP9.mjs → runOpenClaw-DI0qMlgF.mjs} +19 -18
  152. package/dist/{runOpenClaw-Cq29mryb.cjs → runOpenClaw-DN6rEVqC.cjs} +19 -18
  153. package/dist/{sandbox-gIj-EuF4.cjs → sandbox-DtttPBMA.cjs} +4 -4
  154. package/dist/{sandbox-CbCutma4.mjs → sandbox-v-ffe8LR.mjs} +4 -4
  155. package/dist/{selfCheck-BTGBVthv.cjs → selfCheck-B-yy6bW3.cjs} +36 -36
  156. package/dist/selfCheck-BDfTwxqB.mjs +85 -0
  157. package/dist/{send-YZRMQvMJ.mjs → send-DpBwSZuj.mjs} +8 -8
  158. package/dist/{send-CZf5lLw_.cjs → send-ShzXYPRu.cjs} +8 -8
  159. package/dist/{server-CknFq6xG.cjs → server-CEzKrfKj.cjs} +6 -6
  160. package/dist/{server-BqwXRzFZ.mjs → server-DScgJXxa.mjs} +4 -4
  161. package/dist/{sessionLock-nBXdJxJm.mjs → sessionLock-B6K4SBZD.mjs} +3 -3
  162. package/dist/{sessionLock-DRZUcKHT.cjs → sessionLock-Dc0fadGG.cjs} +3 -3
  163. package/dist/{sessionOps-C58iDeD5.mjs → sessionOps-D0BPWPoh.mjs} +4 -4
  164. package/dist/{sessionOps-BrGiU9-s.cjs → sessionOps-Dx7wARjI.cjs} +4 -4
  165. package/dist/{sessionScanner-Bm57LEXe.mjs → sessionScanner-9Fpgaxtf.mjs} +3 -3
  166. package/dist/{sessionScanner-CaO8bLaG.cjs → sessionScanner-BJ35TVG6.cjs} +3 -3
  167. package/dist/{sessions-BXyBHo8n.mjs → sessions-CFtDlyZd.mjs} +10 -10
  168. package/dist/{sessions-uzqPCc37.cjs → sessions-TZGGAry1.cjs} +10 -10
  169. package/dist/{setupOfflineReconnection-DvvVX3TB.cjs → setupOfflineReconnection-B1GsVA8P.cjs} +2 -2
  170. package/dist/{setupOfflineReconnection-1Qlek3wl.mjs → setupOfflineReconnection-Biaus-0T.mjs} +2 -2
  171. package/dist/{spawn-DBkodJEO.cjs → spawn-BRTuypQ2.cjs} +14 -14
  172. package/dist/{spawn-vn93PhYL.mjs → spawn-CsoQ-Ecz.mjs} +14 -14
  173. package/dist/{spawnAgents-DLNMlhWJ.cjs → spawnAgents-CG211IW7.cjs} +1 -1
  174. package/dist/{spawnAgents-CgPMWexP.mjs → spawnAgents-Co-SLcx-.mjs} +1 -1
  175. package/dist/{spawnHappyCLI-BjFZc_7V.mjs → spawnHappyCLI-D1doCjkZ.mjs} +1 -1
  176. package/dist/{spawnHappyCLI-DnUZv0X2.cjs → spawnHappyCLI-D47kX6jg.cjs} +1 -1
  177. package/dist/{specialCommands-BRufxlqe.cjs → specialCommands-Ch-Gvy-i.cjs} +3 -3
  178. package/dist/{specialCommands-D3ND9bdg.mjs → specialCommands-xSmqT_Fu.mjs} +3 -3
  179. package/dist/{startFileWatcher-DXJ7yB5v.mjs → startFileWatcher-CCl2fFxT.mjs} +1 -1
  180. package/dist/{startFileWatcher-D9cYzZOq.cjs → startFileWatcher-gKQTstR6.cjs} +1 -1
  181. package/dist/{startupSessionCreate-kdr3m1uX.cjs → startupSessionCreate-BXqoUX30.cjs} +1 -1
  182. package/dist/{startupSessionCreate-uZ6BM0ty.mjs → startupSessionCreate-DlZc8yPl.mjs} +1 -1
  183. package/dist/{streamRelay-Dy6amlzx.cjs → streamRelay-B-J-4St1.cjs} +1 -1
  184. package/dist/{streamRelay-DnMCMMfV.mjs → streamRelay-DnJXsi36.mjs} +1 -1
  185. package/dist/{sync-BvVhDO40.mjs → sync-BbjMC3sR.mjs} +1 -1
  186. package/dist/{sync-Bn-idAoy.cjs → sync-BxMdnZzz.cjs} +1 -1
  187. package/dist/{systemPrompt-DlyEk35T.cjs → systemPrompt-C2KojRmK.cjs} +3 -3
  188. package/dist/{systemPrompt-CIZcJuJF.mjs → systemPrompt-CmSDAoIx.mjs} +3 -3
  189. package/dist/{teams-BLeRpDxv.cjs → teams-DESdalox.cjs} +13 -10
  190. package/dist/{teams-DLY3B5yi.mjs → teams-lRy1MS0Z.mjs} +13 -10
  191. package/dist/{time-D3ETL7Jb.mjs → time-Cfknl7xG.mjs} +1 -1
  192. package/dist/{time-Cjv0zxQO.cjs → time-DNLeIRgR.cjs} +1 -1
  193. package/dist/{titleGenerator-Uzbd4utr.mjs → titleGenerator-ClnMSzwC.mjs} +1 -1
  194. package/dist/{titleGenerator-Cgp5ihFb.cjs → titleGenerator-YNutaU88.cjs} +2 -2
  195. package/dist/{todo-pOMcEECE.cjs → todo-DS75Mboz.cjs} +6 -6
  196. package/dist/{todo-C9Ga-oEM.mjs → todo-DZbK5POc.mjs} +6 -6
  197. package/dist/{tools-Cho7iIWf.mjs → tools-BgdU0dWp.mjs} +3 -3
  198. package/dist/{tools-BL7Byl_x.cjs → tools-ClYcb1YQ.cjs} +3 -3
  199. package/dist/{uninstall-CbayEVoV.cjs → uninstall-Di1vPBLV.cjs} +3 -3
  200. package/dist/{uninstall-CvZHVY9C.mjs → uninstall-bXenFevR.mjs} +3 -3
  201. package/package.json +8 -8
  202. package/dist/selfCheck-Smt-5Xiq.mjs +0 -85
@@ -1,24 +1,25 @@
1
- import { i as isClaudeSessionId, M as ModelConfigError, B as BackupStore, g as readConfigFile, h as sha256, j as atomicWriteFile, k as extractCompleteLines, m as decideBackfill, n as parseTerminalHookPayload, o as decideMirrorBinding, q as MIRROR_BACKFILL_LINES_DEFAULT, t as mirrorLineKey, u as mirrorLocalId, v as initializeOwnedWorktree, e as summarizeSpawnSessionForLog, w as startAgentVersionChecks, W as WORKTREE_CAPABILITY_VERSION, x as currentWorktreeService } from './mirrorProtocol-BR4luBC0.mjs';
2
- import { P as PI_ADAPTER_INSTALL_HINT, s as sanitizeSpawnModel } from './piRunnerArgs-H2pgpA5z.mjs';
1
+ import { i as isClaudeSessionId, g as extractCompleteLines, h as decideBackfill, j as parseTerminalHookPayload, k as decideMirrorBinding, M as MIRROR_BACKFILL_LINES_DEFAULT, m as mirrorLineKey, n as mirrorLocalId, o as initializeOwnedWorktree, e as summarizeSpawnSessionForLog, q as startAgentVersionChecks, W as WORKTREE_CAPABILITY_VERSION, t as currentWorktreeService } from './mirrorProtocol-bsCUcaCY.mjs';
2
+ import { F as FS_MKDIR_CAPABILITY_VERSION } from './fsMkdir-DumgLTzR.mjs';
3
+ import { P as PI_ADAPTER_INSTALL_HINT, s as sanitizeSpawnModel } from './piRunnerArgs-68I5F_M1.mjs';
3
4
  import fs from 'fs/promises';
4
5
  import os from 'os';
5
6
  import * as tmp from 'tmp';
6
7
  import axios from 'axios';
7
- import { T as TurnActivityTracker, A as ApiClient } from './api-Hov8p7CA.mjs';
8
- import { S as SPAWN_AGENTS, i as isSpawnAgent, s as sanitizeSpawnPermissionMode, r as resolveSpawnPermissionMode, a as resolveFirstMessageMeta, j as joinProject } from './spawnAgents-CgPMWexP.mjs';
9
- import { l as logger } from './logger-BUngm5Xg.mjs';
10
- import { readdirSync, statSync, unlinkSync, realpathSync, promises, mkdirSync, writeFileSync, accessSync, constants as constants$1, existsSync, readFileSync, openSync, readSync, closeSync, fsyncSync, renameSync, rmdirSync } from 'node:fs';
8
+ import { readFileSync, openSync, fsyncSync, closeSync, renameSync, existsSync, readdirSync, statSync, unlinkSync, realpathSync, promises, writeFileSync, mkdirSync, accessSync, constants as constants$1, readSync, rmdirSync } from 'node:fs';
11
9
  import { join, isAbsolute, resolve, dirname, sep, basename, delimiter } from 'node:path';
12
- import { p as pruneStaleCompileCaches } from './index-wCbtF4BK.mjs';
13
- import { a as authAndSetupMachineIfNeeded } from './auth-BG1DY0gj.mjs';
14
- import { c as configuration, f as ensurePrivateDirectorySync, w as writePrivateFileSync } from './configuration-PxzEJumr.mjs';
10
+ import { f as ensurePrivateDirectorySync, w as writePrivateFileSync, c as configuration } from './configuration-YXw_Dcsb.mjs';
11
+ import { T as TurnActivityTracker, A as ApiClient } from './api-C_YSQnsi.mjs';
12
+ import { S as SPAWN_AGENTS, i as isSpawnAgent, s as sanitizeSpawnPermissionMode, r as resolveSpawnPermissionMode, a as resolveFirstMessageMeta, j as joinProject } from './spawnAgents-Co-SLcx-.mjs';
13
+ import { l as logger } from './logger-DpqihN1f.mjs';
14
+ import { p as pruneStaleCompileCaches } from './index-D_LvOWUN.mjs';
15
+ import { a as authAndSetupMachineIfNeeded } from './auth-CGgpiJ5-.mjs';
15
16
  import { spawn, execSync } from 'child_process';
16
- import { p as packageJson } from './package-7Ld1YMhh.mjs';
17
- import { a as globalPrefixForPackage, g as getEnvironmentInfo } from './doctor-IctjVh9T.mjs';
18
- import { s as spawnHappyCLI } from './spawnHappyCLI-BjFZc_7V.mjs';
19
- import { c as readPersistedSessions, r as readSettings, l as latchKeepAwake, q as reportSignature, s as decideDaemonHibernation, t as nextCheckDelay, v as persistSession, d as readDaemonState, x as acquireDaemonLock, a as resolveCheckMs, y as resolveIdleMs, z as deletePersistedSessions, A as writeDaemonState, B as writeSettings, C as settingIdleMs, u as updateSettings, D as rewriteCredentialsForRelay, g as readCredentials, E as releaseDaemonLock } from './persistence-BFUOHW5h.mjs';
20
- import { i as isDaemonRunningCurrentlyInstalledHappyVersion, s as stopDaemon, t as cleanupDaemonState } from './controlClient-DoeYovwP.mjs';
21
- import { e as sendUserMessage, f as readSessionMetadata, r as readSessionTranscript, l as listAccountSessions, a as listSessions, M as MAX_READ_LIMIT, s as sessionWebUrl, g as waitForSessionKey, d as archiveSession, j as analyzeLatestTurn, k as readSessionLog, m as TURN_WINDOW, n as userPromptText } from './sessionOps-C58iDeD5.mjs';
17
+ import { p as packageJson } from './package-CKj8zndE.mjs';
18
+ import { a as globalPrefixForPackage, N as NPM_SAFE_ARG, g as getEnvironmentInfo } from './doctor-bKScPtZu.mjs';
19
+ import { s as spawnHappyCLI } from './spawnHappyCLI-D1doCjkZ.mjs';
20
+ import { c as readPersistedSessions, r as readSettings, l as latchKeepAwake, q as reportSignature, s as decideDaemonHibernation, t as nextCheckDelay, v as persistSession, d as readDaemonState, x as acquireDaemonLock, a as resolveCheckMs, y as resolveIdleMs, z as deletePersistedSessions, A as writeDaemonState, B as writeSettings, C as settingIdleMs, u as updateSettings, D as rewriteCredentialsForRelay, g as readCredentials, E as releaseDaemonLock } from './persistence-jOEuAFTS.mjs';
21
+ import { i as isDaemonRunningCurrentlyInstalledHappyVersion, s as stopDaemon, t as cleanupDaemonState } from './controlClient-BL4hb4l3.mjs';
22
+ import { e as sendUserMessage, f as readSessionMetadata, r as readSessionTranscript, l as listAccountSessions, a as listSessions, M as MAX_READ_LIMIT, s as sessionWebUrl, g as waitForSessionKey, d as archiveSession, j as analyzeLatestTurn, k as readSessionLog, m as TURN_WINDOW, n as userPromptText } from './sessionOps-D0BPWPoh.mjs';
22
23
  import fastify from 'fastify';
23
24
  import { z } from 'zod';
24
25
  import { validatorCompiler, serializerCompiler } from 'fastify-type-provider-zod';
@@ -26,60 +27,62 @@ import { d as decodeBase64, s as sameRelay, e as encodeBase64, h as decrypt } fr
26
27
  import { randomBytes, createHash, timingSafeEqual, randomUUID } from 'node:crypto';
27
28
  import { c as checkPreviewPath } from './previewPath-CXN4Vag0.mjs';
28
29
  import { a as BACKGROUND_TASKS_MAX, d as BACKGROUND_TASK_DESCRIPTION_MAX, e as BackgroundTaskTracker } from './backgroundTaskActivity-8PdPxrvy.mjs';
29
- import { b as sanitizeRateLimitReport } from './rateLimitWindows-BkfuUB1a.mjs';
30
- import { f as formatEditConflictNotice, b as SESSION_PEER_SENT_FROM, s as sendPeerMessage, l as listPeerSessions, C as CLI_PEER_SENDER_ID } from './peerTools-BOlDRyhi.mjs';
30
+ import { b as sanitizeRateLimitReport } from './rateLimitWindows-Cqa4AGaB.mjs';
31
+ import { f as formatEditConflictNotice, b as SESSION_PEER_SENT_FROM, s as sendPeerMessage, l as listPeerSessions, C as CLI_PEER_SENDER_ID } from './peerTools-D_id4d41.mjs';
31
32
  import os$1, { hostname, homedir, userInfo, tmpdir } from 'node:os';
32
- import { c as RemoteCallBudget, e as REMOTE_WRITE_CALLS_PER_MINUTE, d as deliverToSession, g as guardRemoteSessionOpsRequest, h as remoteError, i as REMOTE_OP_DEADLINE_MS, j as formatRemoteAuditLine, k as REMOTE_SEND_COMMIT_DEADLINE_MS, m as REMOTE_RESUME_WAIT_MS, n as capRemoteTranscript, o as capRemoteText, p as REMOTE_ANSWER_MAX_BYTES, q as REMOTE_TITLE_MAX_BYTES } from './remoteSessionClient-PP-mOKmw.mjs';
33
- import { assistantHome, bootstrapAssistantHome } from './bootstrap-Dt5I1SOl.mjs';
34
- import { refreshAgentHomes, locateClaudeConversation, agentHomeSpawnEnv } from './index-Bsvbgh9e.mjs';
33
+ import { c as RemoteCallBudget, e as REMOTE_WRITE_CALLS_PER_MINUTE, d as deliverToSession, g as guardRemoteSessionOpsRequest, h as remoteError, i as REMOTE_OP_DEADLINE_MS, j as formatRemoteAuditLine, k as REMOTE_SEND_COMMIT_DEADLINE_MS, m as REMOTE_RESUME_WAIT_MS, n as capRemoteTranscript, o as capRemoteText, p as REMOTE_ANSWER_MAX_BYTES, q as REMOTE_TITLE_MAX_BYTES } from './remoteSessionClient-BlURPulk.mjs';
34
+ import { assistantHome, bootstrapAssistantHome } from './bootstrap-D3vlOROj.mjs';
35
+ import { refreshAgentHomes, locateClaudeConversation, agentHomeSpawnEnv } from './index-Cllzqoes.mjs';
35
36
  import require$$0, { existsSync as existsSync$1, statSync as statSync$1, mkdtempSync, rmSync } from 'fs';
36
37
  import require$$1, { join as join$1 } from 'path';
37
38
  import { spawn as spawn$1, execFile, spawnSync } from 'node:child_process';
38
39
  import { p as projectPath } from './projectPath-BTBIMB57.mjs';
39
40
  import { d as daemonEndpointsMatch, r as resolveClaudeCredentialReadiness } from './doctorReadiness-C208SMN9.mjs';
40
- import { o as ownerRetryPending, c as consumeOwnerRetry, s as spawnGate, a as afterStartupFailure, b as startupFailureMessage, T as TEAM_SPAWN_MAX_ATTEMPTS, d as TEAM_CLEANUP_RETRY_MS, g as getInitialMachineMetadata, e as getStartupCliAvailability, f as TEAM_RETRY_CAPABILITY_VERSION } from './machineMetadata-BXN_bKU3.mjs';
41
+ import { o as ownerRetryPending, c as consumeOwnerRetry, s as spawnGate, a as afterStartupFailure, b as startupFailureMessage, T as TEAM_SPAWN_MAX_ATTEMPTS, d as TEAM_CLEANUP_RETRY_MS, g as getInitialMachineMetadata, e as getStartupCliAvailability, f as TEAM_RETRY_CAPABILITY_VERSION, w as withRunningCliFacts } from './machineMetadata-D4E3qoIG.mjs';
42
+ import { M as ModelConfigError, B as BackupStore, r as readConfigFile, s as sha256, a as atomicWriteFile } from './fileStore-5qpetZS0.mjs';
43
+ import { c as collectSecretValues, a as parseJsonObject$1, b as childObject, s as setOrDelete, d as changedPaths, e as stringifyLike, f as fail, n as normalizeModelList, g as normalizeModelId, h as normalizeSecret, i as normalizeBaseUrl, u as uniq, j as baseUrlWarnings, m as maskSecret, k as getPath$1, l as normalizeProviderId, o as piAdapter, r as redactText } from './pi-CqyoZMjI.mjs';
41
44
  import require$$3 from 'crypto';
42
45
  import { c as commandOnPath } from './detectCLI-CSGMlV51.mjs';
43
- import { buildResumeLaunch } from './handleResumeCommand-DpaZvm2c.mjs';
46
+ import { buildResumeLaunch } from './handleResumeCommand-CjtjQ_I7.mjs';
44
47
  import { n as normalizeWebPanels } from './webPanels-B2_C2-3g.mjs';
45
- import { w as ServerMigrateNoticeSchema, x as AUTOMATION_SUMMARY_MAX_CHARS, y as AUTOMATION_PRECHECK_OUTPUT_MAX_CHARS, z as AUTOMATION_PRECHECK_DEFAULT_TIMEOUT_MS, b as AUTOMATION_PRECHECK_MAX_TIMEOUT_MS, D as AUTOMATION_CLAIM_MAX_RUNS, E as AUTOMATION_DAEMON_CAPABILITY_PRECHECK, F as AUTOMATION_DEFAULT_LEASE_MS, i as isAutomationRunTerminal } from './index-C6Vn5Eac.mjs';
48
+ import { x as ServerMigrateNoticeSchema, y as isHarnessInstallId, z as AUTOMATION_SUMMARY_MAX_CHARS, D as AUTOMATION_PRECHECK_OUTPUT_MAX_CHARS, E as AUTOMATION_PRECHECK_DEFAULT_TIMEOUT_MS, b as AUTOMATION_PRECHECK_MAX_TIMEOUT_MS, F as AUTOMATION_CLAIM_MAX_RUNS, G as AUTOMATION_DAEMON_CAPABILITY_PRECHECK, H as AUTOMATION_DEFAULT_LEASE_MS, i as isAutomationRunTerminal, I as HARNESS_INSTALL_CAPABILITY_VERSION } from './index-BI0h2Zks.mjs';
46
49
  import { access, symlink, readFile, rm, unlink, readlink, lstat, constants, stat, open } from 'node:fs/promises';
47
50
  import { p as parseExactVersion, c as compareExactVersions, f as fetchCliUpdateState, r as resolveCliUpdateCheckInterval, D as DEFAULT_CLI_UPDATE_CHECK_INTERVAL_MS } from './cliUpdate-Y34VGe9Y.mjs';
48
- import { P as ProjectsUnsupportedError, c as createProjectsClient } from './client-Ca5Pcgy_.mjs';
49
- import { c as createAutomationsClient, i as isAutomationsUnavailable, d as AutomationsRequestError } from './client-CnB7kRCT.mjs';
51
+ import { P as ProjectsUnsupportedError, c as createProjectsClient } from './client-o-meK7-a.mjs';
52
+ import { c as createAutomationsClient, i as isAutomationsUnavailable, d as AutomationsRequestError } from './client-Ctbw05Xj.mjs';
50
53
  import { createRequire } from 'node:module';
51
54
  import { g as getProjectPath } from './path-D2wdaNSk.mjs';
52
55
  import { b as extractClaudeEditPaths } from './editPaths-BVfS_FFL.mjs';
53
- import { s as startFileWatcher } from './startFileWatcher-DXJ7yB5v.mjs';
54
- import { I as InvalidateSync } from './sync-BvVhDO40.mjs';
56
+ import { s as startFileWatcher } from './startFileWatcher-CCl2fFxT.mjs';
57
+ import { I as InvalidateSync } from './sync-BbjMC3sR.mjs';
55
58
  import { R as RawJSONLinesSchema } from './types-CkqAACXx.mjs';
56
59
  import { t as tmuxArgs, a as scrubTmuxClientEnv, s as shellescape } from './tmuxSocket-B8Yx20sT.mjs';
57
- import { p as processIdentityFields, r as readSessionLock, t as terminateProcess, d as terminateProcessTree } from './sessionLock-nBXdJxJm.mjs';
60
+ import { p as processIdentityFields, r as readSessionLock, t as terminateProcess, d as terminateProcessTree } from './sessionLock-B6K4SBZD.mjs';
58
61
  import { findAllHappyProcesses } from './doctor-BPkYWSuq.mjs';
59
- import { T as TEAM_SKILL } from './resources-y9jfyO-_.mjs';
62
+ import { T as TEAM_SKILL, a as TEAM_LEAD_PLAYBOOK, b as TEAM_MEMBER_RULES } from './resources-CH1iP6Cv.mjs';
60
63
  import { promisify } from 'node:util';
61
64
  import { c as clipText, r as renderAction, b as renderTemplate } from './template-CiOPa2rd.mjs';
62
65
  import { d as detectDaemonSupervisor } from './daemonSupervisor-C6urv3_8.mjs';
63
66
  import { d as describeAgentHome } from './loginShellEnv-Dw8M7tkI.mjs';
64
- import './acpSessionResume-B9l54P-X.mjs';
65
- import './lidState-bBrmaQd2.mjs';
67
+ import './acpSessionResume-u2TwX5Jd.mjs';
68
+ import './lidState-D8Iuvjgl.mjs';
66
69
  import 'node:events';
67
70
  import 'socket.io-client';
68
- import './pushNotifications-DCiBEVeC.mjs';
71
+ import './pushNotifications-CMmRsbKv.mjs';
69
72
  import './contentLogMetadata-BlWI0egu.mjs';
70
73
  import './limits-DIo7rEBR.mjs';
71
- import './time-D3ETL7Jb.mjs';
72
- import './registerCommonHandlers-CTkCvjxt.mjs';
74
+ import './time-Cfknl7xG.mjs';
75
+ import './registerCommonHandlers-K8dICQO2.mjs';
73
76
  import 'util';
74
77
  import 'cross-spawn';
75
78
  import '@paralleldrive/cuid2';
76
79
  import 'chalk';
77
80
  import 'tweetnacl';
78
- import './browser-DUNetFT4.mjs';
81
+ import './browser-CUxGoATV.mjs';
79
82
  import 'open';
80
83
  import './ids-DU6sLx4C.mjs';
81
84
  import 'url';
82
- import './localHappyAgentAuth-Dn1jYDpt.mjs';
85
+ import './localHappyAgentAuth-D3rQDRd9.mjs';
83
86
  import './processTreeSnapshot-CuLJDNC-.mjs';
84
87
  import 'ps-list';
85
88
 
@@ -92,6 +95,87 @@ async function resolveRelaunchMetadata(snapshot, fetchServer) {
92
95
  return { metadata: snapshot, source: "snapshot" };
93
96
  }
94
97
 
98
+ function receiptPath(home, operationId) {
99
+ if (!/^[a-zA-Z0-9_-]{1,128}$/.test(operationId)) throw new Error("Invalid team operation id");
100
+ return join(home, "teams", "effects", `${operationId}.json`);
101
+ }
102
+ function readReceipt(home, id) {
103
+ try {
104
+ const value = JSON.parse(readFileSync(receiptPath(home, id), "utf8"));
105
+ if (value.operationId !== id || typeof value.claimId !== "string" || !["prepared", "spawning", "spawned", "delivered", "completed"].includes(value.phase)) throw new Error("Invalid team effect receipt");
106
+ return value;
107
+ } catch (error) {
108
+ if (error.code === "ENOENT") return null;
109
+ throw error;
110
+ }
111
+ }
112
+ function writeReceipt$1(home, receipt) {
113
+ ensurePrivateDirectorySync(join(home, "teams", "effects"));
114
+ const target = receiptPath(home, receipt.operationId);
115
+ const tmp = `${target}.${process.pid}.tmp`;
116
+ writePrivateFileSync(tmp, JSON.stringify(receipt));
117
+ const file = openSync(tmp, "r");
118
+ try {
119
+ fsyncSync(file);
120
+ } finally {
121
+ closeSync(file);
122
+ }
123
+ renameSync(tmp, target);
124
+ if (process.platform !== "win32") {
125
+ const directory = openSync(join(home, "teams", "effects"), "r");
126
+ try {
127
+ fsyncSync(directory);
128
+ } finally {
129
+ closeSync(directory);
130
+ }
131
+ }
132
+ }
133
+
134
+ const OPERATION_ID = /^[a-zA-Z0-9_-]{1,128}$/;
135
+ function teamScopeFilePath(home, operationId) {
136
+ if (!OPERATION_ID.test(operationId)) throw new Error("Invalid team operation id");
137
+ return join(home, "teams", "scopes", `${operationId}.json`);
138
+ }
139
+ function relaunchTeamOperationId(home, sessionId, metadata) {
140
+ const fromMetadata = metadata?.teamOperationId;
141
+ if (typeof fromMetadata === "string" && OPERATION_ID.test(fromMetadata)) return fromMetadata;
142
+ let names;
143
+ try {
144
+ names = readdirSync(join(home, "teams", "effects"));
145
+ } catch {
146
+ return null;
147
+ }
148
+ let newest = null;
149
+ for (const name of names) {
150
+ if (!name.endsWith(".json")) continue;
151
+ const id = name.slice(0, -".json".length);
152
+ if (!OPERATION_ID.test(id)) continue;
153
+ try {
154
+ const receipt = readReceipt(home, id);
155
+ if (receipt?.sessionId !== sessionId || !receipt.scopeFile) continue;
156
+ const at = statSync(receiptPath(home, id)).mtimeMs;
157
+ if (!newest || at > newest.at) newest = { id, at };
158
+ } catch {
159
+ }
160
+ }
161
+ return newest?.id ?? null;
162
+ }
163
+ async function teamRelaunchEnv(input) {
164
+ const operationId = relaunchTeamOperationId(input.home, input.sessionId, input.metadata);
165
+ if (!operationId) return {};
166
+ const scopeFile = teamScopeFilePath(input.home, operationId);
167
+ if (!existsSync(scopeFile) && input.recover) {
168
+ try {
169
+ await input.recover(operationId, scopeFile);
170
+ input.log(`Teams scope for ${input.sessionId} rebuilt from operation ${operationId}`);
171
+ } catch (error) {
172
+ input.log(`Teams scope for ${input.sessionId} could not be rebuilt: ${error instanceof Error ? error.message : "unknown error"}`);
173
+ }
174
+ }
175
+ if (!existsSync(scopeFile)) input.log(`Teams scope for ${input.sessionId} is missing; team tools will refuse until the owner restores it`);
176
+ return { VH_TEAM_SCOPE_FILE: scopeFile, VH_TEAM_OPERATION_ID: operationId, HAPPY_SPAWNED_BY: "teams" };
177
+ }
178
+
95
179
  const DEFAULT_LOG_PRUNE_POLICY = {
96
180
  maxAgeMs: 14 * 24 * 60 * 6e4,
97
181
  maxTotalBytes: 200 * 1024 * 1024,
@@ -2172,196 +2256,6 @@ function unifiedDiff(oldText, newText, label, context = 3) {
2172
2256
  `;
2173
2257
  }
2174
2258
 
2175
- const SECRET_KEY_PATTERN = /(api[_-]?key|apikey|token|secret|password|passwd|authorization|bearer|credential|cookie|private[_-]?key)/i;
2176
- const EXTRA_SECRET_KEYS = /* @__PURE__ */ new Set(["ANTHROPIC_CUSTOM_HEADERS"]);
2177
- function isSecretKey(key) {
2178
- return SECRET_KEY_PATTERN.test(key) || EXTRA_SECRET_KEYS.has(key);
2179
- }
2180
- function maskSecret(value) {
2181
- const v = value.trim();
2182
- if (!v) return "";
2183
- if (v.length < 16) return "\u2022\u2022\u2022\u2022";
2184
- return `${v.slice(0, 4)}\u2026${v.slice(-4)}`;
2185
- }
2186
- function collectSecretValues(value, out = /* @__PURE__ */ new Set(), parentKey = "") {
2187
- if (typeof value === "string") {
2188
- if (parentKey && isSecretKey(parentKey) && value.length >= 4) out.add(value);
2189
- return out;
2190
- }
2191
- if (Array.isArray(value)) {
2192
- for (const item of value) collectSecretValues(item, out, parentKey);
2193
- return out;
2194
- }
2195
- if (value && typeof value === "object") {
2196
- for (const [key, child] of Object.entries(value)) {
2197
- collectSecretValues(child, out, key);
2198
- }
2199
- }
2200
- return out;
2201
- }
2202
- function redactText(text, secrets) {
2203
- const list = [...new Set(secrets)].filter((s) => s.length >= 4).sort((a, b) => b.length - a.length);
2204
- let out = text;
2205
- for (const secret of list) {
2206
- out = out.split(secret).join(maskSecret(secret));
2207
- }
2208
- return out;
2209
- }
2210
- const ENV_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]{0,127}$/;
2211
- function isValidEnvName(name) {
2212
- return typeof name === "string" && ENV_NAME_PATTERN.test(name);
2213
- }
2214
-
2215
- function parseJsonObject$1(text, label) {
2216
- if (text.trim() === "") return {};
2217
- let value;
2218
- try {
2219
- value = JSON.parse(text);
2220
- } catch (error) {
2221
- const hasComments = /^\s*\/\/|\/\*/m.test(text);
2222
- throw new ModelConfigError(
2223
- hasComments ? "json-has-comments" : "invalid-json",
2224
- hasComments ? `${label} contains comments; Very Happy only edits strict JSON so it never drops them. Edit it by hand or remove the comments.` : `${label} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`
2225
- );
2226
- }
2227
- if (!value || typeof value !== "object" || Array.isArray(value)) {
2228
- throw new ModelConfigError("invalid-json", `${label} must contain a JSON object`);
2229
- }
2230
- return value;
2231
- }
2232
- function detectIndent(text) {
2233
- const m = /^([ \t]+)"/m.exec(text);
2234
- return m ? m[1] : 2;
2235
- }
2236
- function stringifyLike(original, value) {
2237
- const compact = original.trim() !== "" && !original.trim().includes("\n");
2238
- const body = compact ? JSON.stringify(value) : JSON.stringify(value, null, detectIndent(original));
2239
- const eol = original.includes("\r\n") ? "\r\n" : "\n";
2240
- const withEol = eol === "\r\n" ? body.replace(/\n/g, "\r\n") : body;
2241
- return original === "" || /\r?\n$/.test(original) ? withEol + eol : withEol;
2242
- }
2243
- function setOrDelete(obj, key, value) {
2244
- if (value === void 0 || value === null || value === "") delete obj[key];
2245
- else obj[key] = value;
2246
- }
2247
- function childObject(obj, key, label) {
2248
- const current = obj[key];
2249
- if (current === void 0) {
2250
- const created = {};
2251
- obj[key] = created;
2252
- return created;
2253
- }
2254
- if (!current || typeof current !== "object" || Array.isArray(current)) {
2255
- throw new ModelConfigError("unexpected-shape", `${label}.${key} is not an object; refusing to overwrite it`);
2256
- }
2257
- return current;
2258
- }
2259
-
2260
- const MODEL_ID = /^[A-Za-z0-9._:/@+\-[\]]{1,200}$/;
2261
- const PROVIDER_ID = /^[A-Za-z0-9_-]{1,64}$/;
2262
- function fail(code, message) {
2263
- throw new ModelConfigError(code, message);
2264
- }
2265
- function normalizeBaseUrl(value, field = "baseUrl") {
2266
- if (value === void 0) return void 0;
2267
- if (value === null) return null;
2268
- if (typeof value !== "string") fail("invalid-input", `${field} must be a string`);
2269
- const v = value.trim();
2270
- if (v === "") return null;
2271
- if (v.length > 2048 || /\s/.test(v)) fail("invalid-input", `${field} is not a valid URL`);
2272
- let url;
2273
- try {
2274
- url = new URL(v);
2275
- } catch {
2276
- fail("invalid-input", `${field} is not a valid URL`);
2277
- }
2278
- if (url.protocol !== "http:" && url.protocol !== "https:") fail("invalid-input", `${field} must be http(s)`);
2279
- if (url.username || url.password) fail("invalid-input", `${field} must not embed credentials; use the API key field`);
2280
- return v;
2281
- }
2282
- function normalizeModelId(value, field = "model") {
2283
- if (value === void 0) return void 0;
2284
- if (value === null) return null;
2285
- if (typeof value !== "string") fail("invalid-input", `${field} must be a string`);
2286
- const v = value.trim();
2287
- if (v === "") return null;
2288
- if (!MODEL_ID.test(v)) fail("invalid-input", `${field} contains unsupported characters`);
2289
- return v;
2290
- }
2291
- function normalizeModelList(value, field = "models") {
2292
- if (value === void 0) return void 0;
2293
- if (!Array.isArray(value)) fail("invalid-input", `${field} must be an array`);
2294
- if (value.length > 64) fail("invalid-input", `${field} has too many entries`);
2295
- const out = [];
2296
- for (const item of value) {
2297
- const id = normalizeModelId(item, field);
2298
- if (id && !out.includes(id)) out.push(id);
2299
- }
2300
- return out;
2301
- }
2302
- function normalizeProviderId(value, reserved = []) {
2303
- if (typeof value !== "string" || !PROVIDER_ID.test(value.trim())) fail("invalid-input", "providerId must be 1-64 letters, digits, - or _");
2304
- const v = value.trim();
2305
- if (reserved.includes(v)) fail("invalid-input", `providerId "${v}" is reserved by the harness`);
2306
- return v;
2307
- }
2308
- function normalizeSecret(value, allowed = ["keep", "clear", "literal", "env"]) {
2309
- if (value === void 0) return { mode: "keep" };
2310
- if (!value || typeof value !== "object") fail("invalid-input", "apiKey must be an object");
2311
- const v = value;
2312
- const mode = v.mode;
2313
- if (typeof mode !== "string" || !allowed.includes(mode)) fail("invalid-input", `apiKey.mode must be one of ${allowed.join(", ")}`);
2314
- if (mode === "literal") {
2315
- if (typeof v.value !== "string") fail("invalid-input", "apiKey.value must be a string");
2316
- const key = v.value.trim();
2317
- if (!key || key.length > 4096 || /[\s\u0000-\u001f\u007f]/.test(key)) fail("invalid-input", "apiKey.value must be a single line without spaces");
2318
- return { mode: "literal", value: key };
2319
- }
2320
- if (mode === "env") {
2321
- if (!isValidEnvName(v.name)) fail("invalid-input", "apiKey.name must be an environment variable name");
2322
- return { mode: "env", name: v.name };
2323
- }
2324
- return { mode };
2325
- }
2326
- function baseUrlWarnings(url, kind) {
2327
- if (!url) return [];
2328
- const path = (() => {
2329
- try {
2330
- return new URL(url).pathname.replace(/\/+$/, "");
2331
- } catch {
2332
- return "";
2333
- }
2334
- })();
2335
- const out = [];
2336
- if (/\/(chat\/completions|responses|messages|completions)$/.test(path)) {
2337
- out.push({ code: "base-url-has-endpoint", message: "The base URL ends with an API endpoint path; harnesses append that themselves." });
2338
- }
2339
- if (kind === "anthropic" && /\/v1$/.test(path)) {
2340
- out.push({ code: "base-url-v1", message: "Claude Code appends /v1/messages; a base URL ending in /v1 usually produces /v1/v1/messages." });
2341
- }
2342
- if (kind === "openai" && !/\/v\d/.test(path)) {
2343
- out.push({ code: "base-url-no-version", message: "OpenAI-compatible base URLs usually end in /v1." });
2344
- }
2345
- return out;
2346
- }
2347
-
2348
- function getPath$1(obj, path) {
2349
- let cur = obj;
2350
- for (const p of path) {
2351
- if (!cur || typeof cur !== "object" || Array.isArray(cur)) return void 0;
2352
- cur = cur[p];
2353
- }
2354
- return cur;
2355
- }
2356
- function changedPaths(before, after, paths) {
2357
- return paths.filter((path) => JSON.stringify(getPath$1(before, path)) !== JSON.stringify(getPath$1(after, path))).map((path) => path.join("."));
2358
- }
2359
- function uniq(values) {
2360
- const out = [];
2361
- for (const v of values) if (v && !out.includes(v)) out.push(v);
2362
- return out;
2363
- }
2364
-
2365
2259
  const ROLE$1 = "settings";
2366
2260
  const AUTH_TOKEN = "ANTHROPIC_AUTH_TOKEN";
2367
2261
  const API_KEY$1 = "ANTHROPIC_API_KEY";
@@ -2386,7 +2280,7 @@ function claudeSettingsPath(ctx) {
2386
2280
  const dir = ctx.env.CLAUDE_CONFIG_DIR?.trim() || join(ctx.home, ".claude");
2387
2281
  return join(dir, "settings.json");
2388
2282
  }
2389
- function str$4(v) {
2283
+ function str$3(v) {
2390
2284
  return typeof v === "string" && v.trim() ? v : null;
2391
2285
  }
2392
2286
  function envOf(settings) {
@@ -2399,11 +2293,11 @@ function pickerOptions(settings) {
2399
2293
  const options = picker.options;
2400
2294
  return Array.isArray(options) ? options.filter((o) => !!o && typeof o === "object" && typeof o.model === "string") : [];
2401
2295
  }
2402
- function secretView$2(settings, ctx) {
2296
+ function secretView$1(settings, ctx) {
2403
2297
  const env = envOf(settings);
2404
- const literal = str$4(env[AUTH_TOKEN]) ?? str$4(env[API_KEY$1]);
2298
+ const literal = str$3(env[AUTH_TOKEN]) ?? str$3(env[API_KEY$1]);
2405
2299
  if (literal) return { mode: "literal", masked: maskSecret(literal) };
2406
- const helper = str$4(settings.apiKeyHelper);
2300
+ const helper = str$3(settings.apiKeyHelper);
2407
2301
  if (helper) {
2408
2302
  const ours = OUR_HELPER.exec(helper.trim());
2409
2303
  if (ours) return { mode: "env", envName: ours[1], envPresent: !!ctx.env[ours[1]] };
@@ -2411,26 +2305,26 @@ function secretView$2(settings, ctx) {
2411
2305
  }
2412
2306
  return { mode: "none" };
2413
2307
  }
2414
- function currentOf$3(settings, ctx) {
2308
+ function currentOf$2(settings, ctx) {
2415
2309
  const env = envOf(settings);
2416
2310
  return {
2417
- baseUrl: str$4(env.ANTHROPIC_BASE_URL),
2418
- authHeader: !str$4(env[AUTH_TOKEN]) && str$4(env[API_KEY$1]) ? "x-api-key" : "bearer",
2419
- apiKey: secretView$2(settings, ctx),
2420
- model: str$4(settings.model),
2421
- opusModel: str$4(env[ALIAS_VARS.opusModel]),
2422
- sonnetModel: str$4(env[ALIAS_VARS.sonnetModel]),
2423
- haikuModel: str$4(env[ALIAS_VARS.haikuModel]),
2311
+ baseUrl: str$3(env.ANTHROPIC_BASE_URL),
2312
+ authHeader: !str$3(env[AUTH_TOKEN]) && str$3(env[API_KEY$1]) ? "x-api-key" : "bearer",
2313
+ apiKey: secretView$1(settings, ctx),
2314
+ model: str$3(settings.model),
2315
+ opusModel: str$3(env[ALIAS_VARS.opusModel]),
2316
+ sonnetModel: str$3(env[ALIAS_VARS.sonnetModel]),
2317
+ haikuModel: str$3(env[ALIAS_VARS.haikuModel]),
2424
2318
  pickerModels: pickerOptions(settings).map((o) => o.model)
2425
2319
  };
2426
2320
  }
2427
- function pickerModelsOf$1(current) {
2321
+ function pickerModelsOf(current) {
2428
2322
  return uniq([current.model, current.opusModel, current.sonnetModel, current.haikuModel, ...current.pickerModels ?? []]);
2429
2323
  }
2430
- function warningsOf$3(settings, current, ctx) {
2324
+ function warningsOf$2(settings, current, ctx) {
2431
2325
  const env = envOf(settings);
2432
2326
  const out = [...baseUrlWarnings(current.baseUrl, "anthropic")];
2433
- if (str$4(env.ANTHROPIC_MODEL)) {
2327
+ if (str$3(env.ANTHROPIC_MODEL)) {
2434
2328
  out.push({ code: "env-model-overrides", message: "env.ANTHROPIC_MODEL is set in settings.json and overrides the default model." });
2435
2329
  }
2436
2330
  const daemonOther = current.authHeader === "bearer" ? API_KEY$1 : AUTH_TOKEN;
@@ -2442,7 +2336,7 @@ function warningsOf$3(settings, current, ctx) {
2442
2336
  }
2443
2337
  if (Array.isArray(settings.availableModels)) {
2444
2338
  const allowed = settings.availableModels;
2445
- const missing = pickerModelsOf$1(current).filter((id) => !allowed.includes(id));
2339
+ const missing = pickerModelsOf(current).filter((id) => !allowed.includes(id));
2446
2340
  if (missing.length) out.push({ code: "available-models-filter", message: `availableModels does not list ${missing.join(", ")}; Claude Code hides them.` });
2447
2341
  }
2448
2342
  return out;
@@ -2468,8 +2362,8 @@ const claudeAdapter = {
2468
2362
  files: (ctx) => [{ role: ROLE$1, path: claudeSettingsPath(ctx) }],
2469
2363
  view(files, ctx) {
2470
2364
  const settings = parseJsonObject$1(files[ROLE$1].content, "settings.json");
2471
- const current = currentOf$3(settings, ctx);
2472
- return { current, pickerModels: pickerModelsOf$1(current), warnings: warningsOf$3(settings, current, ctx) };
2365
+ const current = currentOf$2(settings, ctx);
2366
+ return { current, pickerModels: pickerModelsOf(current), warnings: warningsOf$2(settings, current, ctx) };
2473
2367
  },
2474
2368
  plan(files, raw, ctx) {
2475
2369
  const patch = parsePatch$1(raw);
@@ -2479,7 +2373,7 @@ const claudeAdapter = {
2479
2373
  const hadEnv = settings.env !== void 0;
2480
2374
  const env = childObject(settings, "env", "settings");
2481
2375
  if (patch.baseUrl !== void 0) setOrDelete(env, "ANTHROPIC_BASE_URL", patch.baseUrl);
2482
- const currentHeader = !str$4(env[AUTH_TOKEN]) && str$4(env[API_KEY$1]) ? "x-api-key" : "bearer";
2376
+ const currentHeader = !str$3(env[AUTH_TOKEN]) && str$3(env[API_KEY$1]) ? "x-api-key" : "bearer";
2483
2377
  const header = patch.authHeader ?? currentHeader;
2484
2378
  const target = header === "bearer" ? AUTH_TOKEN : API_KEY$1;
2485
2379
  const other = header === "bearer" ? API_KEY$1 : AUTH_TOKEN;
@@ -2503,7 +2397,7 @@ const claudeAdapter = {
2503
2397
  if (helperIsOurs) delete settings.apiKeyHelper;
2504
2398
  break;
2505
2399
  case "keep":
2506
- if (header !== currentHeader && str$4(env[other]) && !str$4(env[target])) {
2400
+ if (header !== currentHeader && str$3(env[other]) && !str$3(env[target])) {
2507
2401
  env[target] = env[other];
2508
2402
  delete env[other];
2509
2403
  }
@@ -2523,15 +2417,15 @@ const claudeAdapter = {
2523
2417
  if (!hadPicker && Object.keys(picker).length === 0) delete settings.modelPicker;
2524
2418
  }
2525
2419
  if (!hadEnv && Object.keys(env).length === 0) delete settings.env;
2526
- const current = currentOf$3(settings, ctx);
2420
+ const current = currentOf$2(settings, ctx);
2527
2421
  if (Array.isArray(settings.availableModels)) {
2528
2422
  const allowed = settings.availableModels;
2529
- const add = pickerModelsOf$1(current).filter((id) => !allowed.includes(id));
2423
+ const add = pickerModelsOf(current).filter((id) => !allowed.includes(id));
2530
2424
  if (add.length) settings.availableModels = [...allowed, ...add];
2531
2425
  }
2532
2426
  const changedKeys = changedPaths(before, settings, OWNED_PATHS);
2533
2427
  const content = changedKeys.length === 0 ? void 0 : stringifyLike(original, settings);
2534
- return { contents: { [ROLE$1]: content }, warnings: warningsOf$3(settings, current, ctx), changedKeys, secrets };
2428
+ return { contents: { [ROLE$1]: content }, warnings: warningsOf$2(settings, current, ctx), changedKeys, secrets };
2535
2429
  },
2536
2430
  secretsIn(_role, content) {
2537
2431
  try {
@@ -3591,52 +3485,52 @@ function codexConfigPath(ctx) {
3591
3485
  const dir = ctx.env.CODEX_HOME?.trim() || join(ctx.home, ".codex");
3592
3486
  return join(dir, "config.toml");
3593
3487
  }
3594
- const str$3 = (v) => typeof v === "string" && v.trim() ? v : null;
3595
- function providersOf$1(doc) {
3488
+ const str$2 = (v) => typeof v === "string" && v.trim() ? v : null;
3489
+ function providersOf(doc) {
3596
3490
  const table = doc.model_providers;
3597
3491
  if (!table || typeof table !== "object" || Array.isArray(table)) return {};
3598
3492
  return Object.fromEntries(Object.entries(table).filter(([, v]) => v && typeof v === "object" && !Array.isArray(v)));
3599
3493
  }
3600
- function secretView$1(provider, ctx) {
3494
+ function secretView(provider, ctx) {
3601
3495
  if (!provider) return { mode: "none" };
3602
- const token = str$3(provider.experimental_bearer_token);
3496
+ const token = str$2(provider.experimental_bearer_token);
3603
3497
  if (token) return { mode: "literal", masked: maskSecret(token) };
3604
- const envKey = str$3(provider.env_key);
3498
+ const envKey = str$2(provider.env_key);
3605
3499
  if (envKey) return { mode: "env", envName: envKey, envPresent: !!ctx.env[envKey] };
3606
3500
  if (provider.auth && typeof provider.auth === "object") return { mode: "command" };
3607
3501
  return { mode: "none" };
3608
3502
  }
3609
3503
  function selectProvider(doc, requested) {
3610
- const providers = providersOf$1(doc);
3504
+ const providers = providersOf(doc);
3611
3505
  if (requested) return requested;
3612
- const active = str$3(doc.model_provider);
3506
+ const active = str$2(doc.model_provider);
3613
3507
  if (active && providers[active]) return active;
3614
3508
  return Object.keys(providers)[0] ?? null;
3615
3509
  }
3616
- function currentOf$2(doc, providerId, ctx) {
3617
- const provider = providerId ? providersOf$1(doc)[providerId] : void 0;
3618
- const activeProvider = str$3(doc.model_provider);
3510
+ function currentOf$1(doc, providerId, ctx) {
3511
+ const provider = providerId ? providersOf(doc)[providerId] : void 0;
3512
+ const activeProvider = str$2(doc.model_provider);
3619
3513
  return {
3620
3514
  providerId,
3621
- providerName: str$3(provider?.name),
3622
- baseUrl: str$3(provider?.base_url),
3623
- apiKey: secretView$1(provider, ctx),
3624
- model: str$3(doc.model),
3515
+ providerName: str$2(provider?.name),
3516
+ baseUrl: str$2(provider?.base_url),
3517
+ apiKey: secretView(provider, ctx),
3518
+ model: str$2(doc.model),
3625
3519
  activeProvider,
3626
3520
  active: !!providerId && activeProvider === providerId
3627
3521
  };
3628
3522
  }
3629
- function warningsOf$2(doc, current) {
3523
+ function warningsOf$1(doc, current) {
3630
3524
  const out = [...baseUrlWarnings(current.baseUrl, "openai")];
3631
- const provider = current.providerId ? providersOf$1(doc)[current.providerId] : void 0;
3632
- if (provider && str$3(provider.wire_api) && provider.wire_api !== "responses") {
3525
+ const provider = current.providerId ? providersOf(doc)[current.providerId] : void 0;
3526
+ if (provider && str$2(provider.wire_api) && provider.wire_api !== "responses") {
3633
3527
  out.push({ code: "wire-api-unsupported", message: `wire_api = "${String(provider.wire_api)}" is rejected by current Codex; only "responses" is supported.` });
3634
3528
  }
3635
3529
  if (current.apiKey?.mode === "env" && !current.apiKey.envPresent) {
3636
3530
  out.push({ code: "env-missing", message: `${current.apiKey.envName} is not set in the daemon's environment, so sessions will not get a key.` });
3637
3531
  }
3638
3532
  const active = current.activeProvider;
3639
- if (active && !CODEX_RESERVED_PROVIDERS.includes(active) && !providersOf$1(doc)[active]) {
3533
+ if (active && !CODEX_RESERVED_PROVIDERS.includes(active) && !providersOf(doc)[active]) {
3640
3534
  out.push({ code: "active-provider-missing", message: `model_provider = "${active}" has no [model_providers.${active}] table.` });
3641
3535
  }
3642
3536
  return out;
@@ -3662,25 +3556,25 @@ const codexAdapter = {
3662
3556
  files: (ctx) => [{ role: ROLE, path: codexConfigPath(ctx) }],
3663
3557
  view(files, ctx, opts) {
3664
3558
  const doc = parseToml(files[ROLE].content);
3665
- const current = currentOf$2(doc, selectProvider(doc, opts.providerId), ctx);
3666
- return { current, providers: Object.keys(providersOf$1(doc)), pickerModels: uniq([current.model]), warnings: warningsOf$2(doc, current) };
3559
+ const current = currentOf$1(doc, selectProvider(doc, opts.providerId), ctx);
3560
+ return { current, providers: Object.keys(providersOf(doc)), pickerModels: uniq([current.model]), warnings: warningsOf$1(doc, current) };
3667
3561
  },
3668
3562
  plan(files, raw, ctx) {
3669
3563
  const patch = parsePatch(raw);
3670
3564
  const original = files[ROLE].content;
3671
3565
  const before = parseToml(original);
3672
- const existing = providersOf$1(before)[patch.providerId];
3566
+ const existing = providersOf(before)[patch.providerId];
3673
3567
  const table = ["model_providers", patch.providerId];
3674
3568
  const edits = [];
3675
3569
  const secrets = [];
3676
3570
  if (!existing && !patch.baseUrl) fail("invalid-input", "A new provider needs a base URL");
3677
- const name = patch.providerName || str$3(existing?.name) || patch.providerId;
3678
- if (!existing || patch.providerName && patch.providerName !== existing.name || !str$3(existing.name)) edits.push({ table, key: "name", value: name });
3571
+ const name = patch.providerName || str$2(existing?.name) || patch.providerId;
3572
+ if (!existing || patch.providerName && patch.providerName !== existing.name || !str$2(existing.name)) edits.push({ table, key: "name", value: name });
3679
3573
  if (patch.baseUrl !== void 0) {
3680
3574
  if (patch.baseUrl === null && existing) fail("invalid-input", "A provider needs a base URL; remove the provider instead");
3681
3575
  edits.push({ table, key: "base_url", value: patch.baseUrl });
3682
3576
  }
3683
- if (!existing || str$3(existing.wire_api) && existing.wire_api !== "responses") edits.push({ table, key: "wire_api", value: "responses" });
3577
+ if (!existing || str$2(existing.wire_api) && existing.wire_api !== "responses") edits.push({ table, key: "wire_api", value: "responses" });
3684
3578
  switch (patch.apiKey.mode) {
3685
3579
  case "literal":
3686
3580
  edits.push({ table, key: "experimental_bearer_token", value: patch.apiKey.value }, { table, key: "env_key", value: null });
@@ -3695,13 +3589,13 @@ const codexAdapter = {
3695
3589
  }
3696
3590
  if (patch.model !== void 0) edits.push({ table: [], key: "model", value: patch.model });
3697
3591
  if (patch.activate === true) edits.push({ table: [], key: "model_provider", value: patch.providerId });
3698
- if (patch.activate === false && str$3(before.model_provider) === patch.providerId) edits.push({ table: [], key: "model_provider", value: null });
3592
+ if (patch.activate === false && str$2(before.model_provider) === patch.providerId) edits.push({ table: [], key: "model_provider", value: null });
3699
3593
  const effective = edits.filter((e) => "removeTable" in e || getPath$1(before, [...e.table, e.key]) !== (e.value ?? void 0));
3700
3594
  const content = effective.length ? editToml(original, effective) : void 0;
3701
3595
  const after = content === void 0 ? before : parseToml(content);
3702
- const current = currentOf$2(after, patch.providerId, ctx);
3596
+ const current = currentOf$1(after, patch.providerId, ctx);
3703
3597
  const changedKeys = effective.map((e) => [...e.table, "removeTable" in e ? "*" : e.key].join("."));
3704
- return { contents: { [ROLE]: content }, warnings: warningsOf$2(after, current), changedKeys, secrets };
3598
+ return { contents: { [ROLE]: content }, warnings: warningsOf$1(after, current), changedKeys, secrets };
3705
3599
  },
3706
3600
  secretsIn(_role, content) {
3707
3601
  try {
@@ -4053,25 +3947,25 @@ function editDotenv(text, edits) {
4053
3947
  return out;
4054
3948
  }
4055
3949
 
4056
- const SETTINGS$1 = "settings";
3950
+ const SETTINGS = "settings";
4057
3951
  const ENV = "env";
4058
3952
  const BASE_URL = "GOOGLE_GEMINI_BASE_URL";
4059
3953
  const API_KEY = "GEMINI_API_KEY";
4060
3954
  function geminiDir(ctx) {
4061
3955
  return join(ctx.env.GEMINI_CLI_HOME?.trim() || ctx.home, ".gemini");
4062
3956
  }
4063
- const str$2 = (v) => typeof v === "string" && v.trim() ? v : null;
4064
- function currentOf$1(settings, env, ctx) {
4065
- const literal = str$2(env[API_KEY]);
3957
+ const str$1 = (v) => typeof v === "string" && v.trim() ? v : null;
3958
+ function currentOf(settings, env, ctx) {
3959
+ const literal = str$1(env[API_KEY]);
4066
3960
  const apiKey = literal ? { mode: "literal", masked: maskSecret(literal) } : ctx.env[API_KEY] ? { mode: "env", envName: API_KEY, envPresent: true } : { mode: "none" };
4067
- return { baseUrl: str$2(env[BASE_URL]), apiKey, model: str$2(getPath$1(settings, ["model", "name"])) };
3961
+ return { baseUrl: str$1(env[BASE_URL]), apiKey, model: str$1(getPath$1(settings, ["model", "name"])) };
4068
3962
  }
4069
- function warningsOf$1(settings, current) {
3963
+ function warningsOf(settings, current) {
4070
3964
  const out = [...baseUrlWarnings(current.baseUrl, "gemini")];
4071
3965
  if (current.baseUrl || current.apiKey?.mode === "literal") {
4072
3966
  out.push({ code: "project-env-shadows", message: "~/.gemini/.env is used only when the session folder (or a parent) has no .gemini/.env or .env of its own." });
4073
3967
  }
4074
- const selected = str$2(getPath$1(settings, ["security", "auth", "selectedType"]));
3968
+ const selected = str$1(getPath$1(settings, ["security", "auth", "selectedType"]));
4075
3969
  if (current.baseUrl && selected && selected !== "gateway") {
4076
3970
  out.push({ code: "auth-type-pinned", message: `security.auth.selectedType is "${selected}"; Gemini only uses GOOGLE_GEMINI_BASE_URL with the gateway auth type.` });
4077
3971
  }
@@ -4083,23 +3977,23 @@ function warningsOf$1(settings, current) {
4083
3977
  const geminiAdapter = {
4084
3978
  id: "gemini",
4085
3979
  binary: "gemini",
4086
- files: (ctx) => [{ role: SETTINGS$1, path: join(geminiDir(ctx), "settings.json") }, { role: ENV, path: join(geminiDir(ctx), ".env") }],
3980
+ files: (ctx) => [{ role: SETTINGS, path: join(geminiDir(ctx), "settings.json") }, { role: ENV, path: join(geminiDir(ctx), ".env") }],
4087
3981
  view(files, ctx) {
4088
- const settings = parseJsonObject$1(files[SETTINGS$1].content, "settings.json");
4089
- const current = currentOf$1(settings, parseDotenv(files[ENV].content), ctx);
4090
- return { current, pickerModels: uniq([current.model]), warnings: warningsOf$1(settings, current) };
3982
+ const settings = parseJsonObject$1(files[SETTINGS].content, "settings.json");
3983
+ const current = currentOf(settings, parseDotenv(files[ENV].content), ctx);
3984
+ return { current, pickerModels: uniq([current.model]), warnings: warningsOf(settings, current) };
4091
3985
  },
4092
3986
  plan(files, raw, ctx) {
4093
3987
  const p = raw;
4094
3988
  const baseUrl = normalizeBaseUrl(p.baseUrl);
4095
3989
  const apiKey = normalizeSecret(p.apiKey, ["keep", "clear", "literal"]);
4096
3990
  const model = normalizeModelId(p.model);
4097
- const settingsText = files[SETTINGS$1].content;
3991
+ const settingsText = files[SETTINGS].content;
4098
3992
  const before = parseJsonObject$1(settingsText, "settings.json");
4099
3993
  const settings = structuredClone(before);
4100
3994
  const changedKeys = [];
4101
3995
  let settingsOut;
4102
- if (model !== void 0 && (str$2(getPath$1(before, ["model", "name"])) ?? null) !== model) {
3996
+ if (model !== void 0 && (str$1(getPath$1(before, ["model", "name"])) ?? null) !== model) {
4103
3997
  const hadModel = settings.model !== void 0;
4104
3998
  const modelObj = childObject(settings, "model", "settings");
4105
3999
  setOrDelete(modelObj, "name", model);
@@ -4116,10 +4010,10 @@ const geminiAdapter = {
4116
4010
  const envOut = edits.length ? editDotenv(envText, edits) : void 0;
4117
4011
  changedKeys.push(...edits.map((e) => `.env.${e.key}`));
4118
4012
  const afterEnv = envOut === void 0 ? env : parseDotenv(envOut);
4119
- const current = currentOf$1(settings, afterEnv, ctx);
4013
+ const current = currentOf(settings, afterEnv, ctx);
4120
4014
  return {
4121
- contents: { [SETTINGS$1]: settingsOut, [ENV]: envOut },
4122
- warnings: warningsOf$1(settings, current),
4015
+ contents: { [SETTINGS]: settingsOut, [ENV]: envOut },
4016
+ warnings: warningsOf(settings, current),
4123
4017
  changedKeys,
4124
4018
  secrets: apiKey.mode === "literal" ? [apiKey.value] : []
4125
4019
  };
@@ -4133,149 +4027,6 @@ const geminiAdapter = {
4133
4027
  }
4134
4028
  };
4135
4029
 
4136
- const MODELS = "models";
4137
- const SETTINGS = "settings";
4138
- const PI_APIS = ["openai-completions", "openai-responses", "anthropic-messages", "google-generative-ai", "mistral-conversations"];
4139
- const ENV_REF = /^\$\{?([A-Za-z_][A-Za-z0-9_]*)\}?$/;
4140
- function piAgentDir(ctx) {
4141
- return ctx.env.PI_CODING_AGENT_DIR?.trim() || join(ctx.home, ".pi", "agent");
4142
- }
4143
- const str$1 = (v) => typeof v === "string" && v.trim() ? v : null;
4144
- function providersOf(models) {
4145
- const p = models.providers;
4146
- if (!p || typeof p !== "object" || Array.isArray(p)) return {};
4147
- return Object.fromEntries(Object.entries(p).filter(([, v]) => v && typeof v === "object" && !Array.isArray(v)));
4148
- }
4149
- function modelIds(provider) {
4150
- const list = provider?.models;
4151
- return Array.isArray(list) ? list.flatMap((m) => m && typeof m === "object" && typeof m.id === "string" ? [m.id] : []) : [];
4152
- }
4153
- function secretView(provider, ctx) {
4154
- const key = str$1(provider?.apiKey);
4155
- if (!key) return { mode: "none" };
4156
- const ref = ENV_REF.exec(key.trim());
4157
- if (ref) return { mode: "env", envName: ref[1], envPresent: !!ctx.env[ref[1]] };
4158
- if (key.trim().startsWith("!")) return { mode: "command" };
4159
- return { mode: "literal", masked: maskSecret(key) };
4160
- }
4161
- function currentOf(models, settings, providerId, ctx) {
4162
- const provider = providerId ? providersOf(models)[providerId] : void 0;
4163
- const activeProvider = str$1(settings.defaultProvider);
4164
- return {
4165
- providerId,
4166
- baseUrl: str$1(provider?.baseUrl),
4167
- api: str$1(provider?.api),
4168
- apiKey: secretView(provider, ctx),
4169
- models: modelIds(provider),
4170
- activeProvider,
4171
- defaultModel: str$1(settings.defaultModel),
4172
- active: !!providerId && activeProvider === providerId
4173
- };
4174
- }
4175
- function pickerModelsOf(models) {
4176
- return uniq(Object.entries(providersOf(models)).flatMap(([id, p]) => modelIds(p).map((m) => `${id}/${m}`)));
4177
- }
4178
- function warningsOf(settings, current) {
4179
- const out = [...baseUrlWarnings(current.baseUrl, current.api === "anthropic-messages" ? "anthropic" : "openai")].filter((w) => w.code !== "base-url-v1");
4180
- if (current.apiKey?.mode === "env" && !current.apiKey.envPresent) {
4181
- out.push({ code: "env-missing", message: `${current.apiKey.envName} is not set in the daemon's environment; pi hides models whose key cannot be resolved.` });
4182
- }
4183
- if (current.providerId && current.apiKey?.mode === "none") {
4184
- out.push({ code: "no-key", message: "pi hides models whose provider has no resolvable API key." });
4185
- }
4186
- if (Array.isArray(settings.enabledModels) && current.providerId) {
4187
- const enabled = settings.enabledModels;
4188
- const hidden = (current.models ?? []).filter((m) => !enabled.includes(`${current.providerId}/${m}`) && !enabled.includes(m));
4189
- if (hidden.length) out.push({ code: "enabled-models-filter", message: `settings.json enabledModels does not include ${hidden.join(", ")}.` });
4190
- }
4191
- return out;
4192
- }
4193
- const piAdapter = {
4194
- id: "pi",
4195
- binary: "pi",
4196
- files: (ctx) => [{ role: MODELS, path: join(piAgentDir(ctx), "models.json") }, { role: SETTINGS, path: join(piAgentDir(ctx), "settings.json") }],
4197
- view(files, ctx, opts) {
4198
- const models = parseJsonObject$1(files[MODELS].content, "models.json");
4199
- const settings = parseJsonObject$1(files[SETTINGS].content, "settings.json");
4200
- const providers = providersOf(models);
4201
- const active = str$1(settings.defaultProvider);
4202
- const selected = opts.providerId ?? (active && providers[active] ? active : Object.keys(providers)[0] ?? null);
4203
- const current = currentOf(models, settings, selected, ctx);
4204
- return { current, providers: Object.keys(providers), pickerModels: pickerModelsOf(models), warnings: warningsOf(settings, current) };
4205
- },
4206
- plan(files, raw, ctx) {
4207
- const p = raw;
4208
- const providerId = normalizeProviderId(p.providerId);
4209
- const baseUrl = normalizeBaseUrl(p.baseUrl);
4210
- if (p.api !== void 0 && !PI_APIS.includes(p.api)) fail("invalid-input", `api must be one of ${PI_APIS.join(", ")}`);
4211
- const apiKey = normalizeSecret(p.apiKey);
4212
- if (apiKey.mode === "literal" && /^[$!]/.test(apiKey.value)) fail("invalid-input", "pi treats a key starting with $ or ! as an env reference / command; use the environment-variable mode");
4213
- const ids = normalizeModelList(p.models);
4214
- const defaultModel = normalizeModelId(p.defaultModel, "defaultModel");
4215
- if (p.activate !== void 0 && typeof p.activate !== "boolean") fail("invalid-input", "activate must be a boolean");
4216
- const modelsText = files[MODELS].content;
4217
- const settingsText = files[SETTINGS].content;
4218
- const modelsBefore = parseJsonObject$1(modelsText, "models.json");
4219
- const settingsBefore = parseJsonObject$1(settingsText, "settings.json");
4220
- const models = structuredClone(modelsBefore);
4221
- const settings = structuredClone(settingsBefore);
4222
- const providers = childObject(models, "providers", "models.json");
4223
- const isNew = providers[providerId] === void 0;
4224
- if (isNew && !baseUrl) fail("invalid-input", "A new provider needs a base URL");
4225
- const provider = childObject(providers, providerId, "models.json.providers");
4226
- if (baseUrl !== void 0) {
4227
- if (baseUrl === null) fail("invalid-input", "A provider needs a base URL");
4228
- provider.baseUrl = baseUrl;
4229
- }
4230
- if (p.api !== void 0) provider.api = p.api;
4231
- else if (isNew) provider.api = "openai-completions";
4232
- const secrets = [];
4233
- if (apiKey.mode === "literal") {
4234
- provider.apiKey = apiKey.value;
4235
- secrets.push(apiKey.value);
4236
- }
4237
- if (apiKey.mode === "env") provider.apiKey = `\${${apiKey.name}}`;
4238
- if (apiKey.mode === "clear") delete provider.apiKey;
4239
- if (ids !== void 0) {
4240
- const existing = Array.isArray(provider.models) ? provider.models : [];
4241
- const next = ids.map((id) => existing.find((m) => m && typeof m === "object" && m.id === id) ?? { id });
4242
- if (next.length) provider.models = next;
4243
- else delete provider.models;
4244
- }
4245
- const finalIds = modelIds(provider);
4246
- if (p.activate === true) {
4247
- settings.defaultProvider = providerId;
4248
- const chosen = defaultModel ?? (finalIds.includes(str$1(settings.defaultModel) ?? "") ? str$1(settings.defaultModel) : finalIds[0]);
4249
- setOrDelete(settings, "defaultModel", chosen);
4250
- } else if (p.activate === false && str$1(settings.defaultProvider) === providerId) {
4251
- delete settings.defaultProvider;
4252
- delete settings.defaultModel;
4253
- } else if (defaultModel !== void 0 && str$1(settings.defaultProvider) === providerId) {
4254
- setOrDelete(settings, "defaultModel", defaultModel);
4255
- }
4256
- if (defaultModel && !finalIds.includes(defaultModel)) fail("invalid-input", "defaultModel must be one of the provider models");
4257
- const modelKeys = changedPaths(modelsBefore, models, [["providers", providerId, "baseUrl"], ["providers", providerId, "api"], ["providers", providerId, "apiKey"], ["providers", providerId, "models"]]);
4258
- const settingsKeys = changedPaths(settingsBefore, settings, [["defaultProvider"], ["defaultModel"]]);
4259
- const current = currentOf(models, settings, providerId, ctx);
4260
- return {
4261
- contents: {
4262
- [MODELS]: modelKeys.length ? stringifyLike(modelsText, models) : void 0,
4263
- [SETTINGS]: settingsKeys.length ? stringifyLike(settingsText, settings) : void 0
4264
- },
4265
- warnings: warningsOf(settings, current),
4266
- changedKeys: [...modelKeys.map((k) => `models.json.${k}`), ...settingsKeys.map((k) => `settings.json.${k}`)],
4267
- secrets
4268
- };
4269
- },
4270
- secretsIn(_role, content) {
4271
- try {
4272
- return collectSecretValues(parseJsonObject$1(content, "pi config"));
4273
- } catch {
4274
- return /* @__PURE__ */ new Set();
4275
- }
4276
- }
4277
- };
4278
-
4279
4030
  const MODEL_CONFIG_CAPABILITY_VERSION = 1;
4280
4031
  const HARNESS_IDS = ["claude", "codex", "gemini", "pi"];
4281
4032
 
@@ -4768,26 +4519,45 @@ function createUpdateController(deps) {
4768
4519
  let blocked = false;
4769
4520
  let handoverHold;
4770
4521
  let lastPublished = "";
4522
+ let autoUpdateEnabled;
4523
+ let lastInstall = (() => {
4524
+ try {
4525
+ return deps.lastInstall?.read() ?? null;
4526
+ } catch {
4527
+ return null;
4528
+ }
4529
+ })();
4771
4530
  const now = deps.now ?? Date.now;
4531
+ function snapshot() {
4532
+ return {
4533
+ ...policy,
4534
+ retrySupported: !blocked,
4535
+ manualUpdateSupported: !blocked,
4536
+ handoverHold,
4537
+ autoUpdate: outcome,
4538
+ ...autoUpdateEnabled === void 0 ? {} : { autoUpdateEnabled },
4539
+ ...lastInstall ? { lastInstall } : {}
4540
+ };
4541
+ }
4772
4542
  function validManualTarget(next, version) {
4773
4543
  return version === next.recommendedVersion && Number.isFinite(next.checkedAt) && now() - next.checkedAt <= 65 * 6e4 && next.checkedAt <= now() + 6e4 && compareExactVersions(next.currentVersion, version) === -1 && (!next.minimumVersion || [0, 1].includes(compareExactVersions(version, next.minimumVersion) ?? -1));
4774
4544
  }
4775
4545
  function publish(state, version, detail) {
4776
4546
  if (!policy) return;
4777
- const fingerprint = JSON.stringify([policy.checkedAt, state, version, detail, manualVersion]);
4547
+ const fingerprint = JSON.stringify([policy.checkedAt, state, version, detail, manualVersion, autoUpdateEnabled]);
4778
4548
  if (fingerprint === lastPublished) return;
4779
4549
  lastPublished = fingerprint;
4780
4550
  outcome = { state, version, detail, at: now(), ...manualVersion ? { source: "manual" } : {} };
4781
- deps.publish({ ...policy, retrySupported: !blocked, manualUpdateSupported: !blocked, handoverHold, autoUpdate: outcome });
4551
+ deps.publish(snapshot());
4782
4552
  }
4783
4553
  async function evaluate() {
4784
4554
  if (!policy || running) return;
4785
4555
  const target = manualVersion ?? policy.autoUpdateVersion ?? null;
4786
4556
  if (outcome?.version === target && ["failed", "installed", "manual_required"].includes(outcome.state)) {
4787
- const fingerprint = JSON.stringify([policy.checkedAt, outcome.state, outcome.version, outcome.detail, manualVersion]);
4557
+ const fingerprint = JSON.stringify([policy.checkedAt, outcome.state, outcome.version, outcome.detail, manualVersion, autoUpdateEnabled]);
4788
4558
  if (fingerprint !== lastPublished) {
4789
4559
  lastPublished = fingerprint;
4790
- deps.publish({ ...policy, retrySupported: !blocked, manualUpdateSupported: !blocked, handoverHold, autoUpdate: outcome });
4560
+ deps.publish(snapshot());
4791
4561
  }
4792
4562
  return;
4793
4563
  }
@@ -4795,9 +4565,12 @@ function createUpdateController(deps) {
4795
4565
  publish("policy_stale", target);
4796
4566
  return;
4797
4567
  }
4798
- if (!manualVersion && !await deps.enabled()) {
4799
- publish("disabled", target);
4800
- return;
4568
+ if (!manualVersion) {
4569
+ autoUpdateEnabled = await deps.enabled();
4570
+ if (!autoUpdateEnabled) {
4571
+ publish("disabled", target);
4572
+ return;
4573
+ }
4801
4574
  }
4802
4575
  if (running) return;
4803
4576
  if (now() - policy.checkedAt > 65 * 6e4) {
@@ -4827,6 +4600,13 @@ function createUpdateController(deps) {
4827
4600
  return;
4828
4601
  }
4829
4602
  blocked = code === "blocked";
4603
+ if (code === 0) {
4604
+ lastInstall = { from: policy.currentVersion, to: target, at: now(), source: manualVersion ? "manual" : "auto" };
4605
+ try {
4606
+ deps.lastInstall?.write(lastInstall);
4607
+ } catch {
4608
+ }
4609
+ }
4830
4610
  publish(
4831
4611
  blocked ? "manual_required" : code === 0 ? "installed" : "failed",
4832
4612
  target,
@@ -4928,7 +4708,224 @@ function createUpdateController(deps) {
4928
4708
  handoverHold = { reason, at: now() };
4929
4709
  publish("failed", manualVersion ?? policy?.autoUpdateVersion ?? null, "handover_preflight_failed");
4930
4710
  }
4931
- return { refresh, retry, request, tick, withHandover, holdHandover, isRunning: () => running };
4711
+ return { refresh, retry, request, tick, withHandover, holdHandover, isRunning: () => running, currentTarget: () => policy?.autoUpdateVersion ?? null };
4712
+ }
4713
+
4714
+ function parseLastInstall(raw) {
4715
+ if (!raw || typeof raw !== "object") return null;
4716
+ const { from, to, at, source } = raw;
4717
+ if (typeof from !== "string" || typeof to !== "string" || !parseExactVersion(from) || !parseExactVersion(to)) return null;
4718
+ if (typeof at !== "number" || !Number.isFinite(at) || source !== "auto" && source !== "manual") return null;
4719
+ return { from, to, at, source };
4720
+ }
4721
+ function lastInstallStore(homeDir) {
4722
+ const file = join(homeDir, "cli-update-last.json");
4723
+ return {
4724
+ read() {
4725
+ try {
4726
+ return parseLastInstall(JSON.parse(readFileSync(file, "utf8")));
4727
+ } catch {
4728
+ return null;
4729
+ }
4730
+ },
4731
+ write(record) {
4732
+ const tmp = `${file}.${process.pid}.tmp`;
4733
+ writeFileSync(tmp, JSON.stringify(record));
4734
+ renameSync(tmp, file);
4735
+ }
4736
+ };
4737
+ }
4738
+
4739
+ const ANNOUNCE_FIRST_DELAY_MS = 9e4;
4740
+ const ANNOUNCE_JITTER_MS = 6e4;
4741
+ const ANNOUNCE_FOLLOW_UP_MS = 5 * 6e4;
4742
+ function announcedVersion(payload) {
4743
+ if (!payload || typeof payload !== "object") return null;
4744
+ const raw = payload.autoUpdateVersion;
4745
+ if (typeof raw !== "string" || raw.length > 64) return null;
4746
+ return parseExactVersion(raw)?.exact ?? null;
4747
+ }
4748
+ function createPolicyAnnouncementHandler(deps) {
4749
+ const random = deps.random ?? Math.random;
4750
+ const setTimer = deps.setTimer ?? ((fn, ms) => {
4751
+ const t = setTimeout(fn, ms);
4752
+ t.unref?.();
4753
+ return t;
4754
+ });
4755
+ const clearTimer = deps.clearTimer ?? ((handle) => clearTimeout(handle));
4756
+ let timer = null;
4757
+ let pending = null;
4758
+ let stopped = false;
4759
+ function schedule(version, ms, followUp) {
4760
+ if (timer !== null) clearTimer(timer);
4761
+ pending = version;
4762
+ timer = setTimer(() => {
4763
+ timer = null;
4764
+ void deps.refresh().catch(() => void 0).finally(() => {
4765
+ if (stopped || pending !== version) return;
4766
+ if (deps.currentTarget() === version || !followUp) {
4767
+ pending = null;
4768
+ return;
4769
+ }
4770
+ schedule(version, ANNOUNCE_FOLLOW_UP_MS, false);
4771
+ });
4772
+ }, ms);
4773
+ }
4774
+ return {
4775
+ /** Socket handler. Returns the scheduled delay (ms) or null when ignored — for logs and tests. */
4776
+ onAnnouncement(payload) {
4777
+ if (stopped) return null;
4778
+ const version = announcedVersion(payload);
4779
+ if (!version || version === deps.currentTarget() || version === pending) return null;
4780
+ const delay = ANNOUNCE_FIRST_DELAY_MS + Math.floor(random() * ANNOUNCE_JITTER_MS);
4781
+ schedule(version, delay, true);
4782
+ return delay;
4783
+ },
4784
+ stop() {
4785
+ stopped = true;
4786
+ if (timer !== null) clearTimer(timer);
4787
+ timer = null;
4788
+ }
4789
+ };
4790
+ }
4791
+
4792
+ const HARNESS_INSTALL_CATALOG = Object.freeze({
4793
+ claude: Object.freeze({ packages: [{ name: "@anthropic-ai/claude-code", spec: "latest" }], commands: ["claude"], minNode: "22.0.0", scripts: true }),
4794
+ codex: Object.freeze({ packages: [{ name: "@openai/codex", spec: "latest" }], commands: ["codex"], minNode: "16.0.0", scripts: false }),
4795
+ gemini: Object.freeze({ packages: [{ name: "@google/gemini-cli", spec: "latest" }], commands: ["gemini"], minNode: "20.0.0", scripts: false }),
4796
+ pi: Object.freeze({
4797
+ packages: [{ name: "@earendil-works/pi-coding-agent", spec: "latest" }, { name: "pi-acp", spec: "0.0.33" }],
4798
+ commands: ["pi", "pi-acp"],
4799
+ minNode: "22.19.0",
4800
+ scripts: false
4801
+ })
4802
+ });
4803
+ function harnessInstallArgs(id, prefix) {
4804
+ const entry = HARNESS_INSTALL_CATALOG[id];
4805
+ if (!entry) throw new Error("Unknown harness");
4806
+ const args = ["i", "-g", `--prefix=${prefix}`, "--no-fund", "--no-audit", ...entry.scripts ? [] : ["--ignore-scripts"], ...entry.packages.map((p) => `${p.name}@${p.spec}`)];
4807
+ if (args.some((arg) => !NPM_SAFE_ARG.test(arg))) throw new Error("Unsafe npm argument");
4808
+ return args;
4809
+ }
4810
+ function parseNode(version) {
4811
+ const m = /^v?(\d+)\.(\d+)\.(\d+)/.exec(version.trim());
4812
+ return m ? [Number(m[1]), Number(m[2]), Number(m[3])] : null;
4813
+ }
4814
+ function nodeSatisfies(current, minimum) {
4815
+ const a = parseNode(current);
4816
+ const b = parseNode(minimum);
4817
+ if (!a || !b) return false;
4818
+ for (let i = 0; i < 3; i++) if (a[i] !== b[i]) return a[i] > b[i];
4819
+ return true;
4820
+ }
4821
+
4822
+ async function defaultCanWrite(path) {
4823
+ try {
4824
+ await access(path, constants.W_OK);
4825
+ return true;
4826
+ } catch {
4827
+ return false;
4828
+ }
4829
+ }
4830
+ async function defaultIsPlainDirectory(path) {
4831
+ try {
4832
+ const s = await lstat(path);
4833
+ return s.isDirectory() && !s.isSymbolicLink();
4834
+ } catch {
4835
+ return false;
4836
+ }
4837
+ }
4838
+ function redactNpmText(text) {
4839
+ return text.replace(/(_authToken|_auth|_password|token|password)\s*=\s*\S+/gi, "$1=***").replace(/Bearer\s+\S+/gi, "Bearer ***").replace(/([a-z][a-z0-9+.-]*:\/\/)[^\s/@:]+(:[^\s/@]*)?@/gi, "$1***@").replace(/\/(home|Users)\/[^/\s]+/g, "~").replace(/\/root(?=\/|\s|$)/g, "~");
4840
+ }
4841
+ function summarizeNpmFailure(result) {
4842
+ const tail = `${result.stderr ?? ""}
4843
+ ${result.stdout ?? ""}`.trim().split("\n").filter((line) => /npm (error|ERR!)|EACCES|ENOTFOUND|ETARGET|E404|engine/i.test(line)).slice(-3).join(" | ");
4844
+ const code = result.code === null ? "npm_timeout" : `npm_exit_${result.code}`;
4845
+ const safe = redactNpmText(tail).slice(0, 300);
4846
+ return safe ? `${code}: ${safe}` : code;
4847
+ }
4848
+ function createHarnessInstaller(deps) {
4849
+ const now = deps.now ?? Date.now;
4850
+ const canWrite = deps.canWrite ?? defaultCanWrite;
4851
+ const isPlainDirectory = deps.isPlainDirectory ?? defaultIsPlainDirectory;
4852
+ let running = null;
4853
+ let blocked = false;
4854
+ const nodeVersion = deps.nodeVersion ?? process.versions.node;
4855
+ async function installPrefix() {
4856
+ const prefix = globalPrefixForPackage(deps.runningPackageDir);
4857
+ if (!prefix || !await isPlainDirectory(deps.runningPackageDir)) return { error: "install_location_unverified" };
4858
+ for (const path of [dirname(deps.runningPackageDir), join(prefix, "bin")]) {
4859
+ if (!await canWrite(path)) return { error: "install_location_not_writable" };
4860
+ }
4861
+ return { prefix };
4862
+ }
4863
+ async function run(harness, prefix, startedAt) {
4864
+ const publish = (state, detail) => deps.publish({ harness, state, ...detail ? { detail } : {}, startedAt, at: now() });
4865
+ try {
4866
+ const args = harnessInstallArgs(harness, prefix);
4867
+ deps.log(`[HARNESS] installing ${harness}: npm ${args.join(" ")}`);
4868
+ const result = await deps.npm(args);
4869
+ if (result.terminationConfirmed === false) {
4870
+ blocked = true;
4871
+ deps.log(`[HARNESS] ${harness} install timed out and npm could not be confirmed stopped; holding the npm tree until the daemon restarts`);
4872
+ publish("failed", "installer_termination_unconfirmed");
4873
+ return;
4874
+ }
4875
+ if (result.code !== 0) {
4876
+ const detail = summarizeNpmFailure(result);
4877
+ deps.log(`[HARNESS] ${harness} install failed: ${detail}`);
4878
+ publish("failed", detail);
4879
+ return;
4880
+ }
4881
+ const missing = HARNESS_INSTALL_CATALOG[harness].commands.filter((command) => !deps.commandOnPath(command));
4882
+ if (missing.length > 0) {
4883
+ const detail = `not_on_path: ${missing.join(", ")}`;
4884
+ deps.log(`[HARNESS] ${harness} installed into ${prefix} but ${detail}`);
4885
+ publish("failed", detail);
4886
+ return;
4887
+ }
4888
+ await deps.afterInstall().catch((error) => deps.log(`[HARNESS] refresh after ${harness} install failed: ${String(error)}`));
4889
+ deps.log(`[HARNESS] ${harness} installed`);
4890
+ publish("installed");
4891
+ } catch (error) {
4892
+ deps.log(`[HARNESS] ${harness} install error: ${String(error)}`);
4893
+ publish("failed", "install_error");
4894
+ } finally {
4895
+ if (!blocked) running = null;
4896
+ }
4897
+ }
4898
+ return {
4899
+ /** True while npm may be writing the prefix — including after an unconfirmed kill. */
4900
+ isRunning: () => running !== null || blocked,
4901
+ async request(params) {
4902
+ const harness = params && typeof params === "object" ? params.harness : void 0;
4903
+ if (!isHarnessInstallId(harness)) {
4904
+ deps.log(`[HARNESS] refused install request for an unknown harness`);
4905
+ return { error: "unknown_harness" };
4906
+ }
4907
+ if (blocked) return { error: "installer_blocked" };
4908
+ if (running) return { error: "install_in_progress" };
4909
+ if (deps.cliUpdateBusy()) return { error: "cli_update_in_progress" };
4910
+ const minNode = HARNESS_INSTALL_CATALOG[harness].minNode;
4911
+ if (!nodeSatisfies(nodeVersion, minNode)) {
4912
+ deps.log(`[HARNESS] ${harness} install refused: needs Node ${minNode}+, daemon runs ${nodeVersion}`);
4913
+ return { error: "node_too_old", required: minNode, current: nodeVersion };
4914
+ }
4915
+ running = harness;
4916
+ const location = await installPrefix();
4917
+ if ("error" in location) {
4918
+ running = null;
4919
+ deps.log(`[HARNESS] ${harness} install refused: ${location.error}`);
4920
+ return location;
4921
+ }
4922
+ const startedAt = now();
4923
+ deps.log(`[HARNESS] owner requested ${harness} install`);
4924
+ deps.publish({ harness, state: "installing", startedAt, at: startedAt });
4925
+ void run(harness, location.prefix, startedAt);
4926
+ return { accepted: true, harness };
4927
+ }
4928
+ };
4932
4929
  }
4933
4930
 
4934
4931
  function buildPresenceBatch(live, previous) {
@@ -6240,7 +6237,10 @@ function mergeRestoreMetadata(stale, reported) {
6240
6237
  }
6241
6238
 
6242
6239
  function teamInitialPrompt(goal, root, directory) {
6243
- const role = root ? "You are the team lead. Inspect your assigned goal, arrange independent work when useful, review member results, integrate changes, and submit the final result for the user to accept. Delegate under your assigned goal using parentTaskId. You must not accept your own root goal." : "You are a team member. Inspect your assigned task and work in your assigned worktree. Delegate within your task when useful, then submit evidence for the lead to review.";
6240
+ const role = root ? `You are the team lead. Inspect your assigned goal, arrange independent work when useful, review member results, integrate changes, and submit the final result for the user to accept. Delegate under your assigned goal using parentTaskId. You must not accept your own root goal.
6241
+
6242
+ ${TEAM_LEAD_PLAYBOOK}` : `You are a team member. Inspect your assigned task and work in your assigned worktree. Delegate within your task when useful, then submit evidence for the lead to review.
6243
+ ${TEAM_MEMBER_RULES}`;
6244
6244
  return `${TEAM_SKILL}
6245
6245
 
6246
6246
  ## Your role
@@ -6254,42 +6254,6 @@ Delegate from this working directory unless a different repository was explicitl
6254
6254
  ${goal}`;
6255
6255
  }
6256
6256
 
6257
- function receiptPath(home, operationId) {
6258
- if (!/^[a-zA-Z0-9_-]{1,128}$/.test(operationId)) throw new Error("Invalid team operation id");
6259
- return join(home, "teams", "effects", `${operationId}.json`);
6260
- }
6261
- function readReceipt(home, id) {
6262
- try {
6263
- const value = JSON.parse(readFileSync(receiptPath(home, id), "utf8"));
6264
- if (value.operationId !== id || typeof value.claimId !== "string" || !["prepared", "spawning", "spawned", "delivered", "completed"].includes(value.phase)) throw new Error("Invalid team effect receipt");
6265
- return value;
6266
- } catch (error) {
6267
- if (error.code === "ENOENT") return null;
6268
- throw error;
6269
- }
6270
- }
6271
- function writeReceipt$1(home, receipt) {
6272
- ensurePrivateDirectorySync(join(home, "teams", "effects"));
6273
- const target = receiptPath(home, receipt.operationId);
6274
- const tmp = `${target}.${process.pid}.tmp`;
6275
- writePrivateFileSync(tmp, JSON.stringify(receipt));
6276
- const file = openSync(tmp, "r");
6277
- try {
6278
- fsyncSync(file);
6279
- } finally {
6280
- closeSync(file);
6281
- }
6282
- renameSync(tmp, target);
6283
- if (process.platform !== "win32") {
6284
- const directory = openSync(join(home, "teams", "effects"), "r");
6285
- try {
6286
- fsyncSync(directory);
6287
- } finally {
6288
- closeSync(directory);
6289
- }
6290
- }
6291
- }
6292
-
6293
6257
  const exec = promisify(execFile);
6294
6258
  const git = async (directory, args) => (await exec("git", ["-C", directory, ...args], { timeout: 3e4, maxBuffer: 2 * 1024 * 1024 })).stdout.trim();
6295
6259
  const gitRaw = async (directory, args) => (await exec("git", ["-C", directory, ...args], { timeout: 3e4, maxBuffer: 64 * 1024 * 1024 })).stdout;
@@ -6647,7 +6611,19 @@ Use the teams tools to inspect current task state before acting. This message is
6647
6611
  }, 5e3);
6648
6612
  timer.unref();
6649
6613
  void tick();
6650
- return { tick, report, hasSession: (id) => knownSessionIds.has(id), get busy() {
6614
+ async function recoverScope(operationId, sessionId, scopeFile) {
6615
+ const teams = (await http.get("/v1/teams")).data.teams ?? [];
6616
+ const team = teams.find((candidate) => candidate.archivedAt === void 0 && candidate.operations.some((op2) => op2.id === operationId));
6617
+ const op = team?.operations.find((candidate) => candidate.id === operationId);
6618
+ if (!team || !op || op.type !== "spawn" || op.machineId !== deps.machineId) throw new Error("No live team launch for this session");
6619
+ const bot = team.bots.find((candidate) => candidate.id === op.botId);
6620
+ if (!bot || bot.sessionId !== sessionId || bot.generation !== op.generation) throw new Error("Team binding changed; not restoring its scope");
6621
+ const claimed = await action(op.teamId, `claim-${op.id}`, { type: "claim-operation", operationId: op.id, machineId: deps.machineId, ...op.teamLaunchVersion === 1 ? { teamLaunchVersion: 1 } : {} });
6622
+ if (!claimed.credential?.token || claimed.credential.botId !== op.botId) throw new Error("Team did not reissue the member credential");
6623
+ ensurePrivateDirectorySync(join(home, "teams", "scopes"));
6624
+ writePrivateFileSync(scopeFile, JSON.stringify({ serverUrl: server, scopeToken: claimed.credential.token, teamId: op.teamId, botId: op.botId, taskId: op.taskId, attemptId: op.attemptId }));
6625
+ }
6626
+ return { tick, report, recoverScope, hasSession: (id) => knownSessionIds.has(id), get busy() {
6651
6627
  return running;
6652
6628
  }, stop: () => {
6653
6629
  closed = true;
@@ -8167,6 +8143,13 @@ async function startDaemon() {
8167
8143
  }
8168
8144
  };
8169
8145
  const resumeGates = /* @__PURE__ */ new Map();
8146
+ const teamRelaunchEnvFor = (happySessionId, metadata) => teamRelaunchEnv({
8147
+ home: configuration.happyHomeDir,
8148
+ sessionId: happySessionId,
8149
+ metadata,
8150
+ recover: teamWorker ? (operationId, scopeFile) => teamWorker.recoverScope(operationId, happySessionId, scopeFile) : void 0,
8151
+ log: (message) => logger.debug(`[DAEMON RUN] ${message}`)
8152
+ });
8170
8153
  const resumeSession = async (happySessionId, options) => {
8171
8154
  let gate = resumeGates.get(happySessionId);
8172
8155
  if (!gate) {
@@ -8249,9 +8232,11 @@ async function startDaemon() {
8249
8232
  const permissionMode = sanitizeSpawnPermissionMode(requestedMode);
8250
8233
  if (permissionMode) launch.args.push("--permission-mode", permissionMode);
8251
8234
  else if (requestedMode !== void 0) logger.debug(`[DAEMON RUN] resume: ignoring invalid permission mode ${JSON.stringify(requestedMode)}`);
8235
+ const teamEnv = await teamRelaunchEnvFor(happySessionId, metadata);
8252
8236
  const result = await spawnTrackedHappyProcess({
8253
8237
  args: launch.args,
8254
8238
  cwd: launch.cwd,
8239
+ ...teamEnv.VH_TEAM_SCOPE_FILE ? { spawnedBy: "teams" } : {},
8255
8240
  env: {
8256
8241
  ...process.env,
8257
8242
  // B-297: the credentialStore=file shim must ride every spawn path,
@@ -8267,7 +8252,8 @@ async function startDaemon() {
8267
8252
  HAPPY_RECONNECT_ENCRYPTION_VARIANT: tracked.encryption.encryptionVariant,
8268
8253
  HAPPY_RECONNECT_SEQ: String(tracked.encryption.seq),
8269
8254
  HAPPY_RECONNECT_METADATA_VERSION: String(tracked.encryption.metadataVersion),
8270
- HAPPY_RECONNECT_AGENT_STATE_VERSION: String(tracked.encryption.agentStateVersion)
8255
+ HAPPY_RECONNECT_AGENT_STATE_VERSION: String(tracked.encryption.agentStateVersion),
8256
+ ...teamEnv
8271
8257
  }
8272
8258
  });
8273
8259
  if (result.type === "success") {
@@ -8385,9 +8371,11 @@ async function startDaemon() {
8385
8371
  const permissionMode = sanitizeSpawnPermissionMode(options?.permissionMode);
8386
8372
  if (permissionMode) args.push("--permission-mode", permissionMode);
8387
8373
  restartCounts.set(happySessionId, recordRestartAttempt(prior));
8374
+ const teamEnv = await teamRelaunchEnvFor(happySessionId, metadata);
8388
8375
  const result = await spawnTrackedHappyProcess({
8389
8376
  args,
8390
8377
  cwd,
8378
+ ...teamEnv.VH_TEAM_SCOPE_FILE ? { spawnedBy: "teams" } : {},
8391
8379
  env: {
8392
8380
  ...process.env,
8393
8381
  // B-297: the credentialStore=file shim must ride every spawn path,
@@ -8402,7 +8390,8 @@ async function startDaemon() {
8402
8390
  HAPPY_RECONNECT_ENCRYPTION_VARIANT: tracked.encryption.encryptionVariant,
8403
8391
  HAPPY_RECONNECT_SEQ: String(tracked.encryption.seq),
8404
8392
  HAPPY_RECONNECT_METADATA_VERSION: String(tracked.encryption.metadataVersion),
8405
- HAPPY_RECONNECT_AGENT_STATE_VERSION: String(tracked.encryption.agentStateVersion)
8393
+ HAPPY_RECONNECT_AGENT_STATE_VERSION: String(tracked.encryption.agentStateVersion),
8394
+ ...teamEnv
8406
8395
  }
8407
8396
  });
8408
8397
  if (result.type === "success") {
@@ -8705,6 +8694,8 @@ async function startDaemon() {
8705
8694
  })).then(() => logger.debug(`[DAEMON RUN] claimed the machine record for host ${initialMachineMetadata.host}`)).catch((error) => logger.debug("[DAEMON RUN] could not refresh the recorded host:", error));
8706
8695
  }
8707
8696
  let lastUpdateLog = "";
8697
+ let harnessInstaller = null;
8698
+ let handoverInFlight = false;
8708
8699
  const updateController = createUpdateController({
8709
8700
  policy: () => fetchCliUpdateState(configuration.serverUrl, packageJson.version),
8710
8701
  enabled: async () => ((await readSettings()).cliAutoUpdate ?? "idle") !== "off",
@@ -8712,8 +8703,9 @@ async function startDaemon() {
8712
8703
  // handover (they talk to the server themselves) and web terminals live
8713
8704
  // in tmux, so neither is a reason to hold an update. B-507: a session
8714
8705
  // whose turn ended but still has background tasks is busy too.
8715
- idle: () => !turnActivity.hasActiveTurn() && !backgroundTasks.hasAny(),
8706
+ idle: () => !turnActivity.hasActiveTurn() && !backgroundTasks.hasAny() && !harnessInstaller?.isRunning(),
8716
8707
  install: installCliSafely,
8708
+ lastInstall: lastInstallStore(configuration.happyHomeDir),
8717
8709
  publish: (state) => {
8718
8710
  cliUpdateStateRef = state;
8719
8711
  fileState = { ...fileState, cliUpdate: state };
@@ -8760,10 +8752,33 @@ async function startDaemon() {
8760
8752
  logger.warn(`[DAEMON RUN] Ignoring invalid HAPPY_CLI_UPDATE_CHECK_INTERVAL; using ${DEFAULT_CLI_UPDATE_CHECK_INTERVAL_MS}ms`);
8761
8753
  }
8762
8754
  const cliUpdateInterval = setInterval(() => void refreshCliUpdate(), cliUpdateIntervalMs);
8755
+ const policyAnnouncements = createPolicyAnnouncementHandler({
8756
+ refresh: refreshCliUpdate,
8757
+ currentTarget: updateController.currentTarget
8758
+ });
8759
+ apiMachine.setCliUpdatePolicyHandler((payload) => {
8760
+ const delay = policyAnnouncements.onAnnouncement(payload);
8761
+ if (delay !== null) logger.warn(`[DAEMON RUN] CLI update policy announced; re-checking in ${Math.round(delay / 1e3)}s`);
8762
+ });
8763
8763
  const stopAgentVersionChecks = startAgentVersionChecks(
8764
8764
  (snapshot) => apiMachine.updateDaemonState((state) => ({ ...state, status: state?.status ?? "running", agentVersions: snapshot })),
8765
8765
  (error) => logger.debug("[DAEMON RUN] Agent version check failed", error)
8766
8766
  );
8767
+ harnessInstaller = createHarnessInstaller({
8768
+ npm: runNpm,
8769
+ runningPackageDir: projectPath(),
8770
+ commandOnPath,
8771
+ cliUpdateBusy: () => updateController.isRunning() || handoverInFlight,
8772
+ publish: (state) => {
8773
+ void apiMachine.updateDaemonState((current) => ({ ...current, status: current?.status ?? "running", harnessInstall: state })).catch((error) => logger.debug("[DAEMON RUN] harness install state publish failed", error));
8774
+ },
8775
+ afterInstall: async () => {
8776
+ apiMachine.recheckCliAvailability();
8777
+ await stopAgentVersionChecks.refresh();
8778
+ },
8779
+ log: (message) => logger.warn(`[DAEMON RUN] ${message}`)
8780
+ });
8781
+ apiMachine.setHarnessInstallHandler((params) => harnessInstaller.request(params));
8767
8782
  const heartbeatIntervalMs = parseInt(process.env.HAPPY_DAEMON_HEARTBEAT_INTERVAL || "60000");
8768
8783
  const restartOnStaleVersionAndHeartbeat = setInterval(serialTask(async () => {
8769
8784
  try {
@@ -8780,53 +8795,59 @@ async function startDaemon() {
8780
8795
  }
8781
8796
  await updateController.tick();
8782
8797
  await updateController.withHandover(async () => {
8783
- let bundleReplaced = false;
8784
- if (initialBundleMtimeMs > 0) {
8785
- try {
8786
- const currentMtimeMs = statSync$1(bundlePath).mtimeMs;
8787
- bundleReplaced = currentMtimeMs !== initialBundleMtimeMs;
8788
- } catch {
8789
- }
8790
- }
8791
- const relayMove = pendingRelayMove;
8792
- if ((bundleReplaced || relayMove) && !teamWorker?.busy && !automationRunner?.busy && !updateController.isRunning() && !turnActivity.hasActiveTurn() && !backgroundTasks.hasAny()) {
8793
- const preflight = bundleReplaced ? await preflightNewBundle(bundlePath) : { action: "proceed" };
8794
- if (preflight.action === "hold") {
8795
- if (lastHandoverHold !== preflight.reason) {
8796
- lastHandoverHold = preflight.reason;
8797
- logger.warn(`[DAEMON RUN] holding handover: ${preflight.reason}`);
8798
+ handoverInFlight = true;
8799
+ try {
8800
+ let bundleReplaced = false;
8801
+ if (initialBundleMtimeMs > 0) {
8802
+ try {
8803
+ const currentMtimeMs = statSync$1(bundlePath).mtimeMs;
8804
+ bundleReplaced = currentMtimeMs !== initialBundleMtimeMs;
8805
+ } catch {
8798
8806
  }
8799
- updateController.holdHandover(preflight.reason);
8800
- return;
8801
- }
8802
- if (teamWorker?.busy || automationRunner?.busy || updateController.isRunning() || turnActivity.hasActiveTurn() || backgroundTasks.hasAny()) return;
8803
- lastHandoverHold = null;
8804
- logger.debug(relayMove ? `[DAEMON RUN] Relay moved to ${relayMove.serverUrl}; handing off to a new daemon on the new settings` : "[DAEMON RUN] Daemon bundle replaced on disk and verified, handing off to new daemon");
8805
- teamWorker?.stop();
8806
- automationRunner?.stop();
8807
- projectPresence?.stop();
8808
- clearInterval(restartOnStaleVersionAndHeartbeat);
8809
- clearInterval(cliUpdateInterval);
8810
- stopAgentVersionChecks();
8811
- claudeAuthService.stop();
8812
- apiMachine.shutdown();
8813
- await stopControlServer();
8814
- await cleanupDaemonState();
8815
- await releaseDaemonLock(daemonLockHandle);
8816
- await stopCaffeinate();
8817
- if (daemonSupervisor === "systemd") {
8818
- logger.debug(`[DAEMON RUN] systemd-supervised: exiting ${HANDOVER_EXIT_CODE} for the unit to restart on the new bundle`);
8819
- process.exit(HANDOVER_EXIT_CODE);
8820
8807
  }
8821
- try {
8822
- spawnHappyCLI(["daemon", "start"], {
8823
- detached: true,
8824
- stdio: "ignore"
8825
- });
8826
- } catch (error) {
8827
- logger.debug("[DAEMON RUN] Failed to spawn new daemon, this is quite likely to happen during integration tests as we are cleaning out dist/ directory", error);
8808
+ const relayMove = pendingRelayMove;
8809
+ if ((bundleReplaced || relayMove) && !teamWorker?.busy && !automationRunner?.busy && !updateController.isRunning() && !harnessInstaller?.isRunning() && !turnActivity.hasActiveTurn() && !backgroundTasks.hasAny()) {
8810
+ const preflight = bundleReplaced ? await preflightNewBundle(bundlePath) : { action: "proceed" };
8811
+ if (preflight.action === "hold") {
8812
+ if (lastHandoverHold !== preflight.reason) {
8813
+ lastHandoverHold = preflight.reason;
8814
+ logger.warn(`[DAEMON RUN] holding handover: ${preflight.reason}`);
8815
+ }
8816
+ updateController.holdHandover(preflight.reason);
8817
+ return;
8818
+ }
8819
+ if (teamWorker?.busy || automationRunner?.busy || updateController.isRunning() || harnessInstaller?.isRunning() || turnActivity.hasActiveTurn() || backgroundTasks.hasAny()) return;
8820
+ lastHandoverHold = null;
8821
+ logger.debug(relayMove ? `[DAEMON RUN] Relay moved to ${relayMove.serverUrl}; handing off to a new daemon on the new settings` : "[DAEMON RUN] Daemon bundle replaced on disk and verified, handing off to new daemon");
8822
+ teamWorker?.stop();
8823
+ automationRunner?.stop();
8824
+ projectPresence?.stop();
8825
+ clearInterval(restartOnStaleVersionAndHeartbeat);
8826
+ clearInterval(cliUpdateInterval);
8827
+ policyAnnouncements.stop();
8828
+ stopAgentVersionChecks();
8829
+ claudeAuthService.stop();
8830
+ apiMachine.shutdown();
8831
+ await stopControlServer();
8832
+ await cleanupDaemonState();
8833
+ await releaseDaemonLock(daemonLockHandle);
8834
+ await stopCaffeinate();
8835
+ if (daemonSupervisor === "systemd") {
8836
+ logger.debug(`[DAEMON RUN] systemd-supervised: exiting ${HANDOVER_EXIT_CODE} for the unit to restart on the new bundle`);
8837
+ process.exit(HANDOVER_EXIT_CODE);
8838
+ }
8839
+ try {
8840
+ spawnHappyCLI(["daemon", "start"], {
8841
+ detached: true,
8842
+ stdio: "ignore"
8843
+ });
8844
+ } catch (error) {
8845
+ logger.debug("[DAEMON RUN] Failed to spawn new daemon, this is quite likely to happen during integration tests as we are cleaning out dist/ directory", error);
8846
+ }
8847
+ process.exit(0);
8828
8848
  }
8829
- process.exit(0);
8849
+ } finally {
8850
+ handoverInFlight = false;
8830
8851
  }
8831
8852
  });
8832
8853
  const daemonState = await readDaemonState();
@@ -8893,7 +8914,8 @@ async function startDaemon() {
8893
8914
  log: (message) => logger.debug(`[DAEMON RUN] ${message}`)
8894
8915
  });
8895
8916
  void apiMachine.updateMachineMetadata((current) => ({
8896
- ...current ?? initialMachineMetadata,
8917
+ // B-718: the running CLI version, not the one that first registered the row.
8918
+ ...withRunningCliFacts(current, initialMachineMetadata),
8897
8919
  teamsVersion: 1,
8898
8920
  teamLaunchVersion: 1,
8899
8921
  // B-689: this daemon acts on the owner's retry-operation (web gates「重试启动」).
@@ -8901,7 +8923,11 @@ async function startDaemon() {
8901
8923
  // B-586: this daemon serves the worktree-* RPCs (web gates 「在新 worktree 中」 on it).
8902
8924
  worktreeVersion: WORKTREE_CAPABILITY_VERSION,
8903
8925
  // B-688: this daemon serves the model-config-* RPCs (web gates 「模型与 Provider」 on it).
8904
- modelConfigVersion: MODEL_CONFIG_CAPABILITY_VERSION
8926
+ modelConfigVersion: MODEL_CONFIG_CAPABILITY_VERSION,
8927
+ // B-719: this daemon serves `harness-install` (web gates the Install / Update buttons on it).
8928
+ harnessInstallVersion: HARNESS_INSTALL_CAPABILITY_VERSION,
8929
+ // B-755: this daemon serves `fs-mkdir` (web gates 「新建工作目录」 on it).
8930
+ fsMkdirVersion: FS_MKDIR_CAPABILITY_VERSION
8905
8931
  })).catch(() => logger.debug("[DAEMON RUN] Teams capability publication will retry on reconnect"));
8906
8932
  const cleanupAndShutdown = async (source, errorMessage) => {
8907
8933
  webPanelsSync.stop();
@@ -8916,6 +8942,7 @@ async function startDaemon() {
8916
8942
  logger.debug("[DAEMON RUN] Health check interval cleared");
8917
8943
  }
8918
8944
  clearInterval(cliUpdateInterval);
8945
+ policyAnnouncements.stop();
8919
8946
  stopAgentVersionChecks();
8920
8947
  await apiMachine.updateDaemonState((state) => ({
8921
8948
  ...state,