very-happy-cli 0.2.180 → 0.2.181

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 (200) hide show
  1. package/dist/{AcpBackend-DfRDtMcG.cjs → AcpBackend-CgHb_ya5.cjs} +3 -3
  2. package/dist/{AcpBackend-B8Zi30Zh.mjs → AcpBackend-DZGCSuXF.mjs} +3 -3
  3. package/dist/{AcpSessionManager-CASKOnOj.cjs → AcpSessionManager-CacC2EVn.cjs} +1 -1
  4. package/dist/{AcpSessionManager-i8Rab0xT.mjs → AcpSessionManager-DwuPzWBh.mjs} +1 -1
  5. package/dist/{BasePermissionHandler-BlM_tfej.cjs → BasePermissionHandler-DCP8ZEB4.cjs} +1 -1
  6. package/dist/{BasePermissionHandler-26fKbRCm.mjs → BasePermissionHandler-Uo-GUjbb.mjs} +1 -1
  7. package/dist/{acpSessionResume-B9l54P-X.mjs → acpSessionResume-9OLTD6kT.mjs} +1 -1
  8. package/dist/{acpSessionResume-CR5pmgVo.cjs → acpSessionResume-BaVMD80f.cjs} +1 -1
  9. package/dist/{agentHome-CmGy0NWv.mjs → agentHome-B1BsKeqD.mjs} +4 -4
  10. package/dist/{agentHome-C7Sb7-Tz.cjs → agentHome-BtUmGcPX.cjs} +4 -4
  11. package/dist/{api-DM7b1OKj.cjs → api-COHFAkb_.cjs} +10 -10
  12. package/dist/{api-Hov8p7CA.mjs → api-Dr3wXL_7.mjs} +10 -10
  13. package/dist/{apiMachine-CwGEhQDT.mjs → apiMachine-CHEzPQdS.mjs} +54 -32
  14. package/dist/{apiMachine-DkGRj-Ai.cjs → apiMachine-D8H7CXaz.cjs} +55 -33
  15. package/dist/{attachmentContent-CIn4iPEk.mjs → attachmentContent-1YwtKgWl.mjs} +2 -2
  16. package/dist/{attachmentContent-CEm13_gF.cjs → attachmentContent-mIhbtRKl.cjs} +2 -2
  17. package/dist/{auth-rggxMpEE.mjs → auth-CbZkcMAb.mjs} +8 -8
  18. package/dist/{auth-CBXnm-Mj.cjs → auth-CgQP7d0q.cjs} +5 -5
  19. package/dist/{auth-BG1DY0gj.mjs → auth-DZsthtSk.mjs} +5 -5
  20. package/dist/{auth-Dawj7pP5.cjs → auth-Fg8fLadK.cjs} +8 -8
  21. package/dist/{auto-CbYOfoPS.cjs → auto-BB7kqSh5.cjs} +18 -18
  22. package/dist/{auto-CXXLXGeL.mjs → auto-_T-Ud-z5.mjs} +18 -18
  23. package/dist/{bootstrap-Dt5I1SOl.mjs → bootstrap-B6EkZh4u.mjs} +2 -2
  24. package/dist/{bootstrap-DoA7okld.cjs → bootstrap-CYtOtbi7.cjs} +2 -2
  25. package/dist/{browser-DUNetFT4.mjs → browser-C2qhUHSE.mjs} +1 -1
  26. package/dist/{browser-DZ6qRtfH.cjs → browser-DjNNQQk2.cjs} +1 -1
  27. package/dist/{claudeLocal-OHWQigCl.mjs → claudeLocal-BAFjWhZ8.mjs} +6 -6
  28. package/dist/{claudeLocal--DNyMOSv.cjs → claudeLocal-O_w7-fBh.cjs} +6 -6
  29. package/dist/{claudeLocalLauncher-BStvfa7M.mjs → claudeLocalLauncher-CWn30LZR.mjs} +11 -11
  30. package/dist/{claudeLocalLauncher-BhvPbcyH.cjs → claudeLocalLauncher-Df3zg1q0.cjs} +11 -11
  31. package/dist/{claudeSessionFork-GAVWhiKG.mjs → claudeSessionFork-2ZkSTOGn.mjs} +1 -1
  32. package/dist/{claudeSessionFork-Be16vzGc.cjs → claudeSessionFork-pwcKqB-e.cjs} +1 -1
  33. package/dist/{client-Ca5Pcgy_.mjs → client-BKyU66lg.mjs} +2 -2
  34. package/dist/{client-D_loZ9IK.cjs → client-CUO3ItoB.cjs} +34 -8
  35. package/dist/{client-CnB7kRCT.mjs → client-CXOnzuOy.mjs} +2 -2
  36. package/dist/{client-DJplzPjD.cjs → client-DWs6k041.cjs} +2 -2
  37. package/dist/{client-BWjaHKtj.cjs → client-D_U2VjO3.cjs} +2 -2
  38. package/dist/{client-D4uPb1uu.mjs → client-DsQcxVUV.mjs} +34 -9
  39. package/dist/codex/happyMcpStdioBridge.cjs +17 -17
  40. package/dist/codex/happyMcpStdioBridge.mjs +17 -17
  41. package/dist/{codexAppServerClient-PE-12AsW.cjs → codexAppServerClient-BRX3JZ_5.cjs} +3 -3
  42. package/dist/{codexAppServerClient-CO4G6OS4.mjs → codexAppServerClient-C3JO3pwM.mjs} +3 -3
  43. package/dist/{codexCommand-B_xUH79E.mjs → codexCommand-BZIAAqKS.mjs} +47 -45
  44. package/dist/{codexCommand-BNoD1t1C.cjs → codexCommand-dKOc5iQl.cjs} +47 -45
  45. package/dist/{codexTermination-BmBbizFK.cjs → codexTermination-BVR9HeVI.cjs} +1 -1
  46. package/dist/{codexTermination-Dx48ma6e.mjs → codexTermination-ztFkwHrG.mjs} +1 -1
  47. package/dist/{config-DSu0Blos.mjs → config-NbHjx8ge.mjs} +3 -3
  48. package/dist/{config-CtwEUxUP.cjs → config-cftYxLC7.cjs} +3 -3
  49. package/dist/{configuration-PxzEJumr.mjs → configuration-BPrfb17M.mjs} +1 -1
  50. package/dist/{configuration-CYO-kPOS.cjs → configuration-C8JgseHy.cjs} +1 -1
  51. package/dist/{connect-Dq7wndzR.cjs → connect-Br8VMrK1.cjs} +13 -13
  52. package/dist/{connect-BQrg3TB6.mjs → connect-CLD1ai0k.mjs} +13 -13
  53. package/dist/{context-6TP-iz_c.cjs → context-D1lgQTJC.cjs} +1 -1
  54. package/dist/{context-CJGRRGWW.mjs → context-_YGq8xdH.mjs} +1 -1
  55. package/dist/{controlClient-DoeYovwP.mjs → controlClient-CkNpT5zS.mjs} +3 -3
  56. package/dist/{controlClient-Bqv5v5OM.cjs → controlClient-D68Zj7Q-.cjs} +3 -3
  57. package/dist/{createSessionMetadata-DDIChCx8.mjs → createSessionMetadata-3WAj3_xl.mjs} +2 -2
  58. package/dist/{createSessionMetadata-xz767pcG.cjs → createSessionMetadata-DNIbKLHg.cjs} +2 -2
  59. package/dist/{defaultPermissionMode-PkZGsQDk.cjs → defaultPermissionMode-BmvjP32v.cjs} +2 -2
  60. package/dist/{defaultPermissionMode-i5KzdK09.mjs → defaultPermissionMode-DXMznl8v.mjs} +2 -2
  61. package/dist/{deterministicJson-xqA6DaOm.cjs → deterministicJson-BS7D7URE.cjs} +87 -22
  62. package/dist/{deterministicJson-CTEJDqtS.mjs → deterministicJson-BcBIKKf1.mjs} +87 -24
  63. package/dist/{doctor-BrgSlfXn.cjs → doctor-Be7zC0AF.cjs} +8 -7
  64. package/dist/{doctor-IctjVh9T.mjs → doctor-CoOaPq6w.mjs} +8 -8
  65. package/dist/{downloadTarget-jbOz1egC.mjs → downloadTarget-CeaAXdJj.mjs} +6 -6
  66. package/dist/{downloadTarget-CEb9che-.cjs → downloadTarget-LyOiucn_.cjs} +6 -6
  67. package/dist/{ensureDaemonRunning-b1e8LCSM.mjs → ensureDaemonRunning--98j7-17.mjs} +5 -5
  68. package/dist/{ensureDaemonRunning-D-HyjGIX.cjs → ensureDaemonRunning-B8z37VKD.cjs} +5 -5
  69. package/dist/fileStore-5qpetZS0.mjs +146 -0
  70. package/dist/fileStore-7qhnbs6N.cjs +152 -0
  71. package/dist/{handleResumeCommand-BvWVuRwH.cjs → handleResumeCommand-BFZlrFVH.cjs} +5 -5
  72. package/dist/{handleResumeCommand-DpaZvm2c.mjs → handleResumeCommand-gO8I4keP.mjs} +5 -5
  73. package/dist/{index-B2p2Mut7.cjs → index-B5PXbZjW.cjs} +77 -60
  74. package/dist/{index-F2UejqYs.cjs → index-BRMq9gOe.cjs} +3 -3
  75. package/dist/{index-C64Ufj4D.cjs → index-BkluinoS.cjs} +4 -4
  76. package/dist/{index-Bsvbgh9e.mjs → index-CO0n9ue6.mjs} +4 -4
  77. package/dist/{index-C6Vn5Eac.mjs → index-CeRXrGnH.mjs} +39 -6
  78. package/dist/{index-Dv0Q48P3.cjs → index-Df2eHm3O.cjs} +40 -6
  79. package/dist/{index-BUNnpGFF.mjs → index-DneHoQXf.mjs} +78 -61
  80. package/dist/{index-CMf5VbzG.mjs → index-WUj3954m.mjs} +4 -4
  81. package/dist/{index-wCbtF4BK.mjs → index-hQ8LP1C2.mjs} +3 -3
  82. package/dist/{index-DmTlDRSL.cjs → index-jF2t3ACW.cjs} +4 -4
  83. package/dist/index.cjs +2 -2
  84. package/dist/index.mjs +2 -2
  85. package/dist/{install-BX3lv3NO.mjs → install-D0inLCv-.mjs} +1 -1
  86. package/dist/{install-BXvf2E3o.cjs → install-D2NCbeJ-.cjs} +1 -1
  87. package/dist/{install-CdzSV7qO.mjs → install-DkYFiVK4.mjs} +3 -3
  88. package/dist/{install-C0ANcEAW.cjs → install-FqkSUjPC.cjs} +3 -3
  89. package/dist/lib.cjs +12 -12
  90. package/dist/lib.d.cts +39 -1
  91. package/dist/lib.d.mts +39 -1
  92. package/dist/lib.mjs +12 -12
  93. package/dist/{lidState-bBrmaQd2.mjs → lidState-DghbfRbr.mjs} +1 -1
  94. package/dist/{lidState-DWGnm2H4.cjs → lidState-x8cJ8J_9.cjs} +1 -1
  95. package/dist/{localHappyAgentAuth-DZHQnIW9.cjs → localHappyAgentAuth-DZ2kb9uq.cjs} +1 -1
  96. package/dist/{localHappyAgentAuth-Dn1jYDpt.mjs → localHappyAgentAuth-IKmeRpuQ.mjs} +1 -1
  97. package/dist/{logger-CphW5nUE.cjs → logger-DgBwUSgS.cjs} +2 -2
  98. package/dist/{logger-BUngm5Xg.mjs → logger-LFr8iPgl.mjs} +2 -2
  99. package/dist/{machineMetadata-BXN_bKU3.mjs → machineMetadata-BglCsfvf.mjs} +11 -4
  100. package/dist/{machineMetadata-BQm9YTcI.cjs → machineMetadata-DB03ez4K.cjs} +11 -3
  101. package/dist/{main-B2UV51nK.cjs → main-YQ-Vkakw.cjs} +45 -45
  102. package/dist/{main-DGkdYfEq.mjs → main-_1LSzlLv.mjs} +45 -45
  103. package/dist/{mcp-BFvAEIRz.cjs → mcp-BIY80jSc.cjs} +14 -14
  104. package/dist/{mcp-CbTtbs0f.mjs → mcp-Csj-mT5Y.mjs} +14 -14
  105. package/dist/{mirrorProtocol-HKPivs5o.cjs → mirrorProtocol-BphKIAK9.cjs} +16 -153
  106. package/dist/{mirrorProtocol-BR4luBC0.mjs → mirrorProtocol-CAxOMBkb.mjs} +19 -151
  107. package/dist/{package-7Ld1YMhh.mjs → package-DYGwlmXl.mjs} +8 -8
  108. package/dist/{package-KhLqBDX9.cjs → package-NoD8QOOM.cjs} +8 -8
  109. package/dist/{panels-C7IVqpJk.mjs → panels-B8zRm7_d.mjs} +5 -5
  110. package/dist/{panels-Njf6GelF.cjs → panels-DYEc1NyQ.cjs} +5 -5
  111. package/dist/{peerTools-BOlDRyhi.mjs → peerTools-CK3JYu1k.mjs} +5 -5
  112. package/dist/{peerTools-DQNlrcqM.cjs → peerTools-CP-x-Peu.cjs} +5 -5
  113. package/dist/{persistence-9BMYspxG.cjs → persistence-BY-D00wT.cjs} +2 -2
  114. package/dist/{persistence-BFUOHW5h.mjs → persistence-D2Djrf7t.mjs} +2 -2
  115. package/dist/pi-CqyoZMjI.mjs +380 -0
  116. package/dist/pi-DlsrPCTa.cjs +400 -0
  117. package/dist/{piRunnerArgs-H2pgpA5z.mjs → piRunnerArgs-IZLoL7TG.mjs} +1 -1
  118. package/dist/{piRunnerArgs-DGaq6VYM.cjs → piRunnerArgs-gxLYIZH4.cjs} +1 -1
  119. package/dist/{piRuntime-CdN0JAsa.mjs → piRuntime-D7Y4oHVL.mjs} +2 -2
  120. package/dist/{piRuntime-CT5xv_3h.cjs → piRuntime-o5L8uX-W.cjs} +2 -2
  121. package/dist/{piTerminal-0FBtXTHK.cjs → piTerminal-C_-JDkLN.cjs} +17 -17
  122. package/dist/{piTerminal-C4m9YXAV.mjs → piTerminal-Kphx8Uhw.mjs} +17 -17
  123. package/dist/{prompt-lgF9IE82.cjs → prompt-Bof2aQkI.cjs} +7 -2
  124. package/dist/{prompt-BOouDaTM.mjs → prompt-QHuFx9j6.mjs} +7 -2
  125. package/dist/{pushNotifications-DI92havG.cjs → pushNotifications-BXoOSq_G.cjs} +10 -4
  126. package/dist/{pushNotifications-DCiBEVeC.mjs → pushNotifications-DXGIhE-A.mjs} +10 -4
  127. package/dist/{query-ywZbQUzR.mjs → query-CcA6DVZK.mjs} +1 -1
  128. package/dist/{query-BETKBrnz.cjs → query-CdCxzuaB.cjs} +1 -1
  129. package/dist/{rateLimitWindows-YRIYVdo4.cjs → rateLimitWindows-OpAmdOtC.cjs} +1 -1
  130. package/dist/{rateLimitWindows-BkfuUB1a.mjs → rateLimitWindows-ZtHZHe-l.mjs} +1 -1
  131. package/dist/{registerCommonHandlers-C7a_dUWs.cjs → registerCommonHandlers-CRjBoJLK.cjs} +2 -2
  132. package/dist/{registerCommonHandlers-CTkCvjxt.mjs → registerCommonHandlers-DF6bdDtd.mjs} +2 -2
  133. package/dist/{registerKillSessionHandler-CIsvBrVe.cjs → registerKillSessionHandler-7fOm6ooh.cjs} +6 -6
  134. package/dist/{registerKillSessionHandler-CaqqRYq6.mjs → registerKillSessionHandler-DDCKL3wf.mjs} +6 -6
  135. package/dist/{remoteSessionClient-PP-mOKmw.mjs → remoteSessionClient-B2POSKFT.mjs} +5 -5
  136. package/dist/{remoteSessionClient-D843fkzL.cjs → remoteSessionClient-CFCn42bs.cjs} +5 -5
  137. package/dist/{reportProgressTool-ZOCNjWHr.mjs → reportProgressTool-tPxEvh6x.mjs} +4 -4
  138. package/dist/{reportProgressTool-14AXr959.cjs → reportProgressTool-th4trcET.cjs} +4 -4
  139. package/dist/{resources-CvIlkzpP.cjs → resources-BPyN59Iv.cjs} +23 -2
  140. package/dist/{resources-y9jfyO-_.mjs → resources-CH1iP6Cv.mjs} +22 -3
  141. package/dist/{run-CAOCYYfT.cjs → run-C_FumCQk.cjs} +618 -594
  142. package/dist/{run-Dajw2E5j.mjs → run-cKQBbVi4.mjs} +554 -530
  143. package/dist/{runClaude-C6oxWYbp.mjs → runClaude-CYnM6krL.mjs} +22 -22
  144. package/dist/{runClaude-CRRKt-tm.cjs → runClaude-z_2YC_aa.cjs} +22 -22
  145. package/dist/{runClaudeDeps-B1ysqTvx.cjs → runClaudeDeps-CLubzWnA.cjs} +43 -41
  146. package/dist/{runClaudeDeps-BcZ68cX3.mjs → runClaudeDeps-duS06Pb4.mjs} +48 -46
  147. package/dist/{runGemini-D7yKc4wD.mjs → runGemini-C_Pc2EhH.mjs} +37 -35
  148. package/dist/{runGemini-DtKFXqf-.cjs → runGemini-vEoXV6qT.cjs} +37 -35
  149. package/dist/{runOpenClaw-Cq29mryb.cjs → runOpenClaw-CXJCtMgB.cjs} +18 -18
  150. package/dist/{runOpenClaw-C8ilyLP9.mjs → runOpenClaw-Cxun1qLC.mjs} +18 -18
  151. package/dist/{sandbox-gIj-EuF4.cjs → sandbox-CO46H2tK.cjs} +4 -4
  152. package/dist/{sandbox-CbCutma4.mjs → sandbox-CsvPnTmz.mjs} +4 -4
  153. package/dist/{selfCheck-BTGBVthv.cjs → selfCheck-DIr7Gc0i.cjs} +36 -36
  154. package/dist/selfCheck-NamtBHVB.mjs +85 -0
  155. package/dist/{send-YZRMQvMJ.mjs → send-DMoFg6F1.mjs} +8 -8
  156. package/dist/{send-CZf5lLw_.cjs → send-X2mfVEyr.cjs} +8 -8
  157. package/dist/{server-CknFq6xG.cjs → server-BwTEdbkr.cjs} +6 -6
  158. package/dist/{server-BqwXRzFZ.mjs → server-CkqQ0sRH.mjs} +4 -4
  159. package/dist/{sessionLock-DRZUcKHT.cjs → sessionLock-CdIRJlHH.cjs} +3 -3
  160. package/dist/{sessionLock-nBXdJxJm.mjs → sessionLock-JIfwCqMk.mjs} +3 -3
  161. package/dist/{sessionOps-BrGiU9-s.cjs → sessionOps-DVA-00nR.cjs} +4 -4
  162. package/dist/{sessionOps-C58iDeD5.mjs → sessionOps-DqlftXxe.mjs} +4 -4
  163. package/dist/{sessionScanner-Bm57LEXe.mjs → sessionScanner-BI0Vo1xT.mjs} +3 -3
  164. package/dist/{sessionScanner-CaO8bLaG.cjs → sessionScanner-BMwgGAhY.cjs} +3 -3
  165. package/dist/{sessions-BXyBHo8n.mjs → sessions-D6IimX7b.mjs} +10 -10
  166. package/dist/{sessions-uzqPCc37.cjs → sessions-DjgEcHaT.cjs} +10 -10
  167. package/dist/{setupOfflineReconnection-DvvVX3TB.cjs → setupOfflineReconnection-CjuCiKY3.cjs} +2 -2
  168. package/dist/{setupOfflineReconnection-1Qlek3wl.mjs → setupOfflineReconnection-DgJK_4m1.mjs} +2 -2
  169. package/dist/{spawn-vn93PhYL.mjs → spawn-Dvzy-tzc.mjs} +14 -14
  170. package/dist/{spawn-DBkodJEO.cjs → spawn-rhF28shY.cjs} +14 -14
  171. package/dist/{spawnAgents-CgPMWexP.mjs → spawnAgents-CCnlq-FI.mjs} +1 -1
  172. package/dist/{spawnAgents-DLNMlhWJ.cjs → spawnAgents-iwuqrgR9.cjs} +1 -1
  173. package/dist/{spawnHappyCLI-DnUZv0X2.cjs → spawnHappyCLI-DnKmIsEt.cjs} +1 -1
  174. package/dist/{spawnHappyCLI-BjFZc_7V.mjs → spawnHappyCLI-Dq3LLXLb.mjs} +1 -1
  175. package/dist/{specialCommands-D3ND9bdg.mjs → specialCommands-CcyZ1hGA.mjs} +3 -3
  176. package/dist/{specialCommands-BRufxlqe.cjs → specialCommands-Zwr2C6A2.cjs} +3 -3
  177. package/dist/{startFileWatcher-DXJ7yB5v.mjs → startFileWatcher-4thXDK9d.mjs} +1 -1
  178. package/dist/{startFileWatcher-D9cYzZOq.cjs → startFileWatcher-K6hJSyiR.cjs} +1 -1
  179. package/dist/{startupSessionCreate-uZ6BM0ty.mjs → startupSessionCreate-BfByJ16A.mjs} +1 -1
  180. package/dist/{startupSessionCreate-kdr3m1uX.cjs → startupSessionCreate-CKEbq0tL.cjs} +1 -1
  181. package/dist/{streamRelay-Dy6amlzx.cjs → streamRelay-Gy8Xfnon.cjs} +1 -1
  182. package/dist/{streamRelay-DnMCMMfV.mjs → streamRelay-LcDvyEjK.mjs} +1 -1
  183. package/dist/{sync-Bn-idAoy.cjs → sync-1hQ5KHpL.cjs} +1 -1
  184. package/dist/{sync-BvVhDO40.mjs → sync-DO-2PU45.mjs} +1 -1
  185. package/dist/{systemPrompt-CIZcJuJF.mjs → systemPrompt-BctLfMZs.mjs} +3 -3
  186. package/dist/{systemPrompt-DlyEk35T.cjs → systemPrompt-Dj7pgkMK.cjs} +3 -3
  187. package/dist/{teams-DLY3B5yi.mjs → teams-Bu8YeOoF.mjs} +8 -8
  188. package/dist/{teams-BLeRpDxv.cjs → teams-uQxUlyyz.cjs} +8 -8
  189. package/dist/{time-D3ETL7Jb.mjs → time-BcoJaG8E.mjs} +1 -1
  190. package/dist/{time-Cjv0zxQO.cjs → time-nqa9shu8.cjs} +1 -1
  191. package/dist/{titleGenerator-Cgp5ihFb.cjs → titleGenerator-D09XyYYG.cjs} +2 -2
  192. package/dist/{titleGenerator-Uzbd4utr.mjs → titleGenerator-D5r06bhf.mjs} +1 -1
  193. package/dist/{todo-pOMcEECE.cjs → todo-CCOnL7pT.cjs} +6 -6
  194. package/dist/{todo-C9Ga-oEM.mjs → todo-D1q8146q.mjs} +6 -6
  195. package/dist/{tools-Cho7iIWf.mjs → tools-D2E--n1x.mjs} +3 -3
  196. package/dist/{tools-BL7Byl_x.cjs → tools-DiCXnAC5.cjs} +3 -3
  197. package/dist/{uninstall-CbayEVoV.cjs → uninstall-BrNwwwSD.cjs} +3 -3
  198. package/dist/{uninstall-CvZHVY9C.mjs → uninstall-CtwXi1Rt.mjs} +3 -3
  199. package/package.json +8 -8
  200. package/dist/selfCheck-Smt-5Xiq.mjs +0 -85
@@ -1,24 +1,24 @@
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-CAxOMBkb.mjs';
2
+ import { P as PI_ADAPTER_INSTALL_HINT, s as sanitizeSpawnModel } from './piRunnerArgs-IZLoL7TG.mjs';
3
3
  import fs from 'fs/promises';
4
4
  import os from 'os';
5
5
  import * as tmp from 'tmp';
6
6
  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';
7
+ 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
8
  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';
9
+ import { f as ensurePrivateDirectorySync, w as writePrivateFileSync, c as configuration } from './configuration-BPrfb17M.mjs';
10
+ import { T as TurnActivityTracker, A as ApiClient } from './api-Dr3wXL_7.mjs';
11
+ import { S as SPAWN_AGENTS, i as isSpawnAgent, s as sanitizeSpawnPermissionMode, r as resolveSpawnPermissionMode, a as resolveFirstMessageMeta, j as joinProject } from './spawnAgents-CCnlq-FI.mjs';
12
+ import { l as logger } from './logger-LFr8iPgl.mjs';
13
+ import { p as pruneStaleCompileCaches } from './index-hQ8LP1C2.mjs';
14
+ import { a as authAndSetupMachineIfNeeded } from './auth-DZsthtSk.mjs';
15
15
  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';
16
+ import { p as packageJson } from './package-DYGwlmXl.mjs';
17
+ import { a as globalPrefixForPackage, N as NPM_SAFE_ARG, g as getEnvironmentInfo } from './doctor-CoOaPq6w.mjs';
18
+ import { s as spawnHappyCLI } from './spawnHappyCLI-Dq3LLXLb.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-D2Djrf7t.mjs';
20
+ import { i as isDaemonRunningCurrentlyInstalledHappyVersion, s as stopDaemon, t as cleanupDaemonState } from './controlClient-CkNpT5zS.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-DqlftXxe.mjs';
22
22
  import fastify from 'fastify';
23
23
  import { z } from 'zod';
24
24
  import { validatorCompiler, serializerCompiler } from 'fastify-type-provider-zod';
@@ -26,60 +26,62 @@ import { d as decodeBase64, s as sameRelay, e as encodeBase64, h as decrypt } fr
26
26
  import { randomBytes, createHash, timingSafeEqual, randomUUID } from 'node:crypto';
27
27
  import { c as checkPreviewPath } from './previewPath-CXN4Vag0.mjs';
28
28
  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';
29
+ import { b as sanitizeRateLimitReport } from './rateLimitWindows-ZtHZHe-l.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-CK3JYu1k.mjs';
31
31
  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';
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-B2POSKFT.mjs';
33
+ import { assistantHome, bootstrapAssistantHome } from './bootstrap-B6EkZh4u.mjs';
34
+ import { refreshAgentHomes, locateClaudeConversation, agentHomeSpawnEnv } from './index-CO0n9ue6.mjs';
35
35
  import require$$0, { existsSync as existsSync$1, statSync as statSync$1, mkdtempSync, rmSync } from 'fs';
36
36
  import require$$1, { join as join$1 } from 'path';
37
37
  import { spawn as spawn$1, execFile, spawnSync } from 'node:child_process';
38
38
  import { p as projectPath } from './projectPath-BTBIMB57.mjs';
39
39
  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';
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, w as withRunningCliFacts } from './machineMetadata-BglCsfvf.mjs';
41
+ import { M as ModelConfigError, B as BackupStore, r as readConfigFile, s as sha256, a as atomicWriteFile } from './fileStore-5qpetZS0.mjs';
42
+ 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
43
  import require$$3 from 'crypto';
42
44
  import { c as commandOnPath } from './detectCLI-CSGMlV51.mjs';
43
- import { buildResumeLaunch } from './handleResumeCommand-DpaZvm2c.mjs';
45
+ import { buildResumeLaunch } from './handleResumeCommand-gO8I4keP.mjs';
44
46
  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';
47
+ import { v as ServerMigrateNoticeSchema, w as isHarnessInstallId, 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, H as HARNESS_INSTALL_CAPABILITY_VERSION } from './index-CeRXrGnH.mjs';
46
48
  import { access, symlink, readFile, rm, unlink, readlink, lstat, constants, stat, open } from 'node:fs/promises';
47
49
  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';
50
+ import { P as ProjectsUnsupportedError, c as createProjectsClient } from './client-BKyU66lg.mjs';
51
+ import { c as createAutomationsClient, i as isAutomationsUnavailable, d as AutomationsRequestError } from './client-CXOnzuOy.mjs';
50
52
  import { createRequire } from 'node:module';
51
53
  import { g as getProjectPath } from './path-D2wdaNSk.mjs';
52
54
  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';
55
+ import { s as startFileWatcher } from './startFileWatcher-4thXDK9d.mjs';
56
+ import { I as InvalidateSync } from './sync-DO-2PU45.mjs';
55
57
  import { R as RawJSONLinesSchema } from './types-CkqAACXx.mjs';
56
58
  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';
59
+ import { p as processIdentityFields, r as readSessionLock, t as terminateProcess, d as terminateProcessTree } from './sessionLock-JIfwCqMk.mjs';
58
60
  import { findAllHappyProcesses } from './doctor-BPkYWSuq.mjs';
59
- import { T as TEAM_SKILL } from './resources-y9jfyO-_.mjs';
61
+ import { T as TEAM_SKILL, a as TEAM_LEAD_PLAYBOOK, b as TEAM_MEMBER_RULES } from './resources-CH1iP6Cv.mjs';
60
62
  import { promisify } from 'node:util';
61
63
  import { c as clipText, r as renderAction, b as renderTemplate } from './template-CiOPa2rd.mjs';
62
64
  import { d as detectDaemonSupervisor } from './daemonSupervisor-C6urv3_8.mjs';
63
65
  import { d as describeAgentHome } from './loginShellEnv-Dw8M7tkI.mjs';
64
- import './acpSessionResume-B9l54P-X.mjs';
65
- import './lidState-bBrmaQd2.mjs';
66
+ import './acpSessionResume-9OLTD6kT.mjs';
67
+ import './lidState-DghbfRbr.mjs';
66
68
  import 'node:events';
67
69
  import 'socket.io-client';
68
- import './pushNotifications-DCiBEVeC.mjs';
70
+ import './pushNotifications-DXGIhE-A.mjs';
69
71
  import './contentLogMetadata-BlWI0egu.mjs';
70
72
  import './limits-DIo7rEBR.mjs';
71
- import './time-D3ETL7Jb.mjs';
72
- import './registerCommonHandlers-CTkCvjxt.mjs';
73
+ import './time-BcoJaG8E.mjs';
74
+ import './registerCommonHandlers-DF6bdDtd.mjs';
73
75
  import 'util';
74
76
  import 'cross-spawn';
75
77
  import '@paralleldrive/cuid2';
76
78
  import 'chalk';
77
79
  import 'tweetnacl';
78
- import './browser-DUNetFT4.mjs';
80
+ import './browser-C2qhUHSE.mjs';
79
81
  import 'open';
80
82
  import './ids-DU6sLx4C.mjs';
81
83
  import 'url';
82
- import './localHappyAgentAuth-Dn1jYDpt.mjs';
84
+ import './localHappyAgentAuth-IKmeRpuQ.mjs';
83
85
  import './processTreeSnapshot-CuLJDNC-.mjs';
84
86
  import 'ps-list';
85
87
 
@@ -92,6 +94,87 @@ async function resolveRelaunchMetadata(snapshot, fetchServer) {
92
94
  return { metadata: snapshot, source: "snapshot" };
93
95
  }
94
96
 
97
+ function receiptPath(home, operationId) {
98
+ if (!/^[a-zA-Z0-9_-]{1,128}$/.test(operationId)) throw new Error("Invalid team operation id");
99
+ return join(home, "teams", "effects", `${operationId}.json`);
100
+ }
101
+ function readReceipt(home, id) {
102
+ try {
103
+ const value = JSON.parse(readFileSync(receiptPath(home, id), "utf8"));
104
+ if (value.operationId !== id || typeof value.claimId !== "string" || !["prepared", "spawning", "spawned", "delivered", "completed"].includes(value.phase)) throw new Error("Invalid team effect receipt");
105
+ return value;
106
+ } catch (error) {
107
+ if (error.code === "ENOENT") return null;
108
+ throw error;
109
+ }
110
+ }
111
+ function writeReceipt$1(home, receipt) {
112
+ ensurePrivateDirectorySync(join(home, "teams", "effects"));
113
+ const target = receiptPath(home, receipt.operationId);
114
+ const tmp = `${target}.${process.pid}.tmp`;
115
+ writePrivateFileSync(tmp, JSON.stringify(receipt));
116
+ const file = openSync(tmp, "r");
117
+ try {
118
+ fsyncSync(file);
119
+ } finally {
120
+ closeSync(file);
121
+ }
122
+ renameSync(tmp, target);
123
+ if (process.platform !== "win32") {
124
+ const directory = openSync(join(home, "teams", "effects"), "r");
125
+ try {
126
+ fsyncSync(directory);
127
+ } finally {
128
+ closeSync(directory);
129
+ }
130
+ }
131
+ }
132
+
133
+ const OPERATION_ID = /^[a-zA-Z0-9_-]{1,128}$/;
134
+ function teamScopeFilePath(home, operationId) {
135
+ if (!OPERATION_ID.test(operationId)) throw new Error("Invalid team operation id");
136
+ return join(home, "teams", "scopes", `${operationId}.json`);
137
+ }
138
+ function relaunchTeamOperationId(home, sessionId, metadata) {
139
+ const fromMetadata = metadata?.teamOperationId;
140
+ if (typeof fromMetadata === "string" && OPERATION_ID.test(fromMetadata)) return fromMetadata;
141
+ let names;
142
+ try {
143
+ names = readdirSync(join(home, "teams", "effects"));
144
+ } catch {
145
+ return null;
146
+ }
147
+ let newest = null;
148
+ for (const name of names) {
149
+ if (!name.endsWith(".json")) continue;
150
+ const id = name.slice(0, -".json".length);
151
+ if (!OPERATION_ID.test(id)) continue;
152
+ try {
153
+ const receipt = readReceipt(home, id);
154
+ if (receipt?.sessionId !== sessionId || !receipt.scopeFile) continue;
155
+ const at = statSync(receiptPath(home, id)).mtimeMs;
156
+ if (!newest || at > newest.at) newest = { id, at };
157
+ } catch {
158
+ }
159
+ }
160
+ return newest?.id ?? null;
161
+ }
162
+ async function teamRelaunchEnv(input) {
163
+ const operationId = relaunchTeamOperationId(input.home, input.sessionId, input.metadata);
164
+ if (!operationId) return {};
165
+ const scopeFile = teamScopeFilePath(input.home, operationId);
166
+ if (!existsSync(scopeFile) && input.recover) {
167
+ try {
168
+ await input.recover(operationId, scopeFile);
169
+ input.log(`Teams scope for ${input.sessionId} rebuilt from operation ${operationId}`);
170
+ } catch (error) {
171
+ input.log(`Teams scope for ${input.sessionId} could not be rebuilt: ${error instanceof Error ? error.message : "unknown error"}`);
172
+ }
173
+ }
174
+ if (!existsSync(scopeFile)) input.log(`Teams scope for ${input.sessionId} is missing; team tools will refuse until the owner restores it`);
175
+ return { VH_TEAM_SCOPE_FILE: scopeFile, VH_TEAM_OPERATION_ID: operationId, HAPPY_SPAWNED_BY: "teams" };
176
+ }
177
+
95
178
  const DEFAULT_LOG_PRUNE_POLICY = {
96
179
  maxAgeMs: 14 * 24 * 60 * 6e4,
97
180
  maxTotalBytes: 200 * 1024 * 1024,
@@ -2172,196 +2255,6 @@ function unifiedDiff(oldText, newText, label, context = 3) {
2172
2255
  `;
2173
2256
  }
2174
2257
 
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
2258
  const ROLE$1 = "settings";
2366
2259
  const AUTH_TOKEN = "ANTHROPIC_AUTH_TOKEN";
2367
2260
  const API_KEY$1 = "ANTHROPIC_API_KEY";
@@ -2386,7 +2279,7 @@ function claudeSettingsPath(ctx) {
2386
2279
  const dir = ctx.env.CLAUDE_CONFIG_DIR?.trim() || join(ctx.home, ".claude");
2387
2280
  return join(dir, "settings.json");
2388
2281
  }
2389
- function str$4(v) {
2282
+ function str$3(v) {
2390
2283
  return typeof v === "string" && v.trim() ? v : null;
2391
2284
  }
2392
2285
  function envOf(settings) {
@@ -2399,11 +2292,11 @@ function pickerOptions(settings) {
2399
2292
  const options = picker.options;
2400
2293
  return Array.isArray(options) ? options.filter((o) => !!o && typeof o === "object" && typeof o.model === "string") : [];
2401
2294
  }
2402
- function secretView$2(settings, ctx) {
2295
+ function secretView$1(settings, ctx) {
2403
2296
  const env = envOf(settings);
2404
- const literal = str$4(env[AUTH_TOKEN]) ?? str$4(env[API_KEY$1]);
2297
+ const literal = str$3(env[AUTH_TOKEN]) ?? str$3(env[API_KEY$1]);
2405
2298
  if (literal) return { mode: "literal", masked: maskSecret(literal) };
2406
- const helper = str$4(settings.apiKeyHelper);
2299
+ const helper = str$3(settings.apiKeyHelper);
2407
2300
  if (helper) {
2408
2301
  const ours = OUR_HELPER.exec(helper.trim());
2409
2302
  if (ours) return { mode: "env", envName: ours[1], envPresent: !!ctx.env[ours[1]] };
@@ -2411,26 +2304,26 @@ function secretView$2(settings, ctx) {
2411
2304
  }
2412
2305
  return { mode: "none" };
2413
2306
  }
2414
- function currentOf$3(settings, ctx) {
2307
+ function currentOf$2(settings, ctx) {
2415
2308
  const env = envOf(settings);
2416
2309
  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]),
2310
+ baseUrl: str$3(env.ANTHROPIC_BASE_URL),
2311
+ authHeader: !str$3(env[AUTH_TOKEN]) && str$3(env[API_KEY$1]) ? "x-api-key" : "bearer",
2312
+ apiKey: secretView$1(settings, ctx),
2313
+ model: str$3(settings.model),
2314
+ opusModel: str$3(env[ALIAS_VARS.opusModel]),
2315
+ sonnetModel: str$3(env[ALIAS_VARS.sonnetModel]),
2316
+ haikuModel: str$3(env[ALIAS_VARS.haikuModel]),
2424
2317
  pickerModels: pickerOptions(settings).map((o) => o.model)
2425
2318
  };
2426
2319
  }
2427
- function pickerModelsOf$1(current) {
2320
+ function pickerModelsOf(current) {
2428
2321
  return uniq([current.model, current.opusModel, current.sonnetModel, current.haikuModel, ...current.pickerModels ?? []]);
2429
2322
  }
2430
- function warningsOf$3(settings, current, ctx) {
2323
+ function warningsOf$2(settings, current, ctx) {
2431
2324
  const env = envOf(settings);
2432
2325
  const out = [...baseUrlWarnings(current.baseUrl, "anthropic")];
2433
- if (str$4(env.ANTHROPIC_MODEL)) {
2326
+ if (str$3(env.ANTHROPIC_MODEL)) {
2434
2327
  out.push({ code: "env-model-overrides", message: "env.ANTHROPIC_MODEL is set in settings.json and overrides the default model." });
2435
2328
  }
2436
2329
  const daemonOther = current.authHeader === "bearer" ? API_KEY$1 : AUTH_TOKEN;
@@ -2442,7 +2335,7 @@ function warningsOf$3(settings, current, ctx) {
2442
2335
  }
2443
2336
  if (Array.isArray(settings.availableModels)) {
2444
2337
  const allowed = settings.availableModels;
2445
- const missing = pickerModelsOf$1(current).filter((id) => !allowed.includes(id));
2338
+ const missing = pickerModelsOf(current).filter((id) => !allowed.includes(id));
2446
2339
  if (missing.length) out.push({ code: "available-models-filter", message: `availableModels does not list ${missing.join(", ")}; Claude Code hides them.` });
2447
2340
  }
2448
2341
  return out;
@@ -2468,8 +2361,8 @@ const claudeAdapter = {
2468
2361
  files: (ctx) => [{ role: ROLE$1, path: claudeSettingsPath(ctx) }],
2469
2362
  view(files, ctx) {
2470
2363
  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) };
2364
+ const current = currentOf$2(settings, ctx);
2365
+ return { current, pickerModels: pickerModelsOf(current), warnings: warningsOf$2(settings, current, ctx) };
2473
2366
  },
2474
2367
  plan(files, raw, ctx) {
2475
2368
  const patch = parsePatch$1(raw);
@@ -2479,7 +2372,7 @@ const claudeAdapter = {
2479
2372
  const hadEnv = settings.env !== void 0;
2480
2373
  const env = childObject(settings, "env", "settings");
2481
2374
  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";
2375
+ const currentHeader = !str$3(env[AUTH_TOKEN]) && str$3(env[API_KEY$1]) ? "x-api-key" : "bearer";
2483
2376
  const header = patch.authHeader ?? currentHeader;
2484
2377
  const target = header === "bearer" ? AUTH_TOKEN : API_KEY$1;
2485
2378
  const other = header === "bearer" ? API_KEY$1 : AUTH_TOKEN;
@@ -2503,7 +2396,7 @@ const claudeAdapter = {
2503
2396
  if (helperIsOurs) delete settings.apiKeyHelper;
2504
2397
  break;
2505
2398
  case "keep":
2506
- if (header !== currentHeader && str$4(env[other]) && !str$4(env[target])) {
2399
+ if (header !== currentHeader && str$3(env[other]) && !str$3(env[target])) {
2507
2400
  env[target] = env[other];
2508
2401
  delete env[other];
2509
2402
  }
@@ -2523,15 +2416,15 @@ const claudeAdapter = {
2523
2416
  if (!hadPicker && Object.keys(picker).length === 0) delete settings.modelPicker;
2524
2417
  }
2525
2418
  if (!hadEnv && Object.keys(env).length === 0) delete settings.env;
2526
- const current = currentOf$3(settings, ctx);
2419
+ const current = currentOf$2(settings, ctx);
2527
2420
  if (Array.isArray(settings.availableModels)) {
2528
2421
  const allowed = settings.availableModels;
2529
- const add = pickerModelsOf$1(current).filter((id) => !allowed.includes(id));
2422
+ const add = pickerModelsOf(current).filter((id) => !allowed.includes(id));
2530
2423
  if (add.length) settings.availableModels = [...allowed, ...add];
2531
2424
  }
2532
2425
  const changedKeys = changedPaths(before, settings, OWNED_PATHS);
2533
2426
  const content = changedKeys.length === 0 ? void 0 : stringifyLike(original, settings);
2534
- return { contents: { [ROLE$1]: content }, warnings: warningsOf$3(settings, current, ctx), changedKeys, secrets };
2427
+ return { contents: { [ROLE$1]: content }, warnings: warningsOf$2(settings, current, ctx), changedKeys, secrets };
2535
2428
  },
2536
2429
  secretsIn(_role, content) {
2537
2430
  try {
@@ -3591,52 +3484,52 @@ function codexConfigPath(ctx) {
3591
3484
  const dir = ctx.env.CODEX_HOME?.trim() || join(ctx.home, ".codex");
3592
3485
  return join(dir, "config.toml");
3593
3486
  }
3594
- const str$3 = (v) => typeof v === "string" && v.trim() ? v : null;
3595
- function providersOf$1(doc) {
3487
+ const str$2 = (v) => typeof v === "string" && v.trim() ? v : null;
3488
+ function providersOf(doc) {
3596
3489
  const table = doc.model_providers;
3597
3490
  if (!table || typeof table !== "object" || Array.isArray(table)) return {};
3598
3491
  return Object.fromEntries(Object.entries(table).filter(([, v]) => v && typeof v === "object" && !Array.isArray(v)));
3599
3492
  }
3600
- function secretView$1(provider, ctx) {
3493
+ function secretView(provider, ctx) {
3601
3494
  if (!provider) return { mode: "none" };
3602
- const token = str$3(provider.experimental_bearer_token);
3495
+ const token = str$2(provider.experimental_bearer_token);
3603
3496
  if (token) return { mode: "literal", masked: maskSecret(token) };
3604
- const envKey = str$3(provider.env_key);
3497
+ const envKey = str$2(provider.env_key);
3605
3498
  if (envKey) return { mode: "env", envName: envKey, envPresent: !!ctx.env[envKey] };
3606
3499
  if (provider.auth && typeof provider.auth === "object") return { mode: "command" };
3607
3500
  return { mode: "none" };
3608
3501
  }
3609
3502
  function selectProvider(doc, requested) {
3610
- const providers = providersOf$1(doc);
3503
+ const providers = providersOf(doc);
3611
3504
  if (requested) return requested;
3612
- const active = str$3(doc.model_provider);
3505
+ const active = str$2(doc.model_provider);
3613
3506
  if (active && providers[active]) return active;
3614
3507
  return Object.keys(providers)[0] ?? null;
3615
3508
  }
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);
3509
+ function currentOf$1(doc, providerId, ctx) {
3510
+ const provider = providerId ? providersOf(doc)[providerId] : void 0;
3511
+ const activeProvider = str$2(doc.model_provider);
3619
3512
  return {
3620
3513
  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),
3514
+ providerName: str$2(provider?.name),
3515
+ baseUrl: str$2(provider?.base_url),
3516
+ apiKey: secretView(provider, ctx),
3517
+ model: str$2(doc.model),
3625
3518
  activeProvider,
3626
3519
  active: !!providerId && activeProvider === providerId
3627
3520
  };
3628
3521
  }
3629
- function warningsOf$2(doc, current) {
3522
+ function warningsOf$1(doc, current) {
3630
3523
  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") {
3524
+ const provider = current.providerId ? providersOf(doc)[current.providerId] : void 0;
3525
+ if (provider && str$2(provider.wire_api) && provider.wire_api !== "responses") {
3633
3526
  out.push({ code: "wire-api-unsupported", message: `wire_api = "${String(provider.wire_api)}" is rejected by current Codex; only "responses" is supported.` });
3634
3527
  }
3635
3528
  if (current.apiKey?.mode === "env" && !current.apiKey.envPresent) {
3636
3529
  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
3530
  }
3638
3531
  const active = current.activeProvider;
3639
- if (active && !CODEX_RESERVED_PROVIDERS.includes(active) && !providersOf$1(doc)[active]) {
3532
+ if (active && !CODEX_RESERVED_PROVIDERS.includes(active) && !providersOf(doc)[active]) {
3640
3533
  out.push({ code: "active-provider-missing", message: `model_provider = "${active}" has no [model_providers.${active}] table.` });
3641
3534
  }
3642
3535
  return out;
@@ -3662,25 +3555,25 @@ const codexAdapter = {
3662
3555
  files: (ctx) => [{ role: ROLE, path: codexConfigPath(ctx) }],
3663
3556
  view(files, ctx, opts) {
3664
3557
  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) };
3558
+ const current = currentOf$1(doc, selectProvider(doc, opts.providerId), ctx);
3559
+ return { current, providers: Object.keys(providersOf(doc)), pickerModels: uniq([current.model]), warnings: warningsOf$1(doc, current) };
3667
3560
  },
3668
3561
  plan(files, raw, ctx) {
3669
3562
  const patch = parsePatch(raw);
3670
3563
  const original = files[ROLE].content;
3671
3564
  const before = parseToml(original);
3672
- const existing = providersOf$1(before)[patch.providerId];
3565
+ const existing = providersOf(before)[patch.providerId];
3673
3566
  const table = ["model_providers", patch.providerId];
3674
3567
  const edits = [];
3675
3568
  const secrets = [];
3676
3569
  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 });
3570
+ const name = patch.providerName || str$2(existing?.name) || patch.providerId;
3571
+ if (!existing || patch.providerName && patch.providerName !== existing.name || !str$2(existing.name)) edits.push({ table, key: "name", value: name });
3679
3572
  if (patch.baseUrl !== void 0) {
3680
3573
  if (patch.baseUrl === null && existing) fail("invalid-input", "A provider needs a base URL; remove the provider instead");
3681
3574
  edits.push({ table, key: "base_url", value: patch.baseUrl });
3682
3575
  }
3683
- if (!existing || str$3(existing.wire_api) && existing.wire_api !== "responses") edits.push({ table, key: "wire_api", value: "responses" });
3576
+ if (!existing || str$2(existing.wire_api) && existing.wire_api !== "responses") edits.push({ table, key: "wire_api", value: "responses" });
3684
3577
  switch (patch.apiKey.mode) {
3685
3578
  case "literal":
3686
3579
  edits.push({ table, key: "experimental_bearer_token", value: patch.apiKey.value }, { table, key: "env_key", value: null });
@@ -3695,13 +3588,13 @@ const codexAdapter = {
3695
3588
  }
3696
3589
  if (patch.model !== void 0) edits.push({ table: [], key: "model", value: patch.model });
3697
3590
  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 });
3591
+ if (patch.activate === false && str$2(before.model_provider) === patch.providerId) edits.push({ table: [], key: "model_provider", value: null });
3699
3592
  const effective = edits.filter((e) => "removeTable" in e || getPath$1(before, [...e.table, e.key]) !== (e.value ?? void 0));
3700
3593
  const content = effective.length ? editToml(original, effective) : void 0;
3701
3594
  const after = content === void 0 ? before : parseToml(content);
3702
- const current = currentOf$2(after, patch.providerId, ctx);
3595
+ const current = currentOf$1(after, patch.providerId, ctx);
3703
3596
  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 };
3597
+ return { contents: { [ROLE]: content }, warnings: warningsOf$1(after, current), changedKeys, secrets };
3705
3598
  },
3706
3599
  secretsIn(_role, content) {
3707
3600
  try {
@@ -4053,25 +3946,25 @@ function editDotenv(text, edits) {
4053
3946
  return out;
4054
3947
  }
4055
3948
 
4056
- const SETTINGS$1 = "settings";
3949
+ const SETTINGS = "settings";
4057
3950
  const ENV = "env";
4058
3951
  const BASE_URL = "GOOGLE_GEMINI_BASE_URL";
4059
3952
  const API_KEY = "GEMINI_API_KEY";
4060
3953
  function geminiDir(ctx) {
4061
3954
  return join(ctx.env.GEMINI_CLI_HOME?.trim() || ctx.home, ".gemini");
4062
3955
  }
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]);
3956
+ const str$1 = (v) => typeof v === "string" && v.trim() ? v : null;
3957
+ function currentOf(settings, env, ctx) {
3958
+ const literal = str$1(env[API_KEY]);
4066
3959
  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"])) };
3960
+ return { baseUrl: str$1(env[BASE_URL]), apiKey, model: str$1(getPath$1(settings, ["model", "name"])) };
4068
3961
  }
4069
- function warningsOf$1(settings, current) {
3962
+ function warningsOf(settings, current) {
4070
3963
  const out = [...baseUrlWarnings(current.baseUrl, "gemini")];
4071
3964
  if (current.baseUrl || current.apiKey?.mode === "literal") {
4072
3965
  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
3966
  }
4074
- const selected = str$2(getPath$1(settings, ["security", "auth", "selectedType"]));
3967
+ const selected = str$1(getPath$1(settings, ["security", "auth", "selectedType"]));
4075
3968
  if (current.baseUrl && selected && selected !== "gateway") {
4076
3969
  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
3970
  }
@@ -4083,23 +3976,23 @@ function warningsOf$1(settings, current) {
4083
3976
  const geminiAdapter = {
4084
3977
  id: "gemini",
4085
3978
  binary: "gemini",
4086
- files: (ctx) => [{ role: SETTINGS$1, path: join(geminiDir(ctx), "settings.json") }, { role: ENV, path: join(geminiDir(ctx), ".env") }],
3979
+ files: (ctx) => [{ role: SETTINGS, path: join(geminiDir(ctx), "settings.json") }, { role: ENV, path: join(geminiDir(ctx), ".env") }],
4087
3980
  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) };
3981
+ const settings = parseJsonObject$1(files[SETTINGS].content, "settings.json");
3982
+ const current = currentOf(settings, parseDotenv(files[ENV].content), ctx);
3983
+ return { current, pickerModels: uniq([current.model]), warnings: warningsOf(settings, current) };
4091
3984
  },
4092
3985
  plan(files, raw, ctx) {
4093
3986
  const p = raw;
4094
3987
  const baseUrl = normalizeBaseUrl(p.baseUrl);
4095
3988
  const apiKey = normalizeSecret(p.apiKey, ["keep", "clear", "literal"]);
4096
3989
  const model = normalizeModelId(p.model);
4097
- const settingsText = files[SETTINGS$1].content;
3990
+ const settingsText = files[SETTINGS].content;
4098
3991
  const before = parseJsonObject$1(settingsText, "settings.json");
4099
3992
  const settings = structuredClone(before);
4100
3993
  const changedKeys = [];
4101
3994
  let settingsOut;
4102
- if (model !== void 0 && (str$2(getPath$1(before, ["model", "name"])) ?? null) !== model) {
3995
+ if (model !== void 0 && (str$1(getPath$1(before, ["model", "name"])) ?? null) !== model) {
4103
3996
  const hadModel = settings.model !== void 0;
4104
3997
  const modelObj = childObject(settings, "model", "settings");
4105
3998
  setOrDelete(modelObj, "name", model);
@@ -4116,10 +4009,10 @@ const geminiAdapter = {
4116
4009
  const envOut = edits.length ? editDotenv(envText, edits) : void 0;
4117
4010
  changedKeys.push(...edits.map((e) => `.env.${e.key}`));
4118
4011
  const afterEnv = envOut === void 0 ? env : parseDotenv(envOut);
4119
- const current = currentOf$1(settings, afterEnv, ctx);
4012
+ const current = currentOf(settings, afterEnv, ctx);
4120
4013
  return {
4121
- contents: { [SETTINGS$1]: settingsOut, [ENV]: envOut },
4122
- warnings: warningsOf$1(settings, current),
4014
+ contents: { [SETTINGS]: settingsOut, [ENV]: envOut },
4015
+ warnings: warningsOf(settings, current),
4123
4016
  changedKeys,
4124
4017
  secrets: apiKey.mode === "literal" ? [apiKey.value] : []
4125
4018
  };
@@ -4133,149 +4026,6 @@ const geminiAdapter = {
4133
4026
  }
4134
4027
  };
4135
4028
 
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
4029
  const MODEL_CONFIG_CAPABILITY_VERSION = 1;
4280
4030
  const HARNESS_IDS = ["claude", "codex", "gemini", "pi"];
4281
4031
 
@@ -4768,26 +4518,45 @@ function createUpdateController(deps) {
4768
4518
  let blocked = false;
4769
4519
  let handoverHold;
4770
4520
  let lastPublished = "";
4521
+ let autoUpdateEnabled;
4522
+ let lastInstall = (() => {
4523
+ try {
4524
+ return deps.lastInstall?.read() ?? null;
4525
+ } catch {
4526
+ return null;
4527
+ }
4528
+ })();
4771
4529
  const now = deps.now ?? Date.now;
4530
+ function snapshot() {
4531
+ return {
4532
+ ...policy,
4533
+ retrySupported: !blocked,
4534
+ manualUpdateSupported: !blocked,
4535
+ handoverHold,
4536
+ autoUpdate: outcome,
4537
+ ...autoUpdateEnabled === void 0 ? {} : { autoUpdateEnabled },
4538
+ ...lastInstall ? { lastInstall } : {}
4539
+ };
4540
+ }
4772
4541
  function validManualTarget(next, version) {
4773
4542
  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
4543
  }
4775
4544
  function publish(state, version, detail) {
4776
4545
  if (!policy) return;
4777
- const fingerprint = JSON.stringify([policy.checkedAt, state, version, detail, manualVersion]);
4546
+ const fingerprint = JSON.stringify([policy.checkedAt, state, version, detail, manualVersion, autoUpdateEnabled]);
4778
4547
  if (fingerprint === lastPublished) return;
4779
4548
  lastPublished = fingerprint;
4780
4549
  outcome = { state, version, detail, at: now(), ...manualVersion ? { source: "manual" } : {} };
4781
- deps.publish({ ...policy, retrySupported: !blocked, manualUpdateSupported: !blocked, handoverHold, autoUpdate: outcome });
4550
+ deps.publish(snapshot());
4782
4551
  }
4783
4552
  async function evaluate() {
4784
4553
  if (!policy || running) return;
4785
4554
  const target = manualVersion ?? policy.autoUpdateVersion ?? null;
4786
4555
  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]);
4556
+ const fingerprint = JSON.stringify([policy.checkedAt, outcome.state, outcome.version, outcome.detail, manualVersion, autoUpdateEnabled]);
4788
4557
  if (fingerprint !== lastPublished) {
4789
4558
  lastPublished = fingerprint;
4790
- deps.publish({ ...policy, retrySupported: !blocked, manualUpdateSupported: !blocked, handoverHold, autoUpdate: outcome });
4559
+ deps.publish(snapshot());
4791
4560
  }
4792
4561
  return;
4793
4562
  }
@@ -4795,9 +4564,12 @@ function createUpdateController(deps) {
4795
4564
  publish("policy_stale", target);
4796
4565
  return;
4797
4566
  }
4798
- if (!manualVersion && !await deps.enabled()) {
4799
- publish("disabled", target);
4800
- return;
4567
+ if (!manualVersion) {
4568
+ autoUpdateEnabled = await deps.enabled();
4569
+ if (!autoUpdateEnabled) {
4570
+ publish("disabled", target);
4571
+ return;
4572
+ }
4801
4573
  }
4802
4574
  if (running) return;
4803
4575
  if (now() - policy.checkedAt > 65 * 6e4) {
@@ -4827,6 +4599,13 @@ function createUpdateController(deps) {
4827
4599
  return;
4828
4600
  }
4829
4601
  blocked = code === "blocked";
4602
+ if (code === 0) {
4603
+ lastInstall = { from: policy.currentVersion, to: target, at: now(), source: manualVersion ? "manual" : "auto" };
4604
+ try {
4605
+ deps.lastInstall?.write(lastInstall);
4606
+ } catch {
4607
+ }
4608
+ }
4830
4609
  publish(
4831
4610
  blocked ? "manual_required" : code === 0 ? "installed" : "failed",
4832
4611
  target,
@@ -4928,7 +4707,224 @@ function createUpdateController(deps) {
4928
4707
  handoverHold = { reason, at: now() };
4929
4708
  publish("failed", manualVersion ?? policy?.autoUpdateVersion ?? null, "handover_preflight_failed");
4930
4709
  }
4931
- return { refresh, retry, request, tick, withHandover, holdHandover, isRunning: () => running };
4710
+ return { refresh, retry, request, tick, withHandover, holdHandover, isRunning: () => running, currentTarget: () => policy?.autoUpdateVersion ?? null };
4711
+ }
4712
+
4713
+ function parseLastInstall(raw) {
4714
+ if (!raw || typeof raw !== "object") return null;
4715
+ const { from, to, at, source } = raw;
4716
+ if (typeof from !== "string" || typeof to !== "string" || !parseExactVersion(from) || !parseExactVersion(to)) return null;
4717
+ if (typeof at !== "number" || !Number.isFinite(at) || source !== "auto" && source !== "manual") return null;
4718
+ return { from, to, at, source };
4719
+ }
4720
+ function lastInstallStore(homeDir) {
4721
+ const file = join(homeDir, "cli-update-last.json");
4722
+ return {
4723
+ read() {
4724
+ try {
4725
+ return parseLastInstall(JSON.parse(readFileSync(file, "utf8")));
4726
+ } catch {
4727
+ return null;
4728
+ }
4729
+ },
4730
+ write(record) {
4731
+ const tmp = `${file}.${process.pid}.tmp`;
4732
+ writeFileSync(tmp, JSON.stringify(record));
4733
+ renameSync(tmp, file);
4734
+ }
4735
+ };
4736
+ }
4737
+
4738
+ const ANNOUNCE_FIRST_DELAY_MS = 9e4;
4739
+ const ANNOUNCE_JITTER_MS = 6e4;
4740
+ const ANNOUNCE_FOLLOW_UP_MS = 5 * 6e4;
4741
+ function announcedVersion(payload) {
4742
+ if (!payload || typeof payload !== "object") return null;
4743
+ const raw = payload.autoUpdateVersion;
4744
+ if (typeof raw !== "string" || raw.length > 64) return null;
4745
+ return parseExactVersion(raw)?.exact ?? null;
4746
+ }
4747
+ function createPolicyAnnouncementHandler(deps) {
4748
+ const random = deps.random ?? Math.random;
4749
+ const setTimer = deps.setTimer ?? ((fn, ms) => {
4750
+ const t = setTimeout(fn, ms);
4751
+ t.unref?.();
4752
+ return t;
4753
+ });
4754
+ const clearTimer = deps.clearTimer ?? ((handle) => clearTimeout(handle));
4755
+ let timer = null;
4756
+ let pending = null;
4757
+ let stopped = false;
4758
+ function schedule(version, ms, followUp) {
4759
+ if (timer !== null) clearTimer(timer);
4760
+ pending = version;
4761
+ timer = setTimer(() => {
4762
+ timer = null;
4763
+ void deps.refresh().catch(() => void 0).finally(() => {
4764
+ if (stopped || pending !== version) return;
4765
+ if (deps.currentTarget() === version || !followUp) {
4766
+ pending = null;
4767
+ return;
4768
+ }
4769
+ schedule(version, ANNOUNCE_FOLLOW_UP_MS, false);
4770
+ });
4771
+ }, ms);
4772
+ }
4773
+ return {
4774
+ /** Socket handler. Returns the scheduled delay (ms) or null when ignored — for logs and tests. */
4775
+ onAnnouncement(payload) {
4776
+ if (stopped) return null;
4777
+ const version = announcedVersion(payload);
4778
+ if (!version || version === deps.currentTarget() || version === pending) return null;
4779
+ const delay = ANNOUNCE_FIRST_DELAY_MS + Math.floor(random() * ANNOUNCE_JITTER_MS);
4780
+ schedule(version, delay, true);
4781
+ return delay;
4782
+ },
4783
+ stop() {
4784
+ stopped = true;
4785
+ if (timer !== null) clearTimer(timer);
4786
+ timer = null;
4787
+ }
4788
+ };
4789
+ }
4790
+
4791
+ const HARNESS_INSTALL_CATALOG = Object.freeze({
4792
+ claude: Object.freeze({ packages: [{ name: "@anthropic-ai/claude-code", spec: "latest" }], commands: ["claude"], minNode: "22.0.0", scripts: true }),
4793
+ codex: Object.freeze({ packages: [{ name: "@openai/codex", spec: "latest" }], commands: ["codex"], minNode: "16.0.0", scripts: false }),
4794
+ gemini: Object.freeze({ packages: [{ name: "@google/gemini-cli", spec: "latest" }], commands: ["gemini"], minNode: "20.0.0", scripts: false }),
4795
+ pi: Object.freeze({
4796
+ packages: [{ name: "@earendil-works/pi-coding-agent", spec: "latest" }, { name: "pi-acp", spec: "0.0.33" }],
4797
+ commands: ["pi", "pi-acp"],
4798
+ minNode: "22.19.0",
4799
+ scripts: false
4800
+ })
4801
+ });
4802
+ function harnessInstallArgs(id, prefix) {
4803
+ const entry = HARNESS_INSTALL_CATALOG[id];
4804
+ if (!entry) throw new Error("Unknown harness");
4805
+ const args = ["i", "-g", `--prefix=${prefix}`, "--no-fund", "--no-audit", ...entry.scripts ? [] : ["--ignore-scripts"], ...entry.packages.map((p) => `${p.name}@${p.spec}`)];
4806
+ if (args.some((arg) => !NPM_SAFE_ARG.test(arg))) throw new Error("Unsafe npm argument");
4807
+ return args;
4808
+ }
4809
+ function parseNode(version) {
4810
+ const m = /^v?(\d+)\.(\d+)\.(\d+)/.exec(version.trim());
4811
+ return m ? [Number(m[1]), Number(m[2]), Number(m[3])] : null;
4812
+ }
4813
+ function nodeSatisfies(current, minimum) {
4814
+ const a = parseNode(current);
4815
+ const b = parseNode(minimum);
4816
+ if (!a || !b) return false;
4817
+ for (let i = 0; i < 3; i++) if (a[i] !== b[i]) return a[i] > b[i];
4818
+ return true;
4819
+ }
4820
+
4821
+ async function defaultCanWrite(path) {
4822
+ try {
4823
+ await access(path, constants.W_OK);
4824
+ return true;
4825
+ } catch {
4826
+ return false;
4827
+ }
4828
+ }
4829
+ async function defaultIsPlainDirectory(path) {
4830
+ try {
4831
+ const s = await lstat(path);
4832
+ return s.isDirectory() && !s.isSymbolicLink();
4833
+ } catch {
4834
+ return false;
4835
+ }
4836
+ }
4837
+ function redactNpmText(text) {
4838
+ 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, "~");
4839
+ }
4840
+ function summarizeNpmFailure(result) {
4841
+ const tail = `${result.stderr ?? ""}
4842
+ ${result.stdout ?? ""}`.trim().split("\n").filter((line) => /npm (error|ERR!)|EACCES|ENOTFOUND|ETARGET|E404|engine/i.test(line)).slice(-3).join(" | ");
4843
+ const code = result.code === null ? "npm_timeout" : `npm_exit_${result.code}`;
4844
+ const safe = redactNpmText(tail).slice(0, 300);
4845
+ return safe ? `${code}: ${safe}` : code;
4846
+ }
4847
+ function createHarnessInstaller(deps) {
4848
+ const now = deps.now ?? Date.now;
4849
+ const canWrite = deps.canWrite ?? defaultCanWrite;
4850
+ const isPlainDirectory = deps.isPlainDirectory ?? defaultIsPlainDirectory;
4851
+ let running = null;
4852
+ let blocked = false;
4853
+ const nodeVersion = deps.nodeVersion ?? process.versions.node;
4854
+ async function installPrefix() {
4855
+ const prefix = globalPrefixForPackage(deps.runningPackageDir);
4856
+ if (!prefix || !await isPlainDirectory(deps.runningPackageDir)) return { error: "install_location_unverified" };
4857
+ for (const path of [dirname(deps.runningPackageDir), join(prefix, "bin")]) {
4858
+ if (!await canWrite(path)) return { error: "install_location_not_writable" };
4859
+ }
4860
+ return { prefix };
4861
+ }
4862
+ async function run(harness, prefix, startedAt) {
4863
+ const publish = (state, detail) => deps.publish({ harness, state, ...detail ? { detail } : {}, startedAt, at: now() });
4864
+ try {
4865
+ const args = harnessInstallArgs(harness, prefix);
4866
+ deps.log(`[HARNESS] installing ${harness}: npm ${args.join(" ")}`);
4867
+ const result = await deps.npm(args);
4868
+ if (result.terminationConfirmed === false) {
4869
+ blocked = true;
4870
+ deps.log(`[HARNESS] ${harness} install timed out and npm could not be confirmed stopped; holding the npm tree until the daemon restarts`);
4871
+ publish("failed", "installer_termination_unconfirmed");
4872
+ return;
4873
+ }
4874
+ if (result.code !== 0) {
4875
+ const detail = summarizeNpmFailure(result);
4876
+ deps.log(`[HARNESS] ${harness} install failed: ${detail}`);
4877
+ publish("failed", detail);
4878
+ return;
4879
+ }
4880
+ const missing = HARNESS_INSTALL_CATALOG[harness].commands.filter((command) => !deps.commandOnPath(command));
4881
+ if (missing.length > 0) {
4882
+ const detail = `not_on_path: ${missing.join(", ")}`;
4883
+ deps.log(`[HARNESS] ${harness} installed into ${prefix} but ${detail}`);
4884
+ publish("failed", detail);
4885
+ return;
4886
+ }
4887
+ await deps.afterInstall().catch((error) => deps.log(`[HARNESS] refresh after ${harness} install failed: ${String(error)}`));
4888
+ deps.log(`[HARNESS] ${harness} installed`);
4889
+ publish("installed");
4890
+ } catch (error) {
4891
+ deps.log(`[HARNESS] ${harness} install error: ${String(error)}`);
4892
+ publish("failed", "install_error");
4893
+ } finally {
4894
+ if (!blocked) running = null;
4895
+ }
4896
+ }
4897
+ return {
4898
+ /** True while npm may be writing the prefix — including after an unconfirmed kill. */
4899
+ isRunning: () => running !== null || blocked,
4900
+ async request(params) {
4901
+ const harness = params && typeof params === "object" ? params.harness : void 0;
4902
+ if (!isHarnessInstallId(harness)) {
4903
+ deps.log(`[HARNESS] refused install request for an unknown harness`);
4904
+ return { error: "unknown_harness" };
4905
+ }
4906
+ if (blocked) return { error: "installer_blocked" };
4907
+ if (running) return { error: "install_in_progress" };
4908
+ if (deps.cliUpdateBusy()) return { error: "cli_update_in_progress" };
4909
+ const minNode = HARNESS_INSTALL_CATALOG[harness].minNode;
4910
+ if (!nodeSatisfies(nodeVersion, minNode)) {
4911
+ deps.log(`[HARNESS] ${harness} install refused: needs Node ${minNode}+, daemon runs ${nodeVersion}`);
4912
+ return { error: "node_too_old", required: minNode, current: nodeVersion };
4913
+ }
4914
+ running = harness;
4915
+ const location = await installPrefix();
4916
+ if ("error" in location) {
4917
+ running = null;
4918
+ deps.log(`[HARNESS] ${harness} install refused: ${location.error}`);
4919
+ return location;
4920
+ }
4921
+ const startedAt = now();
4922
+ deps.log(`[HARNESS] owner requested ${harness} install`);
4923
+ deps.publish({ harness, state: "installing", startedAt, at: startedAt });
4924
+ void run(harness, location.prefix, startedAt);
4925
+ return { accepted: true, harness };
4926
+ }
4927
+ };
4932
4928
  }
4933
4929
 
4934
4930
  function buildPresenceBatch(live, previous) {
@@ -6240,7 +6236,10 @@ function mergeRestoreMetadata(stale, reported) {
6240
6236
  }
6241
6237
 
6242
6238
  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.";
6239
+ 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.
6240
+
6241
+ ${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.
6242
+ ${TEAM_MEMBER_RULES}`;
6244
6243
  return `${TEAM_SKILL}
6245
6244
 
6246
6245
  ## Your role
@@ -6254,42 +6253,6 @@ Delegate from this working directory unless a different repository was explicitl
6254
6253
  ${goal}`;
6255
6254
  }
6256
6255
 
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
6256
  const exec = promisify(execFile);
6294
6257
  const git = async (directory, args) => (await exec("git", ["-C", directory, ...args], { timeout: 3e4, maxBuffer: 2 * 1024 * 1024 })).stdout.trim();
6295
6258
  const gitRaw = async (directory, args) => (await exec("git", ["-C", directory, ...args], { timeout: 3e4, maxBuffer: 64 * 1024 * 1024 })).stdout;
@@ -6647,7 +6610,19 @@ Use the teams tools to inspect current task state before acting. This message is
6647
6610
  }, 5e3);
6648
6611
  timer.unref();
6649
6612
  void tick();
6650
- return { tick, report, hasSession: (id) => knownSessionIds.has(id), get busy() {
6613
+ async function recoverScope(operationId, sessionId, scopeFile) {
6614
+ const teams = (await http.get("/v1/teams")).data.teams ?? [];
6615
+ const team = teams.find((candidate) => candidate.archivedAt === void 0 && candidate.operations.some((op2) => op2.id === operationId));
6616
+ const op = team?.operations.find((candidate) => candidate.id === operationId);
6617
+ if (!team || !op || op.type !== "spawn" || op.machineId !== deps.machineId) throw new Error("No live team launch for this session");
6618
+ const bot = team.bots.find((candidate) => candidate.id === op.botId);
6619
+ if (!bot || bot.sessionId !== sessionId || bot.generation !== op.generation) throw new Error("Team binding changed; not restoring its scope");
6620
+ const claimed = await action(op.teamId, `claim-${op.id}`, { type: "claim-operation", operationId: op.id, machineId: deps.machineId, ...op.teamLaunchVersion === 1 ? { teamLaunchVersion: 1 } : {} });
6621
+ if (!claimed.credential?.token || claimed.credential.botId !== op.botId) throw new Error("Team did not reissue the member credential");
6622
+ ensurePrivateDirectorySync(join(home, "teams", "scopes"));
6623
+ writePrivateFileSync(scopeFile, JSON.stringify({ serverUrl: server, scopeToken: claimed.credential.token, teamId: op.teamId, botId: op.botId, taskId: op.taskId, attemptId: op.attemptId }));
6624
+ }
6625
+ return { tick, report, recoverScope, hasSession: (id) => knownSessionIds.has(id), get busy() {
6651
6626
  return running;
6652
6627
  }, stop: () => {
6653
6628
  closed = true;
@@ -8167,6 +8142,13 @@ async function startDaemon() {
8167
8142
  }
8168
8143
  };
8169
8144
  const resumeGates = /* @__PURE__ */ new Map();
8145
+ const teamRelaunchEnvFor = (happySessionId, metadata) => teamRelaunchEnv({
8146
+ home: configuration.happyHomeDir,
8147
+ sessionId: happySessionId,
8148
+ metadata,
8149
+ recover: teamWorker ? (operationId, scopeFile) => teamWorker.recoverScope(operationId, happySessionId, scopeFile) : void 0,
8150
+ log: (message) => logger.debug(`[DAEMON RUN] ${message}`)
8151
+ });
8170
8152
  const resumeSession = async (happySessionId, options) => {
8171
8153
  let gate = resumeGates.get(happySessionId);
8172
8154
  if (!gate) {
@@ -8249,9 +8231,11 @@ async function startDaemon() {
8249
8231
  const permissionMode = sanitizeSpawnPermissionMode(requestedMode);
8250
8232
  if (permissionMode) launch.args.push("--permission-mode", permissionMode);
8251
8233
  else if (requestedMode !== void 0) logger.debug(`[DAEMON RUN] resume: ignoring invalid permission mode ${JSON.stringify(requestedMode)}`);
8234
+ const teamEnv = await teamRelaunchEnvFor(happySessionId, metadata);
8252
8235
  const result = await spawnTrackedHappyProcess({
8253
8236
  args: launch.args,
8254
8237
  cwd: launch.cwd,
8238
+ ...teamEnv.VH_TEAM_SCOPE_FILE ? { spawnedBy: "teams" } : {},
8255
8239
  env: {
8256
8240
  ...process.env,
8257
8241
  // B-297: the credentialStore=file shim must ride every spawn path,
@@ -8267,7 +8251,8 @@ async function startDaemon() {
8267
8251
  HAPPY_RECONNECT_ENCRYPTION_VARIANT: tracked.encryption.encryptionVariant,
8268
8252
  HAPPY_RECONNECT_SEQ: String(tracked.encryption.seq),
8269
8253
  HAPPY_RECONNECT_METADATA_VERSION: String(tracked.encryption.metadataVersion),
8270
- HAPPY_RECONNECT_AGENT_STATE_VERSION: String(tracked.encryption.agentStateVersion)
8254
+ HAPPY_RECONNECT_AGENT_STATE_VERSION: String(tracked.encryption.agentStateVersion),
8255
+ ...teamEnv
8271
8256
  }
8272
8257
  });
8273
8258
  if (result.type === "success") {
@@ -8385,9 +8370,11 @@ async function startDaemon() {
8385
8370
  const permissionMode = sanitizeSpawnPermissionMode(options?.permissionMode);
8386
8371
  if (permissionMode) args.push("--permission-mode", permissionMode);
8387
8372
  restartCounts.set(happySessionId, recordRestartAttempt(prior));
8373
+ const teamEnv = await teamRelaunchEnvFor(happySessionId, metadata);
8388
8374
  const result = await spawnTrackedHappyProcess({
8389
8375
  args,
8390
8376
  cwd,
8377
+ ...teamEnv.VH_TEAM_SCOPE_FILE ? { spawnedBy: "teams" } : {},
8391
8378
  env: {
8392
8379
  ...process.env,
8393
8380
  // B-297: the credentialStore=file shim must ride every spawn path,
@@ -8402,7 +8389,8 @@ async function startDaemon() {
8402
8389
  HAPPY_RECONNECT_ENCRYPTION_VARIANT: tracked.encryption.encryptionVariant,
8403
8390
  HAPPY_RECONNECT_SEQ: String(tracked.encryption.seq),
8404
8391
  HAPPY_RECONNECT_METADATA_VERSION: String(tracked.encryption.metadataVersion),
8405
- HAPPY_RECONNECT_AGENT_STATE_VERSION: String(tracked.encryption.agentStateVersion)
8392
+ HAPPY_RECONNECT_AGENT_STATE_VERSION: String(tracked.encryption.agentStateVersion),
8393
+ ...teamEnv
8406
8394
  }
8407
8395
  });
8408
8396
  if (result.type === "success") {
@@ -8705,6 +8693,8 @@ async function startDaemon() {
8705
8693
  })).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
8694
  }
8707
8695
  let lastUpdateLog = "";
8696
+ let harnessInstaller = null;
8697
+ let handoverInFlight = false;
8708
8698
  const updateController = createUpdateController({
8709
8699
  policy: () => fetchCliUpdateState(configuration.serverUrl, packageJson.version),
8710
8700
  enabled: async () => ((await readSettings()).cliAutoUpdate ?? "idle") !== "off",
@@ -8712,8 +8702,9 @@ async function startDaemon() {
8712
8702
  // handover (they talk to the server themselves) and web terminals live
8713
8703
  // in tmux, so neither is a reason to hold an update. B-507: a session
8714
8704
  // whose turn ended but still has background tasks is busy too.
8715
- idle: () => !turnActivity.hasActiveTurn() && !backgroundTasks.hasAny(),
8705
+ idle: () => !turnActivity.hasActiveTurn() && !backgroundTasks.hasAny() && !harnessInstaller?.isRunning(),
8716
8706
  install: installCliSafely,
8707
+ lastInstall: lastInstallStore(configuration.happyHomeDir),
8717
8708
  publish: (state) => {
8718
8709
  cliUpdateStateRef = state;
8719
8710
  fileState = { ...fileState, cliUpdate: state };
@@ -8760,10 +8751,33 @@ async function startDaemon() {
8760
8751
  logger.warn(`[DAEMON RUN] Ignoring invalid HAPPY_CLI_UPDATE_CHECK_INTERVAL; using ${DEFAULT_CLI_UPDATE_CHECK_INTERVAL_MS}ms`);
8761
8752
  }
8762
8753
  const cliUpdateInterval = setInterval(() => void refreshCliUpdate(), cliUpdateIntervalMs);
8754
+ const policyAnnouncements = createPolicyAnnouncementHandler({
8755
+ refresh: refreshCliUpdate,
8756
+ currentTarget: updateController.currentTarget
8757
+ });
8758
+ apiMachine.setCliUpdatePolicyHandler((payload) => {
8759
+ const delay = policyAnnouncements.onAnnouncement(payload);
8760
+ if (delay !== null) logger.warn(`[DAEMON RUN] CLI update policy announced; re-checking in ${Math.round(delay / 1e3)}s`);
8761
+ });
8763
8762
  const stopAgentVersionChecks = startAgentVersionChecks(
8764
8763
  (snapshot) => apiMachine.updateDaemonState((state) => ({ ...state, status: state?.status ?? "running", agentVersions: snapshot })),
8765
8764
  (error) => logger.debug("[DAEMON RUN] Agent version check failed", error)
8766
8765
  );
8766
+ harnessInstaller = createHarnessInstaller({
8767
+ npm: runNpm,
8768
+ runningPackageDir: projectPath(),
8769
+ commandOnPath,
8770
+ cliUpdateBusy: () => updateController.isRunning() || handoverInFlight,
8771
+ publish: (state) => {
8772
+ void apiMachine.updateDaemonState((current) => ({ ...current, status: current?.status ?? "running", harnessInstall: state })).catch((error) => logger.debug("[DAEMON RUN] harness install state publish failed", error));
8773
+ },
8774
+ afterInstall: async () => {
8775
+ apiMachine.recheckCliAvailability();
8776
+ await stopAgentVersionChecks.refresh();
8777
+ },
8778
+ log: (message) => logger.warn(`[DAEMON RUN] ${message}`)
8779
+ });
8780
+ apiMachine.setHarnessInstallHandler((params) => harnessInstaller.request(params));
8767
8781
  const heartbeatIntervalMs = parseInt(process.env.HAPPY_DAEMON_HEARTBEAT_INTERVAL || "60000");
8768
8782
  const restartOnStaleVersionAndHeartbeat = setInterval(serialTask(async () => {
8769
8783
  try {
@@ -8780,53 +8794,59 @@ async function startDaemon() {
8780
8794
  }
8781
8795
  await updateController.tick();
8782
8796
  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}`);
8797
+ handoverInFlight = true;
8798
+ try {
8799
+ let bundleReplaced = false;
8800
+ if (initialBundleMtimeMs > 0) {
8801
+ try {
8802
+ const currentMtimeMs = statSync$1(bundlePath).mtimeMs;
8803
+ bundleReplaced = currentMtimeMs !== initialBundleMtimeMs;
8804
+ } catch {
8798
8805
  }
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
8806
  }
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);
8807
+ const relayMove = pendingRelayMove;
8808
+ if ((bundleReplaced || relayMove) && !teamWorker?.busy && !automationRunner?.busy && !updateController.isRunning() && !harnessInstaller?.isRunning() && !turnActivity.hasActiveTurn() && !backgroundTasks.hasAny()) {
8809
+ const preflight = bundleReplaced ? await preflightNewBundle(bundlePath) : { action: "proceed" };
8810
+ if (preflight.action === "hold") {
8811
+ if (lastHandoverHold !== preflight.reason) {
8812
+ lastHandoverHold = preflight.reason;
8813
+ logger.warn(`[DAEMON RUN] holding handover: ${preflight.reason}`);
8814
+ }
8815
+ updateController.holdHandover(preflight.reason);
8816
+ return;
8817
+ }
8818
+ if (teamWorker?.busy || automationRunner?.busy || updateController.isRunning() || harnessInstaller?.isRunning() || turnActivity.hasActiveTurn() || backgroundTasks.hasAny()) return;
8819
+ lastHandoverHold = null;
8820
+ 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");
8821
+ teamWorker?.stop();
8822
+ automationRunner?.stop();
8823
+ projectPresence?.stop();
8824
+ clearInterval(restartOnStaleVersionAndHeartbeat);
8825
+ clearInterval(cliUpdateInterval);
8826
+ policyAnnouncements.stop();
8827
+ stopAgentVersionChecks();
8828
+ claudeAuthService.stop();
8829
+ apiMachine.shutdown();
8830
+ await stopControlServer();
8831
+ await cleanupDaemonState();
8832
+ await releaseDaemonLock(daemonLockHandle);
8833
+ await stopCaffeinate();
8834
+ if (daemonSupervisor === "systemd") {
8835
+ logger.debug(`[DAEMON RUN] systemd-supervised: exiting ${HANDOVER_EXIT_CODE} for the unit to restart on the new bundle`);
8836
+ process.exit(HANDOVER_EXIT_CODE);
8837
+ }
8838
+ try {
8839
+ spawnHappyCLI(["daemon", "start"], {
8840
+ detached: true,
8841
+ stdio: "ignore"
8842
+ });
8843
+ } catch (error) {
8844
+ 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);
8845
+ }
8846
+ process.exit(0);
8828
8847
  }
8829
- process.exit(0);
8848
+ } finally {
8849
+ handoverInFlight = false;
8830
8850
  }
8831
8851
  });
8832
8852
  const daemonState = await readDaemonState();
@@ -8893,7 +8913,8 @@ async function startDaemon() {
8893
8913
  log: (message) => logger.debug(`[DAEMON RUN] ${message}`)
8894
8914
  });
8895
8915
  void apiMachine.updateMachineMetadata((current) => ({
8896
- ...current ?? initialMachineMetadata,
8916
+ // B-718: the running CLI version, not the one that first registered the row.
8917
+ ...withRunningCliFacts(current, initialMachineMetadata),
8897
8918
  teamsVersion: 1,
8898
8919
  teamLaunchVersion: 1,
8899
8920
  // B-689: this daemon acts on the owner's retry-operation (web gates「重试启动」).
@@ -8901,7 +8922,9 @@ async function startDaemon() {
8901
8922
  // B-586: this daemon serves the worktree-* RPCs (web gates 「在新 worktree 中」 on it).
8902
8923
  worktreeVersion: WORKTREE_CAPABILITY_VERSION,
8903
8924
  // B-688: this daemon serves the model-config-* RPCs (web gates 「模型与 Provider」 on it).
8904
- modelConfigVersion: MODEL_CONFIG_CAPABILITY_VERSION
8925
+ modelConfigVersion: MODEL_CONFIG_CAPABILITY_VERSION,
8926
+ // B-719: this daemon serves `harness-install` (web gates the Install / Update buttons on it).
8927
+ harnessInstallVersion: HARNESS_INSTALL_CAPABILITY_VERSION
8905
8928
  })).catch(() => logger.debug("[DAEMON RUN] Teams capability publication will retry on reconnect"));
8906
8929
  const cleanupAndShutdown = async (source, errorMessage) => {
8907
8930
  webPanelsSync.stop();
@@ -8916,6 +8939,7 @@ async function startDaemon() {
8916
8939
  logger.debug("[DAEMON RUN] Health check interval cleared");
8917
8940
  }
8918
8941
  clearInterval(cliUpdateInterval);
8942
+ policyAnnouncements.stop();
8919
8943
  stopAgentVersionChecks();
8920
8944
  await apiMachine.updateDaemonState((state) => ({
8921
8945
  ...state,