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,26 +1,26 @@
1
1
  'use strict';
2
2
 
3
- var mirrorProtocol = require('./mirrorProtocol-HKPivs5o.cjs');
4
- var piRunnerArgs = require('./piRunnerArgs-DGaq6VYM.cjs');
3
+ var mirrorProtocol = require('./mirrorProtocol-BphKIAK9.cjs');
4
+ var piRunnerArgs = require('./piRunnerArgs-gxLYIZH4.cjs');
5
5
  var fs = require('fs/promises');
6
6
  var os$1 = require('os');
7
7
  var tmp = require('tmp');
8
8
  var axios = require('axios');
9
- var api = require('./api-DM7b1OKj.cjs');
10
- var spawnAgents = require('./spawnAgents-DLNMlhWJ.cjs');
11
- var logger = require('./logger-CphW5nUE.cjs');
12
9
  var node_fs = require('node:fs');
13
10
  var path = require('node:path');
14
- var index$2 = require('./index-F2UejqYs.cjs');
15
- var auth = require('./auth-CBXnm-Mj.cjs');
16
- var configuration = require('./configuration-CYO-kPOS.cjs');
11
+ var configuration = require('./configuration-C8JgseHy.cjs');
12
+ var api = require('./api-COHFAkb_.cjs');
13
+ var spawnAgents = require('./spawnAgents-iwuqrgR9.cjs');
14
+ var logger = require('./logger-DgBwUSgS.cjs');
15
+ var index$2 = require('./index-BRMq9gOe.cjs');
16
+ var auth = require('./auth-CgQP7d0q.cjs');
17
17
  var child_process = require('child_process');
18
- var _package = require('./package-KhLqBDX9.cjs');
19
- var doctor = require('./doctor-BrgSlfXn.cjs');
20
- var spawnHappyCLI = require('./spawnHappyCLI-DnUZv0X2.cjs');
21
- var persistence = require('./persistence-9BMYspxG.cjs');
22
- var controlClient = require('./controlClient-Bqv5v5OM.cjs');
23
- var sessionOps = require('./sessionOps-BrGiU9-s.cjs');
18
+ var _package = require('./package-NoD8QOOM.cjs');
19
+ var doctor = require('./doctor-Be7zC0AF.cjs');
20
+ var spawnHappyCLI = require('./spawnHappyCLI-DnKmIsEt.cjs');
21
+ var persistence = require('./persistence-BY-D00wT.cjs');
22
+ var controlClient = require('./controlClient-D68Zj7Q-.cjs');
23
+ var sessionOps = require('./sessionOps-DVA-00nR.cjs');
24
24
  var fastify = require('fastify');
25
25
  var z = require('zod');
26
26
  var fastifyTypeProviderZod = require('fastify-type-provider-zod');
@@ -28,60 +28,62 @@ var authRelay = require('./authRelay-Dy1qU9td.cjs');
28
28
  var node_crypto = require('node:crypto');
29
29
  var previewPath = require('./previewPath-B0rBdhy1.cjs');
30
30
  var backgroundTaskActivity = require('./backgroundTaskActivity-CBvUdj39.cjs');
31
- var rateLimitWindows = require('./rateLimitWindows-YRIYVdo4.cjs');
32
- var peerTools = require('./peerTools-DQNlrcqM.cjs');
31
+ var rateLimitWindows = require('./rateLimitWindows-OpAmdOtC.cjs');
32
+ var peerTools = require('./peerTools-CP-x-Peu.cjs');
33
33
  var os = require('node:os');
34
- var remoteSessionClient = require('./remoteSessionClient-D843fkzL.cjs');
35
- var bootstrap = require('./bootstrap-DoA7okld.cjs');
36
- var index$1 = require('./index-C64Ufj4D.cjs');
34
+ var remoteSessionClient = require('./remoteSessionClient-CFCn42bs.cjs');
35
+ var bootstrap = require('./bootstrap-CYtOtbi7.cjs');
36
+ var index$1 = require('./index-BkluinoS.cjs');
37
37
  var require$$0 = require('fs');
38
38
  var require$$1 = require('path');
39
39
  var node_child_process = require('node:child_process');
40
40
  var projectPath = require('./projectPath-C3ioB4Bu.cjs');
41
41
  var doctorReadiness = require('./doctorReadiness-Dl6yRQCy.cjs');
42
- var machineMetadata = require('./machineMetadata-BQm9YTcI.cjs');
42
+ var machineMetadata = require('./machineMetadata-DB03ez4K.cjs');
43
+ var fileStore = require('./fileStore-7qhnbs6N.cjs');
44
+ var pi = require('./pi-DlsrPCTa.cjs');
43
45
  var require$$3 = require('crypto');
44
46
  var detectCLI = require('./detectCLI-BuYR_Qxe.cjs');
45
- var handleResumeCommand = require('./handleResumeCommand-BvWVuRwH.cjs');
47
+ var handleResumeCommand = require('./handleResumeCommand-BFZlrFVH.cjs');
46
48
  var webPanels = require('./webPanels-FCgFKvVe.cjs');
47
- var index = require('./index-Dv0Q48P3.cjs');
49
+ var index = require('./index-Df2eHm3O.cjs');
48
50
  var promises = require('node:fs/promises');
49
51
  var cliUpdate = require('./cliUpdate-DsKXPFYp.cjs');
50
- var client = require('./client-DJplzPjD.cjs');
51
- var client$1 = require('./client-BWjaHKtj.cjs');
52
+ var client = require('./client-DWs6k041.cjs');
53
+ var client$1 = require('./client-D_U2VjO3.cjs');
52
54
  var module$1 = require('node:module');
53
55
  var path$1 = require('./path-B7zW-WwE.cjs');
54
56
  var editPaths = require('./editPaths-Bqp4OFPw.cjs');
55
- var startFileWatcher = require('./startFileWatcher-D9cYzZOq.cjs');
56
- var sync = require('./sync-Bn-idAoy.cjs');
57
+ var startFileWatcher = require('./startFileWatcher-K6hJSyiR.cjs');
58
+ var sync = require('./sync-1hQ5KHpL.cjs');
57
59
  var types = require('./types-CRS-srZK.cjs');
58
60
  var tmuxSocket = require('./tmuxSocket-D-Xt6Vn-.cjs');
59
- var sessionLock = require('./sessionLock-DRZUcKHT.cjs');
61
+ var sessionLock = require('./sessionLock-CdIRJlHH.cjs');
60
62
  var doctor$1 = require('./doctor-u46qYYKX.cjs');
61
- var resources = require('./resources-CvIlkzpP.cjs');
63
+ var resources = require('./resources-BPyN59Iv.cjs');
62
64
  var node_util = require('node:util');
63
65
  var template = require('./template-BaHAmH2K.cjs');
64
66
  var daemonSupervisor = require('./daemonSupervisor-B-SPSqhs.cjs');
65
67
  var loginShellEnv = require('./loginShellEnv-BkuYtExZ.cjs');
66
- require('./acpSessionResume-CR5pmgVo.cjs');
67
- require('./lidState-DWGnm2H4.cjs');
68
+ require('./acpSessionResume-BaVMD80f.cjs');
69
+ require('./lidState-x8cJ8J_9.cjs');
68
70
  require('node:events');
69
71
  require('socket.io-client');
70
- require('./pushNotifications-DI92havG.cjs');
72
+ require('./pushNotifications-BXoOSq_G.cjs');
71
73
  require('./contentLogMetadata-ZSFk_NjQ.cjs');
72
74
  require('./limits-C-a7gr04.cjs');
73
- require('./time-Cjv0zxQO.cjs');
74
- require('./registerCommonHandlers-C7a_dUWs.cjs');
75
+ require('./time-nqa9shu8.cjs');
76
+ require('./registerCommonHandlers-CRjBoJLK.cjs');
75
77
  require('util');
76
78
  require('cross-spawn');
77
79
  require('@paralleldrive/cuid2');
78
80
  require('chalk');
79
81
  require('tweetnacl');
80
- require('./browser-DZ6qRtfH.cjs');
82
+ require('./browser-DjNNQQk2.cjs');
81
83
  require('open');
82
84
  require('./ids-B6ZC0HGt.cjs');
83
85
  require('url');
84
- require('./localHappyAgentAuth-DZHQnIW9.cjs');
86
+ require('./localHappyAgentAuth-DZ2kb9uq.cjs');
85
87
  require('./processTreeSnapshot-mDbnuojn.cjs');
86
88
  require('ps-list');
87
89
 
@@ -114,6 +116,87 @@ async function resolveRelaunchMetadata(snapshot, fetchServer) {
114
116
  return { metadata: snapshot, source: "snapshot" };
115
117
  }
116
118
 
119
+ function receiptPath(home, operationId) {
120
+ if (!/^[a-zA-Z0-9_-]{1,128}$/.test(operationId)) throw new Error("Invalid team operation id");
121
+ return path.join(home, "teams", "effects", `${operationId}.json`);
122
+ }
123
+ function readReceipt(home, id) {
124
+ try {
125
+ const value = JSON.parse(node_fs.readFileSync(receiptPath(home, id), "utf8"));
126
+ if (value.operationId !== id || typeof value.claimId !== "string" || !["prepared", "spawning", "spawned", "delivered", "completed"].includes(value.phase)) throw new Error("Invalid team effect receipt");
127
+ return value;
128
+ } catch (error) {
129
+ if (error.code === "ENOENT") return null;
130
+ throw error;
131
+ }
132
+ }
133
+ function writeReceipt$1(home, receipt) {
134
+ configuration.ensurePrivateDirectorySync(path.join(home, "teams", "effects"));
135
+ const target = receiptPath(home, receipt.operationId);
136
+ const tmp = `${target}.${process.pid}.tmp`;
137
+ configuration.writePrivateFileSync(tmp, JSON.stringify(receipt));
138
+ const file = node_fs.openSync(tmp, "r");
139
+ try {
140
+ node_fs.fsyncSync(file);
141
+ } finally {
142
+ node_fs.closeSync(file);
143
+ }
144
+ node_fs.renameSync(tmp, target);
145
+ if (process.platform !== "win32") {
146
+ const directory = node_fs.openSync(path.join(home, "teams", "effects"), "r");
147
+ try {
148
+ node_fs.fsyncSync(directory);
149
+ } finally {
150
+ node_fs.closeSync(directory);
151
+ }
152
+ }
153
+ }
154
+
155
+ const OPERATION_ID = /^[a-zA-Z0-9_-]{1,128}$/;
156
+ function teamScopeFilePath(home, operationId) {
157
+ if (!OPERATION_ID.test(operationId)) throw new Error("Invalid team operation id");
158
+ return path.join(home, "teams", "scopes", `${operationId}.json`);
159
+ }
160
+ function relaunchTeamOperationId(home, sessionId, metadata) {
161
+ const fromMetadata = metadata?.teamOperationId;
162
+ if (typeof fromMetadata === "string" && OPERATION_ID.test(fromMetadata)) return fromMetadata;
163
+ let names;
164
+ try {
165
+ names = node_fs.readdirSync(path.join(home, "teams", "effects"));
166
+ } catch {
167
+ return null;
168
+ }
169
+ let newest = null;
170
+ for (const name of names) {
171
+ if (!name.endsWith(".json")) continue;
172
+ const id = name.slice(0, -".json".length);
173
+ if (!OPERATION_ID.test(id)) continue;
174
+ try {
175
+ const receipt = readReceipt(home, id);
176
+ if (receipt?.sessionId !== sessionId || !receipt.scopeFile) continue;
177
+ const at = node_fs.statSync(receiptPath(home, id)).mtimeMs;
178
+ if (!newest || at > newest.at) newest = { id, at };
179
+ } catch {
180
+ }
181
+ }
182
+ return newest?.id ?? null;
183
+ }
184
+ async function teamRelaunchEnv(input) {
185
+ const operationId = relaunchTeamOperationId(input.home, input.sessionId, input.metadata);
186
+ if (!operationId) return {};
187
+ const scopeFile = teamScopeFilePath(input.home, operationId);
188
+ if (!node_fs.existsSync(scopeFile) && input.recover) {
189
+ try {
190
+ await input.recover(operationId, scopeFile);
191
+ input.log(`Teams scope for ${input.sessionId} rebuilt from operation ${operationId}`);
192
+ } catch (error) {
193
+ input.log(`Teams scope for ${input.sessionId} could not be rebuilt: ${error instanceof Error ? error.message : "unknown error"}`);
194
+ }
195
+ }
196
+ if (!node_fs.existsSync(scopeFile)) input.log(`Teams scope for ${input.sessionId} is missing; team tools will refuse until the owner restores it`);
197
+ return { VH_TEAM_SCOPE_FILE: scopeFile, VH_TEAM_OPERATION_ID: operationId, HAPPY_SPAWNED_BY: "teams" };
198
+ }
199
+
117
200
  const DEFAULT_LOG_PRUNE_POLICY = {
118
201
  maxAgeMs: 14 * 24 * 60 * 6e4,
119
202
  maxTotalBytes: 200 * 1024 * 1024,
@@ -2194,196 +2277,6 @@ function unifiedDiff(oldText, newText, label, context = 3) {
2194
2277
  `;
2195
2278
  }
2196
2279
 
2197
- const SECRET_KEY_PATTERN = /(api[_-]?key|apikey|token|secret|password|passwd|authorization|bearer|credential|cookie|private[_-]?key)/i;
2198
- const EXTRA_SECRET_KEYS = /* @__PURE__ */ new Set(["ANTHROPIC_CUSTOM_HEADERS"]);
2199
- function isSecretKey(key) {
2200
- return SECRET_KEY_PATTERN.test(key) || EXTRA_SECRET_KEYS.has(key);
2201
- }
2202
- function maskSecret(value) {
2203
- const v = value.trim();
2204
- if (!v) return "";
2205
- if (v.length < 16) return "\u2022\u2022\u2022\u2022";
2206
- return `${v.slice(0, 4)}\u2026${v.slice(-4)}`;
2207
- }
2208
- function collectSecretValues(value, out = /* @__PURE__ */ new Set(), parentKey = "") {
2209
- if (typeof value === "string") {
2210
- if (parentKey && isSecretKey(parentKey) && value.length >= 4) out.add(value);
2211
- return out;
2212
- }
2213
- if (Array.isArray(value)) {
2214
- for (const item of value) collectSecretValues(item, out, parentKey);
2215
- return out;
2216
- }
2217
- if (value && typeof value === "object") {
2218
- for (const [key, child] of Object.entries(value)) {
2219
- collectSecretValues(child, out, key);
2220
- }
2221
- }
2222
- return out;
2223
- }
2224
- function redactText(text, secrets) {
2225
- const list = [...new Set(secrets)].filter((s) => s.length >= 4).sort((a, b) => b.length - a.length);
2226
- let out = text;
2227
- for (const secret of list) {
2228
- out = out.split(secret).join(maskSecret(secret));
2229
- }
2230
- return out;
2231
- }
2232
- const ENV_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]{0,127}$/;
2233
- function isValidEnvName(name) {
2234
- return typeof name === "string" && ENV_NAME_PATTERN.test(name);
2235
- }
2236
-
2237
- function parseJsonObject$1(text, label) {
2238
- if (text.trim() === "") return {};
2239
- let value;
2240
- try {
2241
- value = JSON.parse(text);
2242
- } catch (error) {
2243
- const hasComments = /^\s*\/\/|\/\*/m.test(text);
2244
- throw new mirrorProtocol.ModelConfigError(
2245
- hasComments ? "json-has-comments" : "invalid-json",
2246
- 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)}`
2247
- );
2248
- }
2249
- if (!value || typeof value !== "object" || Array.isArray(value)) {
2250
- throw new mirrorProtocol.ModelConfigError("invalid-json", `${label} must contain a JSON object`);
2251
- }
2252
- return value;
2253
- }
2254
- function detectIndent(text) {
2255
- const m = /^([ \t]+)"/m.exec(text);
2256
- return m ? m[1] : 2;
2257
- }
2258
- function stringifyLike(original, value) {
2259
- const compact = original.trim() !== "" && !original.trim().includes("\n");
2260
- const body = compact ? JSON.stringify(value) : JSON.stringify(value, null, detectIndent(original));
2261
- const eol = original.includes("\r\n") ? "\r\n" : "\n";
2262
- const withEol = eol === "\r\n" ? body.replace(/\n/g, "\r\n") : body;
2263
- return original === "" || /\r?\n$/.test(original) ? withEol + eol : withEol;
2264
- }
2265
- function setOrDelete(obj, key, value) {
2266
- if (value === void 0 || value === null || value === "") delete obj[key];
2267
- else obj[key] = value;
2268
- }
2269
- function childObject(obj, key, label) {
2270
- const current = obj[key];
2271
- if (current === void 0) {
2272
- const created = {};
2273
- obj[key] = created;
2274
- return created;
2275
- }
2276
- if (!current || typeof current !== "object" || Array.isArray(current)) {
2277
- throw new mirrorProtocol.ModelConfigError("unexpected-shape", `${label}.${key} is not an object; refusing to overwrite it`);
2278
- }
2279
- return current;
2280
- }
2281
-
2282
- const MODEL_ID = /^[A-Za-z0-9._:/@+\-[\]]{1,200}$/;
2283
- const PROVIDER_ID = /^[A-Za-z0-9_-]{1,64}$/;
2284
- function fail(code, message) {
2285
- throw new mirrorProtocol.ModelConfigError(code, message);
2286
- }
2287
- function normalizeBaseUrl(value, field = "baseUrl") {
2288
- if (value === void 0) return void 0;
2289
- if (value === null) return null;
2290
- if (typeof value !== "string") fail("invalid-input", `${field} must be a string`);
2291
- const v = value.trim();
2292
- if (v === "") return null;
2293
- if (v.length > 2048 || /\s/.test(v)) fail("invalid-input", `${field} is not a valid URL`);
2294
- let url;
2295
- try {
2296
- url = new URL(v);
2297
- } catch {
2298
- fail("invalid-input", `${field} is not a valid URL`);
2299
- }
2300
- if (url.protocol !== "http:" && url.protocol !== "https:") fail("invalid-input", `${field} must be http(s)`);
2301
- if (url.username || url.password) fail("invalid-input", `${field} must not embed credentials; use the API key field`);
2302
- return v;
2303
- }
2304
- function normalizeModelId(value, field = "model") {
2305
- if (value === void 0) return void 0;
2306
- if (value === null) return null;
2307
- if (typeof value !== "string") fail("invalid-input", `${field} must be a string`);
2308
- const v = value.trim();
2309
- if (v === "") return null;
2310
- if (!MODEL_ID.test(v)) fail("invalid-input", `${field} contains unsupported characters`);
2311
- return v;
2312
- }
2313
- function normalizeModelList(value, field = "models") {
2314
- if (value === void 0) return void 0;
2315
- if (!Array.isArray(value)) fail("invalid-input", `${field} must be an array`);
2316
- if (value.length > 64) fail("invalid-input", `${field} has too many entries`);
2317
- const out = [];
2318
- for (const item of value) {
2319
- const id = normalizeModelId(item, field);
2320
- if (id && !out.includes(id)) out.push(id);
2321
- }
2322
- return out;
2323
- }
2324
- function normalizeProviderId(value, reserved = []) {
2325
- if (typeof value !== "string" || !PROVIDER_ID.test(value.trim())) fail("invalid-input", "providerId must be 1-64 letters, digits, - or _");
2326
- const v = value.trim();
2327
- if (reserved.includes(v)) fail("invalid-input", `providerId "${v}" is reserved by the harness`);
2328
- return v;
2329
- }
2330
- function normalizeSecret(value, allowed = ["keep", "clear", "literal", "env"]) {
2331
- if (value === void 0) return { mode: "keep" };
2332
- if (!value || typeof value !== "object") fail("invalid-input", "apiKey must be an object");
2333
- const v = value;
2334
- const mode = v.mode;
2335
- if (typeof mode !== "string" || !allowed.includes(mode)) fail("invalid-input", `apiKey.mode must be one of ${allowed.join(", ")}`);
2336
- if (mode === "literal") {
2337
- if (typeof v.value !== "string") fail("invalid-input", "apiKey.value must be a string");
2338
- const key = v.value.trim();
2339
- if (!key || key.length > 4096 || /[\s\u0000-\u001f\u007f]/.test(key)) fail("invalid-input", "apiKey.value must be a single line without spaces");
2340
- return { mode: "literal", value: key };
2341
- }
2342
- if (mode === "env") {
2343
- if (!isValidEnvName(v.name)) fail("invalid-input", "apiKey.name must be an environment variable name");
2344
- return { mode: "env", name: v.name };
2345
- }
2346
- return { mode };
2347
- }
2348
- function baseUrlWarnings(url, kind) {
2349
- if (!url) return [];
2350
- const path = (() => {
2351
- try {
2352
- return new URL(url).pathname.replace(/\/+$/, "");
2353
- } catch {
2354
- return "";
2355
- }
2356
- })();
2357
- const out = [];
2358
- if (/\/(chat\/completions|responses|messages|completions)$/.test(path)) {
2359
- out.push({ code: "base-url-has-endpoint", message: "The base URL ends with an API endpoint path; harnesses append that themselves." });
2360
- }
2361
- if (kind === "anthropic" && /\/v1$/.test(path)) {
2362
- out.push({ code: "base-url-v1", message: "Claude Code appends /v1/messages; a base URL ending in /v1 usually produces /v1/v1/messages." });
2363
- }
2364
- if (kind === "openai" && !/\/v\d/.test(path)) {
2365
- out.push({ code: "base-url-no-version", message: "OpenAI-compatible base URLs usually end in /v1." });
2366
- }
2367
- return out;
2368
- }
2369
-
2370
- function getPath$1(obj, path) {
2371
- let cur = obj;
2372
- for (const p of path) {
2373
- if (!cur || typeof cur !== "object" || Array.isArray(cur)) return void 0;
2374
- cur = cur[p];
2375
- }
2376
- return cur;
2377
- }
2378
- function changedPaths(before, after, paths) {
2379
- return paths.filter((path) => JSON.stringify(getPath$1(before, path)) !== JSON.stringify(getPath$1(after, path))).map((path) => path.join("."));
2380
- }
2381
- function uniq(values) {
2382
- const out = [];
2383
- for (const v of values) if (v && !out.includes(v)) out.push(v);
2384
- return out;
2385
- }
2386
-
2387
2280
  const ROLE$1 = "settings";
2388
2281
  const AUTH_TOKEN = "ANTHROPIC_AUTH_TOKEN";
2389
2282
  const API_KEY$1 = "ANTHROPIC_API_KEY";
@@ -2408,7 +2301,7 @@ function claudeSettingsPath(ctx) {
2408
2301
  const dir = ctx.env.CLAUDE_CONFIG_DIR?.trim() || path.join(ctx.home, ".claude");
2409
2302
  return path.join(dir, "settings.json");
2410
2303
  }
2411
- function str$4(v) {
2304
+ function str$3(v) {
2412
2305
  return typeof v === "string" && v.trim() ? v : null;
2413
2306
  }
2414
2307
  function envOf(settings) {
@@ -2421,11 +2314,11 @@ function pickerOptions(settings) {
2421
2314
  const options = picker.options;
2422
2315
  return Array.isArray(options) ? options.filter((o) => !!o && typeof o === "object" && typeof o.model === "string") : [];
2423
2316
  }
2424
- function secretView$2(settings, ctx) {
2317
+ function secretView$1(settings, ctx) {
2425
2318
  const env = envOf(settings);
2426
- const literal = str$4(env[AUTH_TOKEN]) ?? str$4(env[API_KEY$1]);
2427
- if (literal) return { mode: "literal", masked: maskSecret(literal) };
2428
- const helper = str$4(settings.apiKeyHelper);
2319
+ const literal = str$3(env[AUTH_TOKEN]) ?? str$3(env[API_KEY$1]);
2320
+ if (literal) return { mode: "literal", masked: pi.maskSecret(literal) };
2321
+ const helper = str$3(settings.apiKeyHelper);
2429
2322
  if (helper) {
2430
2323
  const ours = OUR_HELPER.exec(helper.trim());
2431
2324
  if (ours) return { mode: "env", envName: ours[1], envPresent: !!ctx.env[ours[1]] };
@@ -2433,26 +2326,26 @@ function secretView$2(settings, ctx) {
2433
2326
  }
2434
2327
  return { mode: "none" };
2435
2328
  }
2436
- function currentOf$3(settings, ctx) {
2329
+ function currentOf$2(settings, ctx) {
2437
2330
  const env = envOf(settings);
2438
2331
  return {
2439
- baseUrl: str$4(env.ANTHROPIC_BASE_URL),
2440
- authHeader: !str$4(env[AUTH_TOKEN]) && str$4(env[API_KEY$1]) ? "x-api-key" : "bearer",
2441
- apiKey: secretView$2(settings, ctx),
2442
- model: str$4(settings.model),
2443
- opusModel: str$4(env[ALIAS_VARS.opusModel]),
2444
- sonnetModel: str$4(env[ALIAS_VARS.sonnetModel]),
2445
- haikuModel: str$4(env[ALIAS_VARS.haikuModel]),
2332
+ baseUrl: str$3(env.ANTHROPIC_BASE_URL),
2333
+ authHeader: !str$3(env[AUTH_TOKEN]) && str$3(env[API_KEY$1]) ? "x-api-key" : "bearer",
2334
+ apiKey: secretView$1(settings, ctx),
2335
+ model: str$3(settings.model),
2336
+ opusModel: str$3(env[ALIAS_VARS.opusModel]),
2337
+ sonnetModel: str$3(env[ALIAS_VARS.sonnetModel]),
2338
+ haikuModel: str$3(env[ALIAS_VARS.haikuModel]),
2446
2339
  pickerModels: pickerOptions(settings).map((o) => o.model)
2447
2340
  };
2448
2341
  }
2449
- function pickerModelsOf$1(current) {
2450
- return uniq([current.model, current.opusModel, current.sonnetModel, current.haikuModel, ...current.pickerModels ?? []]);
2342
+ function pickerModelsOf(current) {
2343
+ return pi.uniq([current.model, current.opusModel, current.sonnetModel, current.haikuModel, ...current.pickerModels ?? []]);
2451
2344
  }
2452
- function warningsOf$3(settings, current, ctx) {
2345
+ function warningsOf$2(settings, current, ctx) {
2453
2346
  const env = envOf(settings);
2454
- const out = [...baseUrlWarnings(current.baseUrl, "anthropic")];
2455
- if (str$4(env.ANTHROPIC_MODEL)) {
2347
+ const out = [...pi.baseUrlWarnings(current.baseUrl, "anthropic")];
2348
+ if (str$3(env.ANTHROPIC_MODEL)) {
2456
2349
  out.push({ code: "env-model-overrides", message: "env.ANTHROPIC_MODEL is set in settings.json and overrides the default model." });
2457
2350
  }
2458
2351
  const daemonOther = current.authHeader === "bearer" ? API_KEY$1 : AUTH_TOKEN;
@@ -2464,24 +2357,24 @@ function warningsOf$3(settings, current, ctx) {
2464
2357
  }
2465
2358
  if (Array.isArray(settings.availableModels)) {
2466
2359
  const allowed = settings.availableModels;
2467
- const missing = pickerModelsOf$1(current).filter((id) => !allowed.includes(id));
2360
+ const missing = pickerModelsOf(current).filter((id) => !allowed.includes(id));
2468
2361
  if (missing.length) out.push({ code: "available-models-filter", message: `availableModels does not list ${missing.join(", ")}; Claude Code hides them.` });
2469
2362
  }
2470
2363
  return out;
2471
2364
  }
2472
2365
  function parsePatch$1(raw) {
2473
2366
  const p = raw;
2474
- if (p.authHeader !== void 0 && p.authHeader !== "bearer" && p.authHeader !== "x-api-key") fail("invalid-input", "authHeader must be bearer or x-api-key");
2367
+ if (p.authHeader !== void 0 && p.authHeader !== "bearer" && p.authHeader !== "x-api-key") pi.fail("invalid-input", "authHeader must be bearer or x-api-key");
2475
2368
  return {
2476
2369
  harness: "claude",
2477
- baseUrl: normalizeBaseUrl(p.baseUrl),
2370
+ baseUrl: pi.normalizeBaseUrl(p.baseUrl),
2478
2371
  authHeader: p.authHeader,
2479
- apiKey: normalizeSecret(p.apiKey),
2480
- model: normalizeModelId(p.model),
2481
- opusModel: normalizeModelId(p.opusModel, "opusModel"),
2482
- sonnetModel: normalizeModelId(p.sonnetModel, "sonnetModel"),
2483
- haikuModel: normalizeModelId(p.haikuModel, "haikuModel"),
2484
- pickerModels: normalizeModelList(p.pickerModels, "pickerModels")
2372
+ apiKey: pi.normalizeSecret(p.apiKey),
2373
+ model: pi.normalizeModelId(p.model),
2374
+ opusModel: pi.normalizeModelId(p.opusModel, "opusModel"),
2375
+ sonnetModel: pi.normalizeModelId(p.sonnetModel, "sonnetModel"),
2376
+ haikuModel: pi.normalizeModelId(p.haikuModel, "haikuModel"),
2377
+ pickerModels: pi.normalizeModelList(p.pickerModels, "pickerModels")
2485
2378
  };
2486
2379
  }
2487
2380
  const claudeAdapter = {
@@ -2489,19 +2382,19 @@ const claudeAdapter = {
2489
2382
  binary: "claude",
2490
2383
  files: (ctx) => [{ role: ROLE$1, path: claudeSettingsPath(ctx) }],
2491
2384
  view(files, ctx) {
2492
- const settings = parseJsonObject$1(files[ROLE$1].content, "settings.json");
2493
- const current = currentOf$3(settings, ctx);
2494
- return { current, pickerModels: pickerModelsOf$1(current), warnings: warningsOf$3(settings, current, ctx) };
2385
+ const settings = pi.parseJsonObject(files[ROLE$1].content, "settings.json");
2386
+ const current = currentOf$2(settings, ctx);
2387
+ return { current, pickerModels: pickerModelsOf(current), warnings: warningsOf$2(settings, current, ctx) };
2495
2388
  },
2496
2389
  plan(files, raw, ctx) {
2497
2390
  const patch = parsePatch$1(raw);
2498
2391
  const original = files[ROLE$1].content;
2499
- const before = parseJsonObject$1(original, "settings.json");
2392
+ const before = pi.parseJsonObject(original, "settings.json");
2500
2393
  const settings = structuredClone(before);
2501
2394
  const hadEnv = settings.env !== void 0;
2502
- const env = childObject(settings, "env", "settings");
2503
- if (patch.baseUrl !== void 0) setOrDelete(env, "ANTHROPIC_BASE_URL", patch.baseUrl);
2504
- const currentHeader = !str$4(env[AUTH_TOKEN]) && str$4(env[API_KEY$1]) ? "x-api-key" : "bearer";
2395
+ const env = pi.childObject(settings, "env", "settings");
2396
+ if (patch.baseUrl !== void 0) pi.setOrDelete(env, "ANTHROPIC_BASE_URL", patch.baseUrl);
2397
+ const currentHeader = !str$3(env[AUTH_TOKEN]) && str$3(env[API_KEY$1]) ? "x-api-key" : "bearer";
2505
2398
  const header = patch.authHeader ?? currentHeader;
2506
2399
  const target = header === "bearer" ? AUTH_TOKEN : API_KEY$1;
2507
2400
  const other = header === "bearer" ? API_KEY$1 : AUTH_TOKEN;
@@ -2525,19 +2418,19 @@ const claudeAdapter = {
2525
2418
  if (helperIsOurs) delete settings.apiKeyHelper;
2526
2419
  break;
2527
2420
  case "keep":
2528
- if (header !== currentHeader && str$4(env[other]) && !str$4(env[target])) {
2421
+ if (header !== currentHeader && str$3(env[other]) && !str$3(env[target])) {
2529
2422
  env[target] = env[other];
2530
2423
  delete env[other];
2531
2424
  }
2532
2425
  break;
2533
2426
  }
2534
- if (patch.model !== void 0) setOrDelete(settings, "model", patch.model);
2427
+ if (patch.model !== void 0) pi.setOrDelete(settings, "model", patch.model);
2535
2428
  for (const [field, variable] of Object.entries(ALIAS_VARS)) {
2536
- if (patch[field] !== void 0) setOrDelete(env, variable, patch[field]);
2429
+ if (patch[field] !== void 0) pi.setOrDelete(env, variable, patch[field]);
2537
2430
  }
2538
2431
  if (patch.pickerModels !== void 0) {
2539
2432
  const hadPicker = settings.modelPicker !== void 0;
2540
- const picker = childObject(settings, "modelPicker", "settings");
2433
+ const picker = pi.childObject(settings, "modelPicker", "settings");
2541
2434
  const existing = pickerOptions(settings);
2542
2435
  const next = patch.pickerModels.map((id) => existing.find((o) => o.model === id) ?? { model: id });
2543
2436
  if (next.length) picker.options = next;
@@ -2545,19 +2438,19 @@ const claudeAdapter = {
2545
2438
  if (!hadPicker && Object.keys(picker).length === 0) delete settings.modelPicker;
2546
2439
  }
2547
2440
  if (!hadEnv && Object.keys(env).length === 0) delete settings.env;
2548
- const current = currentOf$3(settings, ctx);
2441
+ const current = currentOf$2(settings, ctx);
2549
2442
  if (Array.isArray(settings.availableModels)) {
2550
2443
  const allowed = settings.availableModels;
2551
- const add = pickerModelsOf$1(current).filter((id) => !allowed.includes(id));
2444
+ const add = pickerModelsOf(current).filter((id) => !allowed.includes(id));
2552
2445
  if (add.length) settings.availableModels = [...allowed, ...add];
2553
2446
  }
2554
- const changedKeys = changedPaths(before, settings, OWNED_PATHS);
2555
- const content = changedKeys.length === 0 ? void 0 : stringifyLike(original, settings);
2556
- return { contents: { [ROLE$1]: content }, warnings: warningsOf$3(settings, current, ctx), changedKeys, secrets };
2447
+ const changedKeys = pi.changedPaths(before, settings, OWNED_PATHS);
2448
+ const content = changedKeys.length === 0 ? void 0 : pi.stringifyLike(original, settings);
2449
+ return { contents: { [ROLE$1]: content }, warnings: warningsOf$2(settings, current, ctx), changedKeys, secrets };
2557
2450
  },
2558
2451
  secretsIn(_role, content) {
2559
2452
  try {
2560
- return collectSecretValues(parseJsonObject$1(content, "settings.json"));
2453
+ return pi.collectSecretValues(pi.parseJsonObject(content, "settings.json"));
2561
2454
  } catch {
2562
2455
  return /* @__PURE__ */ new Set();
2563
2456
  }
@@ -3531,7 +3424,7 @@ function parseToml(text) {
3531
3424
  return parse(text);
3532
3425
  } catch (error) {
3533
3426
  const message = error instanceof Error ? error.message.split("\n")[0] : String(error);
3534
- throw new mirrorProtocol.ModelConfigError("invalid-toml", `Not valid TOML: ${message}`);
3427
+ throw new fileStore.ModelConfigError("invalid-toml", `Not valid TOML: ${message}`);
3535
3428
  }
3536
3429
  }
3537
3430
  function getPath(obj, path) {
@@ -3581,20 +3474,20 @@ function verifyTomlEdit(oldText, newText, edits) {
3581
3474
  try {
3582
3475
  after = parse(newText);
3583
3476
  } catch (error) {
3584
- throw new mirrorProtocol.ModelConfigError("unsafe-edit", `Edit would produce invalid TOML (${error instanceof Error ? error.message.split("\n")[0] : String(error)}); the file layout is not one this editor can change safely`);
3477
+ throw new fileStore.ModelConfigError("unsafe-edit", `Edit would produce invalid TOML (${error instanceof Error ? error.message.split("\n")[0] : String(error)}); the file layout is not one this editor can change safely`);
3585
3478
  }
3586
3479
  const owned = edits.map((e) => "removeTable" in e ? e.table : [...e.table, e.key]);
3587
3480
  for (const edit of edits) {
3588
3481
  if ("removeTable" in edit) {
3589
- if (getPath(after, edit.table) !== void 0) throw new mirrorProtocol.ModelConfigError("unsafe-edit", `Table ${edit.table.join(".")} is still present after removal`);
3482
+ if (getPath(after, edit.table) !== void 0) throw new fileStore.ModelConfigError("unsafe-edit", `Table ${edit.table.join(".")} is still present after removal`);
3590
3483
  continue;
3591
3484
  }
3592
3485
  const got = getPath(after, [...edit.table, edit.key]);
3593
3486
  const want = edit.value === null ? void 0 : edit.value;
3594
- if (got !== want) throw new mirrorProtocol.ModelConfigError("unsafe-edit", `Key ${[...edit.table, edit.key].join(".")} did not take the requested value`);
3487
+ if (got !== want) throw new fileStore.ModelConfigError("unsafe-edit", `Key ${[...edit.table, edit.key].join(".")} did not take the requested value`);
3595
3488
  }
3596
3489
  if (stableStringify(pruneEmpty(withoutPaths(before, owned))) !== stableStringify(pruneEmpty(withoutPaths(after, owned)))) {
3597
- throw new mirrorProtocol.ModelConfigError("unsafe-edit", "Edit would change settings outside the keys Very Happy manages");
3490
+ throw new fileStore.ModelConfigError("unsafe-edit", "Edit would change settings outside the keys Very Happy manages");
3598
3491
  }
3599
3492
  }
3600
3493
  function editToml(text, edits) {
@@ -3613,68 +3506,68 @@ function codexConfigPath(ctx) {
3613
3506
  const dir = ctx.env.CODEX_HOME?.trim() || path.join(ctx.home, ".codex");
3614
3507
  return path.join(dir, "config.toml");
3615
3508
  }
3616
- const str$3 = (v) => typeof v === "string" && v.trim() ? v : null;
3617
- function providersOf$1(doc) {
3509
+ const str$2 = (v) => typeof v === "string" && v.trim() ? v : null;
3510
+ function providersOf(doc) {
3618
3511
  const table = doc.model_providers;
3619
3512
  if (!table || typeof table !== "object" || Array.isArray(table)) return {};
3620
3513
  return Object.fromEntries(Object.entries(table).filter(([, v]) => v && typeof v === "object" && !Array.isArray(v)));
3621
3514
  }
3622
- function secretView$1(provider, ctx) {
3515
+ function secretView(provider, ctx) {
3623
3516
  if (!provider) return { mode: "none" };
3624
- const token = str$3(provider.experimental_bearer_token);
3625
- if (token) return { mode: "literal", masked: maskSecret(token) };
3626
- const envKey = str$3(provider.env_key);
3517
+ const token = str$2(provider.experimental_bearer_token);
3518
+ if (token) return { mode: "literal", masked: pi.maskSecret(token) };
3519
+ const envKey = str$2(provider.env_key);
3627
3520
  if (envKey) return { mode: "env", envName: envKey, envPresent: !!ctx.env[envKey] };
3628
3521
  if (provider.auth && typeof provider.auth === "object") return { mode: "command" };
3629
3522
  return { mode: "none" };
3630
3523
  }
3631
3524
  function selectProvider(doc, requested) {
3632
- const providers = providersOf$1(doc);
3525
+ const providers = providersOf(doc);
3633
3526
  if (requested) return requested;
3634
- const active = str$3(doc.model_provider);
3527
+ const active = str$2(doc.model_provider);
3635
3528
  if (active && providers[active]) return active;
3636
3529
  return Object.keys(providers)[0] ?? null;
3637
3530
  }
3638
- function currentOf$2(doc, providerId, ctx) {
3639
- const provider = providerId ? providersOf$1(doc)[providerId] : void 0;
3640
- const activeProvider = str$3(doc.model_provider);
3531
+ function currentOf$1(doc, providerId, ctx) {
3532
+ const provider = providerId ? providersOf(doc)[providerId] : void 0;
3533
+ const activeProvider = str$2(doc.model_provider);
3641
3534
  return {
3642
3535
  providerId,
3643
- providerName: str$3(provider?.name),
3644
- baseUrl: str$3(provider?.base_url),
3645
- apiKey: secretView$1(provider, ctx),
3646
- model: str$3(doc.model),
3536
+ providerName: str$2(provider?.name),
3537
+ baseUrl: str$2(provider?.base_url),
3538
+ apiKey: secretView(provider, ctx),
3539
+ model: str$2(doc.model),
3647
3540
  activeProvider,
3648
3541
  active: !!providerId && activeProvider === providerId
3649
3542
  };
3650
3543
  }
3651
- function warningsOf$2(doc, current) {
3652
- const out = [...baseUrlWarnings(current.baseUrl, "openai")];
3653
- const provider = current.providerId ? providersOf$1(doc)[current.providerId] : void 0;
3654
- if (provider && str$3(provider.wire_api) && provider.wire_api !== "responses") {
3544
+ function warningsOf$1(doc, current) {
3545
+ const out = [...pi.baseUrlWarnings(current.baseUrl, "openai")];
3546
+ const provider = current.providerId ? providersOf(doc)[current.providerId] : void 0;
3547
+ if (provider && str$2(provider.wire_api) && provider.wire_api !== "responses") {
3655
3548
  out.push({ code: "wire-api-unsupported", message: `wire_api = "${String(provider.wire_api)}" is rejected by current Codex; only "responses" is supported.` });
3656
3549
  }
3657
3550
  if (current.apiKey?.mode === "env" && !current.apiKey.envPresent) {
3658
3551
  out.push({ code: "env-missing", message: `${current.apiKey.envName} is not set in the daemon's environment, so sessions will not get a key.` });
3659
3552
  }
3660
3553
  const active = current.activeProvider;
3661
- if (active && !CODEX_RESERVED_PROVIDERS.includes(active) && !providersOf$1(doc)[active]) {
3554
+ if (active && !CODEX_RESERVED_PROVIDERS.includes(active) && !providersOf(doc)[active]) {
3662
3555
  out.push({ code: "active-provider-missing", message: `model_provider = "${active}" has no [model_providers.${active}] table.` });
3663
3556
  }
3664
3557
  return out;
3665
3558
  }
3666
3559
  function parsePatch(raw) {
3667
3560
  const p = raw;
3668
- if (p.activate !== void 0 && typeof p.activate !== "boolean") fail("invalid-input", "activate must be a boolean");
3561
+ if (p.activate !== void 0 && typeof p.activate !== "boolean") pi.fail("invalid-input", "activate must be a boolean");
3669
3562
  if (p.providerName !== void 0 && (typeof p.providerName !== "string" || p.providerName.length > 120 || /[\u0000-\u001f]/.test(p.providerName))) {
3670
- fail("invalid-input", "providerName must be a short single-line string");
3563
+ pi.fail("invalid-input", "providerName must be a short single-line string");
3671
3564
  }
3672
3565
  return {
3673
- providerId: normalizeProviderId(p.providerId, CODEX_RESERVED_PROVIDERS),
3566
+ providerId: pi.normalizeProviderId(p.providerId, CODEX_RESERVED_PROVIDERS),
3674
3567
  providerName: p.providerName?.trim(),
3675
- baseUrl: normalizeBaseUrl(p.baseUrl),
3676
- apiKey: normalizeSecret(p.apiKey),
3677
- model: normalizeModelId(p.model),
3568
+ baseUrl: pi.normalizeBaseUrl(p.baseUrl),
3569
+ apiKey: pi.normalizeSecret(p.apiKey),
3570
+ model: pi.normalizeModelId(p.model),
3678
3571
  activate: p.activate
3679
3572
  };
3680
3573
  }
@@ -3684,25 +3577,25 @@ const codexAdapter = {
3684
3577
  files: (ctx) => [{ role: ROLE, path: codexConfigPath(ctx) }],
3685
3578
  view(files, ctx, opts) {
3686
3579
  const doc = parseToml(files[ROLE].content);
3687
- const current = currentOf$2(doc, selectProvider(doc, opts.providerId), ctx);
3688
- return { current, providers: Object.keys(providersOf$1(doc)), pickerModels: uniq([current.model]), warnings: warningsOf$2(doc, current) };
3580
+ const current = currentOf$1(doc, selectProvider(doc, opts.providerId), ctx);
3581
+ return { current, providers: Object.keys(providersOf(doc)), pickerModels: pi.uniq([current.model]), warnings: warningsOf$1(doc, current) };
3689
3582
  },
3690
3583
  plan(files, raw, ctx) {
3691
3584
  const patch = parsePatch(raw);
3692
3585
  const original = files[ROLE].content;
3693
3586
  const before = parseToml(original);
3694
- const existing = providersOf$1(before)[patch.providerId];
3587
+ const existing = providersOf(before)[patch.providerId];
3695
3588
  const table = ["model_providers", patch.providerId];
3696
3589
  const edits = [];
3697
3590
  const secrets = [];
3698
- if (!existing && !patch.baseUrl) fail("invalid-input", "A new provider needs a base URL");
3699
- const name = patch.providerName || str$3(existing?.name) || patch.providerId;
3700
- if (!existing || patch.providerName && patch.providerName !== existing.name || !str$3(existing.name)) edits.push({ table, key: "name", value: name });
3591
+ if (!existing && !patch.baseUrl) pi.fail("invalid-input", "A new provider needs a base URL");
3592
+ const name = patch.providerName || str$2(existing?.name) || patch.providerId;
3593
+ if (!existing || patch.providerName && patch.providerName !== existing.name || !str$2(existing.name)) edits.push({ table, key: "name", value: name });
3701
3594
  if (patch.baseUrl !== void 0) {
3702
- if (patch.baseUrl === null && existing) fail("invalid-input", "A provider needs a base URL; remove the provider instead");
3595
+ if (patch.baseUrl === null && existing) pi.fail("invalid-input", "A provider needs a base URL; remove the provider instead");
3703
3596
  edits.push({ table, key: "base_url", value: patch.baseUrl });
3704
3597
  }
3705
- if (!existing || str$3(existing.wire_api) && existing.wire_api !== "responses") edits.push({ table, key: "wire_api", value: "responses" });
3598
+ if (!existing || str$2(existing.wire_api) && existing.wire_api !== "responses") edits.push({ table, key: "wire_api", value: "responses" });
3706
3599
  switch (patch.apiKey.mode) {
3707
3600
  case "literal":
3708
3601
  edits.push({ table, key: "experimental_bearer_token", value: patch.apiKey.value }, { table, key: "env_key", value: null });
@@ -3717,19 +3610,19 @@ const codexAdapter = {
3717
3610
  }
3718
3611
  if (patch.model !== void 0) edits.push({ table: [], key: "model", value: patch.model });
3719
3612
  if (patch.activate === true) edits.push({ table: [], key: "model_provider", value: patch.providerId });
3720
- if (patch.activate === false && str$3(before.model_provider) === patch.providerId) edits.push({ table: [], key: "model_provider", value: null });
3721
- const effective = edits.filter((e) => "removeTable" in e || getPath$1(before, [...e.table, e.key]) !== (e.value ?? void 0));
3613
+ if (patch.activate === false && str$2(before.model_provider) === patch.providerId) edits.push({ table: [], key: "model_provider", value: null });
3614
+ const effective = edits.filter((e) => "removeTable" in e || pi.getPath(before, [...e.table, e.key]) !== (e.value ?? void 0));
3722
3615
  const content = effective.length ? editToml(original, effective) : void 0;
3723
3616
  const after = content === void 0 ? before : parseToml(content);
3724
- const current = currentOf$2(after, patch.providerId, ctx);
3617
+ const current = currentOf$1(after, patch.providerId, ctx);
3725
3618
  const changedKeys = effective.map((e) => [...e.table, "removeTable" in e ? "*" : e.key].join("."));
3726
- return { contents: { [ROLE]: content }, warnings: warningsOf$2(after, current), changedKeys, secrets };
3619
+ return { contents: { [ROLE]: content }, warnings: warningsOf$1(after, current), changedKeys, secrets };
3727
3620
  },
3728
3621
  secretsIn(_role, content) {
3729
3622
  try {
3730
- return collectSecretValues(parseToml(content));
3623
+ return pi.collectSecretValues(parseToml(content));
3731
3624
  } catch (error) {
3732
- if (error instanceof mirrorProtocol.ModelConfigError) return /* @__PURE__ */ new Set();
3625
+ if (error instanceof fileStore.ModelConfigError) return /* @__PURE__ */ new Set();
3733
3626
  throw error;
3734
3627
  }
3735
3628
  }
@@ -4065,35 +3958,35 @@ function editDotenv(text, edits) {
4065
3958
  const before = mainExports.parse(text);
4066
3959
  const after = mainExports.parse(out);
4067
3960
  for (const edit of edits) {
4068
- if ((after[edit.key] ?? null) !== edit.value) throw new mirrorProtocol.ModelConfigError("unsafe-edit", `.env edit did not produce ${edit.key} as requested`);
3961
+ if ((after[edit.key] ?? null) !== edit.value) throw new fileStore.ModelConfigError("unsafe-edit", `.env edit did not produce ${edit.key} as requested`);
4069
3962
  delete before[edit.key];
4070
3963
  delete after[edit.key];
4071
3964
  }
4072
3965
  if (JSON.stringify(Object.entries(before).sort()) !== JSON.stringify(Object.entries(after).sort())) {
4073
- throw new mirrorProtocol.ModelConfigError("unsafe-edit", ".env edit would change other variables");
3966
+ throw new fileStore.ModelConfigError("unsafe-edit", ".env edit would change other variables");
4074
3967
  }
4075
3968
  return out;
4076
3969
  }
4077
3970
 
4078
- const SETTINGS$1 = "settings";
3971
+ const SETTINGS = "settings";
4079
3972
  const ENV = "env";
4080
3973
  const BASE_URL = "GOOGLE_GEMINI_BASE_URL";
4081
3974
  const API_KEY = "GEMINI_API_KEY";
4082
3975
  function geminiDir(ctx) {
4083
3976
  return path.join(ctx.env.GEMINI_CLI_HOME?.trim() || ctx.home, ".gemini");
4084
3977
  }
4085
- const str$2 = (v) => typeof v === "string" && v.trim() ? v : null;
4086
- function currentOf$1(settings, env, ctx) {
4087
- const literal = str$2(env[API_KEY]);
4088
- const apiKey = literal ? { mode: "literal", masked: maskSecret(literal) } : ctx.env[API_KEY] ? { mode: "env", envName: API_KEY, envPresent: true } : { mode: "none" };
4089
- return { baseUrl: str$2(env[BASE_URL]), apiKey, model: str$2(getPath$1(settings, ["model", "name"])) };
3978
+ const str$1 = (v) => typeof v === "string" && v.trim() ? v : null;
3979
+ function currentOf(settings, env, ctx) {
3980
+ const literal = str$1(env[API_KEY]);
3981
+ const apiKey = literal ? { mode: "literal", masked: pi.maskSecret(literal) } : ctx.env[API_KEY] ? { mode: "env", envName: API_KEY, envPresent: true } : { mode: "none" };
3982
+ return { baseUrl: str$1(env[BASE_URL]), apiKey, model: str$1(pi.getPath(settings, ["model", "name"])) };
4090
3983
  }
4091
- function warningsOf$1(settings, current) {
4092
- const out = [...baseUrlWarnings(current.baseUrl, "gemini")];
3984
+ function warningsOf(settings, current) {
3985
+ const out = [...pi.baseUrlWarnings(current.baseUrl, "gemini")];
4093
3986
  if (current.baseUrl || current.apiKey?.mode === "literal") {
4094
3987
  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." });
4095
3988
  }
4096
- const selected = str$2(getPath$1(settings, ["security", "auth", "selectedType"]));
3989
+ const selected = str$1(pi.getPath(settings, ["security", "auth", "selectedType"]));
4097
3990
  if (current.baseUrl && selected && selected !== "gateway") {
4098
3991
  out.push({ code: "auth-type-pinned", message: `security.auth.selectedType is "${selected}"; Gemini only uses GOOGLE_GEMINI_BASE_URL with the gateway auth type.` });
4099
3992
  }
@@ -4105,28 +3998,28 @@ function warningsOf$1(settings, current) {
4105
3998
  const geminiAdapter = {
4106
3999
  id: "gemini",
4107
4000
  binary: "gemini",
4108
- files: (ctx) => [{ role: SETTINGS$1, path: path.join(geminiDir(ctx), "settings.json") }, { role: ENV, path: path.join(geminiDir(ctx), ".env") }],
4001
+ files: (ctx) => [{ role: SETTINGS, path: path.join(geminiDir(ctx), "settings.json") }, { role: ENV, path: path.join(geminiDir(ctx), ".env") }],
4109
4002
  view(files, ctx) {
4110
- const settings = parseJsonObject$1(files[SETTINGS$1].content, "settings.json");
4111
- const current = currentOf$1(settings, parseDotenv(files[ENV].content), ctx);
4112
- return { current, pickerModels: uniq([current.model]), warnings: warningsOf$1(settings, current) };
4003
+ const settings = pi.parseJsonObject(files[SETTINGS].content, "settings.json");
4004
+ const current = currentOf(settings, parseDotenv(files[ENV].content), ctx);
4005
+ return { current, pickerModels: pi.uniq([current.model]), warnings: warningsOf(settings, current) };
4113
4006
  },
4114
4007
  plan(files, raw, ctx) {
4115
4008
  const p = raw;
4116
- const baseUrl = normalizeBaseUrl(p.baseUrl);
4117
- const apiKey = normalizeSecret(p.apiKey, ["keep", "clear", "literal"]);
4118
- const model = normalizeModelId(p.model);
4119
- const settingsText = files[SETTINGS$1].content;
4120
- const before = parseJsonObject$1(settingsText, "settings.json");
4009
+ const baseUrl = pi.normalizeBaseUrl(p.baseUrl);
4010
+ const apiKey = pi.normalizeSecret(p.apiKey, ["keep", "clear", "literal"]);
4011
+ const model = pi.normalizeModelId(p.model);
4012
+ const settingsText = files[SETTINGS].content;
4013
+ const before = pi.parseJsonObject(settingsText, "settings.json");
4121
4014
  const settings = structuredClone(before);
4122
4015
  const changedKeys = [];
4123
4016
  let settingsOut;
4124
- if (model !== void 0 && (str$2(getPath$1(before, ["model", "name"])) ?? null) !== model) {
4017
+ if (model !== void 0 && (str$1(pi.getPath(before, ["model", "name"])) ?? null) !== model) {
4125
4018
  const hadModel = settings.model !== void 0;
4126
- const modelObj = childObject(settings, "model", "settings");
4127
- setOrDelete(modelObj, "name", model);
4019
+ const modelObj = pi.childObject(settings, "model", "settings");
4020
+ pi.setOrDelete(modelObj, "name", model);
4128
4021
  if (!hadModel && Object.keys(modelObj).length === 0) delete settings.model;
4129
- settingsOut = stringifyLike(settingsText, settings);
4022
+ settingsOut = pi.stringifyLike(settingsText, settings);
4130
4023
  changedKeys.push("settings.model.name");
4131
4024
  }
4132
4025
  const envText = files[ENV].content;
@@ -4138,160 +4031,17 @@ const geminiAdapter = {
4138
4031
  const envOut = edits.length ? editDotenv(envText, edits) : void 0;
4139
4032
  changedKeys.push(...edits.map((e) => `.env.${e.key}`));
4140
4033
  const afterEnv = envOut === void 0 ? env : parseDotenv(envOut);
4141
- const current = currentOf$1(settings, afterEnv, ctx);
4034
+ const current = currentOf(settings, afterEnv, ctx);
4142
4035
  return {
4143
- contents: { [SETTINGS$1]: settingsOut, [ENV]: envOut },
4144
- warnings: warningsOf$1(settings, current),
4036
+ contents: { [SETTINGS]: settingsOut, [ENV]: envOut },
4037
+ warnings: warningsOf(settings, current),
4145
4038
  changedKeys,
4146
4039
  secrets: apiKey.mode === "literal" ? [apiKey.value] : []
4147
4040
  };
4148
4041
  },
4149
4042
  secretsIn(role, content) {
4150
4043
  try {
4151
- return role === ENV ? collectSecretValues(parseDotenv(content)) : collectSecretValues(parseJsonObject$1(content, "settings.json"));
4152
- } catch {
4153
- return /* @__PURE__ */ new Set();
4154
- }
4155
- }
4156
- };
4157
-
4158
- const MODELS = "models";
4159
- const SETTINGS = "settings";
4160
- const PI_APIS = ["openai-completions", "openai-responses", "anthropic-messages", "google-generative-ai", "mistral-conversations"];
4161
- const ENV_REF = /^\$\{?([A-Za-z_][A-Za-z0-9_]*)\}?$/;
4162
- function piAgentDir(ctx) {
4163
- return ctx.env.PI_CODING_AGENT_DIR?.trim() || path.join(ctx.home, ".pi", "agent");
4164
- }
4165
- const str$1 = (v) => typeof v === "string" && v.trim() ? v : null;
4166
- function providersOf(models) {
4167
- const p = models.providers;
4168
- if (!p || typeof p !== "object" || Array.isArray(p)) return {};
4169
- return Object.fromEntries(Object.entries(p).filter(([, v]) => v && typeof v === "object" && !Array.isArray(v)));
4170
- }
4171
- function modelIds(provider) {
4172
- const list = provider?.models;
4173
- return Array.isArray(list) ? list.flatMap((m) => m && typeof m === "object" && typeof m.id === "string" ? [m.id] : []) : [];
4174
- }
4175
- function secretView(provider, ctx) {
4176
- const key = str$1(provider?.apiKey);
4177
- if (!key) return { mode: "none" };
4178
- const ref = ENV_REF.exec(key.trim());
4179
- if (ref) return { mode: "env", envName: ref[1], envPresent: !!ctx.env[ref[1]] };
4180
- if (key.trim().startsWith("!")) return { mode: "command" };
4181
- return { mode: "literal", masked: maskSecret(key) };
4182
- }
4183
- function currentOf(models, settings, providerId, ctx) {
4184
- const provider = providerId ? providersOf(models)[providerId] : void 0;
4185
- const activeProvider = str$1(settings.defaultProvider);
4186
- return {
4187
- providerId,
4188
- baseUrl: str$1(provider?.baseUrl),
4189
- api: str$1(provider?.api),
4190
- apiKey: secretView(provider, ctx),
4191
- models: modelIds(provider),
4192
- activeProvider,
4193
- defaultModel: str$1(settings.defaultModel),
4194
- active: !!providerId && activeProvider === providerId
4195
- };
4196
- }
4197
- function pickerModelsOf(models) {
4198
- return uniq(Object.entries(providersOf(models)).flatMap(([id, p]) => modelIds(p).map((m) => `${id}/${m}`)));
4199
- }
4200
- function warningsOf(settings, current) {
4201
- const out = [...baseUrlWarnings(current.baseUrl, current.api === "anthropic-messages" ? "anthropic" : "openai")].filter((w) => w.code !== "base-url-v1");
4202
- if (current.apiKey?.mode === "env" && !current.apiKey.envPresent) {
4203
- 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.` });
4204
- }
4205
- if (current.providerId && current.apiKey?.mode === "none") {
4206
- out.push({ code: "no-key", message: "pi hides models whose provider has no resolvable API key." });
4207
- }
4208
- if (Array.isArray(settings.enabledModels) && current.providerId) {
4209
- const enabled = settings.enabledModels;
4210
- const hidden = (current.models ?? []).filter((m) => !enabled.includes(`${current.providerId}/${m}`) && !enabled.includes(m));
4211
- if (hidden.length) out.push({ code: "enabled-models-filter", message: `settings.json enabledModels does not include ${hidden.join(", ")}.` });
4212
- }
4213
- return out;
4214
- }
4215
- const piAdapter = {
4216
- id: "pi",
4217
- binary: "pi",
4218
- files: (ctx) => [{ role: MODELS, path: path.join(piAgentDir(ctx), "models.json") }, { role: SETTINGS, path: path.join(piAgentDir(ctx), "settings.json") }],
4219
- view(files, ctx, opts) {
4220
- const models = parseJsonObject$1(files[MODELS].content, "models.json");
4221
- const settings = parseJsonObject$1(files[SETTINGS].content, "settings.json");
4222
- const providers = providersOf(models);
4223
- const active = str$1(settings.defaultProvider);
4224
- const selected = opts.providerId ?? (active && providers[active] ? active : Object.keys(providers)[0] ?? null);
4225
- const current = currentOf(models, settings, selected, ctx);
4226
- return { current, providers: Object.keys(providers), pickerModels: pickerModelsOf(models), warnings: warningsOf(settings, current) };
4227
- },
4228
- plan(files, raw, ctx) {
4229
- const p = raw;
4230
- const providerId = normalizeProviderId(p.providerId);
4231
- const baseUrl = normalizeBaseUrl(p.baseUrl);
4232
- if (p.api !== void 0 && !PI_APIS.includes(p.api)) fail("invalid-input", `api must be one of ${PI_APIS.join(", ")}`);
4233
- const apiKey = normalizeSecret(p.apiKey);
4234
- 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");
4235
- const ids = normalizeModelList(p.models);
4236
- const defaultModel = normalizeModelId(p.defaultModel, "defaultModel");
4237
- if (p.activate !== void 0 && typeof p.activate !== "boolean") fail("invalid-input", "activate must be a boolean");
4238
- const modelsText = files[MODELS].content;
4239
- const settingsText = files[SETTINGS].content;
4240
- const modelsBefore = parseJsonObject$1(modelsText, "models.json");
4241
- const settingsBefore = parseJsonObject$1(settingsText, "settings.json");
4242
- const models = structuredClone(modelsBefore);
4243
- const settings = structuredClone(settingsBefore);
4244
- const providers = childObject(models, "providers", "models.json");
4245
- const isNew = providers[providerId] === void 0;
4246
- if (isNew && !baseUrl) fail("invalid-input", "A new provider needs a base URL");
4247
- const provider = childObject(providers, providerId, "models.json.providers");
4248
- if (baseUrl !== void 0) {
4249
- if (baseUrl === null) fail("invalid-input", "A provider needs a base URL");
4250
- provider.baseUrl = baseUrl;
4251
- }
4252
- if (p.api !== void 0) provider.api = p.api;
4253
- else if (isNew) provider.api = "openai-completions";
4254
- const secrets = [];
4255
- if (apiKey.mode === "literal") {
4256
- provider.apiKey = apiKey.value;
4257
- secrets.push(apiKey.value);
4258
- }
4259
- if (apiKey.mode === "env") provider.apiKey = `\${${apiKey.name}}`;
4260
- if (apiKey.mode === "clear") delete provider.apiKey;
4261
- if (ids !== void 0) {
4262
- const existing = Array.isArray(provider.models) ? provider.models : [];
4263
- const next = ids.map((id) => existing.find((m) => m && typeof m === "object" && m.id === id) ?? { id });
4264
- if (next.length) provider.models = next;
4265
- else delete provider.models;
4266
- }
4267
- const finalIds = modelIds(provider);
4268
- if (p.activate === true) {
4269
- settings.defaultProvider = providerId;
4270
- const chosen = defaultModel ?? (finalIds.includes(str$1(settings.defaultModel) ?? "") ? str$1(settings.defaultModel) : finalIds[0]);
4271
- setOrDelete(settings, "defaultModel", chosen);
4272
- } else if (p.activate === false && str$1(settings.defaultProvider) === providerId) {
4273
- delete settings.defaultProvider;
4274
- delete settings.defaultModel;
4275
- } else if (defaultModel !== void 0 && str$1(settings.defaultProvider) === providerId) {
4276
- setOrDelete(settings, "defaultModel", defaultModel);
4277
- }
4278
- if (defaultModel && !finalIds.includes(defaultModel)) fail("invalid-input", "defaultModel must be one of the provider models");
4279
- const modelKeys = changedPaths(modelsBefore, models, [["providers", providerId, "baseUrl"], ["providers", providerId, "api"], ["providers", providerId, "apiKey"], ["providers", providerId, "models"]]);
4280
- const settingsKeys = changedPaths(settingsBefore, settings, [["defaultProvider"], ["defaultModel"]]);
4281
- const current = currentOf(models, settings, providerId, ctx);
4282
- return {
4283
- contents: {
4284
- [MODELS]: modelKeys.length ? stringifyLike(modelsText, models) : void 0,
4285
- [SETTINGS]: settingsKeys.length ? stringifyLike(settingsText, settings) : void 0
4286
- },
4287
- warnings: warningsOf(settings, current),
4288
- changedKeys: [...modelKeys.map((k) => `models.json.${k}`), ...settingsKeys.map((k) => `settings.json.${k}`)],
4289
- secrets
4290
- };
4291
- },
4292
- secretsIn(_role, content) {
4293
- try {
4294
- return collectSecretValues(parseJsonObject$1(content, "pi config"));
4044
+ return role === ENV ? pi.collectSecretValues(parseDotenv(content)) : pi.collectSecretValues(pi.parseJsonObject(content, "settings.json"));
4295
4045
  } catch {
4296
4046
  return /* @__PURE__ */ new Set();
4297
4047
  }
@@ -4301,17 +4051,17 @@ const piAdapter = {
4301
4051
  const MODEL_CONFIG_CAPABILITY_VERSION = 1;
4302
4052
  const HARNESS_IDS = ["claude", "codex", "gemini", "pi"];
4303
4053
 
4304
- const ADAPTERS = { claude: claudeAdapter, codex: codexAdapter, gemini: geminiAdapter, pi: piAdapter };
4054
+ const ADAPTERS = { claude: claudeAdapter, codex: codexAdapter, gemini: geminiAdapter, pi: pi.piAdapter };
4305
4055
  function harnessOf(value) {
4306
4056
  if (typeof value !== "string" || !HARNESS_IDS.includes(value)) {
4307
- throw new mirrorProtocol.ModelConfigError("invalid-harness", `harness must be one of ${HARNESS_IDS.join(", ")}`);
4057
+ throw new fileStore.ModelConfigError("invalid-harness", `harness must be one of ${HARNESS_IDS.join(", ")}`);
4308
4058
  }
4309
4059
  return value;
4310
4060
  }
4311
4061
  class ModelConfigService {
4312
4062
  constructor(opts) {
4313
4063
  this.opts = opts;
4314
- this.backups = new mirrorProtocol.BackupStore(path.join(opts.happyHomeDir, "model-config-backups"));
4064
+ this.backups = new fileStore.BackupStore(path.join(opts.happyHomeDir, "model-config-backups"));
4315
4065
  this.auditPath = path.join(opts.happyHomeDir, "model-config-audit.jsonl");
4316
4066
  }
4317
4067
  backups;
@@ -4328,8 +4078,8 @@ class ModelConfigService {
4328
4078
  }
4329
4079
  async loadFiles(adapter) {
4330
4080
  const files = {};
4331
- for (const { role, path } of adapter.files(this.ctx)) files[role] = await mirrorProtocol.readConfigFile(path);
4332
- const baseHash = mirrorProtocol.sha256(Object.entries(files).map(([role, f]) => `${role}\0${f.exists ? f.hash : "-"}`).join("\n"));
4081
+ for (const { role, path } of adapter.files(this.ctx)) files[role] = await fileStore.readConfigFile(path);
4082
+ const baseHash = fileStore.sha256(Object.entries(files).map(([role, f]) => `${role}\0${f.exists ? f.hash : "-"}`).join("\n"));
4333
4083
  return { files, baseHash };
4334
4084
  }
4335
4085
  async readOne(harness, providerId) {
@@ -4377,7 +4127,7 @@ class ModelConfigService {
4377
4127
  if (target === null && !file.exists) continue;
4378
4128
  const secrets = [...adapter.secretsIn(role, file.content), ...target ? adapter.secretsIn(role, target) : [], ...extraSecrets];
4379
4129
  const raw = unifiedDiff(file.content, newText, file.path);
4380
- out.push({ path: file.path, diff: redactText(raw, secrets), created: !file.exists && target !== null, deleted: target === null && file.exists });
4130
+ out.push({ path: file.path, diff: pi.redactText(raw, secrets), created: !file.exists && target !== null, deleted: target === null && file.exists });
4381
4131
  }
4382
4132
  return out;
4383
4133
  }
@@ -4388,7 +4138,7 @@ class ModelConfigService {
4388
4138
  for (const [role, target] of touched) {
4389
4139
  const file = files[role];
4390
4140
  if (target.content === null) await node_fs.promises.rm(file.path, { force: true });
4391
- else await mirrorProtocol.atomicWriteFile(file.path, target.content, file.mode ?? target.mode);
4141
+ else await fileStore.atomicWriteFile(file.path, target.content, file.mode ?? target.mode);
4392
4142
  }
4393
4143
  await this.audit({ at: this.opts.now?.() ?? Date.now(), action, harness, files: touched.map(([role]) => files[role].path), keys, apiKeyMode, backupId: backup.id });
4394
4144
  this.opts.onChanged?.(harness);
@@ -4405,13 +4155,13 @@ class ModelConfigService {
4405
4155
  apply(params) {
4406
4156
  const run = async () => {
4407
4157
  const patch = params.patch;
4408
- if (!patch || typeof patch !== "object") throw new mirrorProtocol.ModelConfigError("invalid-input", "patch is required");
4158
+ if (!patch || typeof patch !== "object") throw new fileStore.ModelConfigError("invalid-input", "patch is required");
4409
4159
  const harness = harnessOf(patch.harness);
4410
4160
  const adapter = ADAPTERS[harness];
4411
4161
  const { files, baseHash } = await this.loadFiles(adapter);
4412
4162
  const dryRun = params.dryRun !== false;
4413
4163
  if (!dryRun && params.baseHash !== baseHash) {
4414
- throw new mirrorProtocol.ModelConfigError("conflict", "The config files changed since the preview; review the new diff and apply again");
4164
+ throw new fileStore.ModelConfigError("conflict", "The config files changed since the preview; review the new diff and apply again");
4415
4165
  }
4416
4166
  const plan = adapter.plan(files, patch, this.ctx);
4417
4167
  const diffs = this.diffs(adapter, files, plan.contents, plan.secrets);
@@ -4431,18 +4181,18 @@ class ModelConfigService {
4431
4181
  const run = async () => {
4432
4182
  const harness = harnessOf(params.harness);
4433
4183
  const adapter = ADAPTERS[harness];
4434
- if (typeof params.backupId !== "string") throw new mirrorProtocol.ModelConfigError("invalid-input", "backupId is required");
4184
+ if (typeof params.backupId !== "string") throw new fileStore.ModelConfigError("invalid-input", "backupId is required");
4435
4185
  const saved = await this.backups.read(harness, params.backupId);
4436
4186
  const { files, baseHash } = await this.loadFiles(adapter);
4437
4187
  const dryRun = params.dryRun !== false;
4438
4188
  if (!dryRun && params.baseHash !== baseHash) {
4439
- throw new mirrorProtocol.ModelConfigError("conflict", "The config files changed since the preview; review the new diff and restore again");
4189
+ throw new fileStore.ModelConfigError("conflict", "The config files changed since the preview; review the new diff and restore again");
4440
4190
  }
4441
4191
  const allowed = new Map(Object.entries(files).map(([role, f]) => [f.path, role]));
4442
4192
  const target = {};
4443
4193
  for (const file of saved) {
4444
4194
  const role = allowed.get(file.path);
4445
- if (!role) throw new mirrorProtocol.ModelConfigError("backup-mismatch", "Backup does not belong to the current config location");
4195
+ if (!role) throw new fileStore.ModelConfigError("backup-mismatch", "Backup does not belong to the current config location");
4446
4196
  target[role] = { content: file.content, mode: file.mode };
4447
4197
  }
4448
4198
  const diffs = this.diffs(adapter, files, Object.fromEntries(Object.entries(target).map(([r, v]) => [r, v?.content])), []);
@@ -4457,7 +4207,7 @@ class ModelConfigService {
4457
4207
  }
4458
4208
  }
4459
4209
  function toError(error) {
4460
- if (error instanceof mirrorProtocol.ModelConfigError) return { code: error.code, message: error.message };
4210
+ if (error instanceof fileStore.ModelConfigError) return { code: error.code, message: error.message };
4461
4211
  return { code: "read-failed", message: error instanceof Error ? error.message : String(error) };
4462
4212
  }
4463
4213
 
@@ -4790,26 +4540,45 @@ function createUpdateController(deps) {
4790
4540
  let blocked = false;
4791
4541
  let handoverHold;
4792
4542
  let lastPublished = "";
4543
+ let autoUpdateEnabled;
4544
+ let lastInstall = (() => {
4545
+ try {
4546
+ return deps.lastInstall?.read() ?? null;
4547
+ } catch {
4548
+ return null;
4549
+ }
4550
+ })();
4793
4551
  const now = deps.now ?? Date.now;
4552
+ function snapshot() {
4553
+ return {
4554
+ ...policy,
4555
+ retrySupported: !blocked,
4556
+ manualUpdateSupported: !blocked,
4557
+ handoverHold,
4558
+ autoUpdate: outcome,
4559
+ ...autoUpdateEnabled === void 0 ? {} : { autoUpdateEnabled },
4560
+ ...lastInstall ? { lastInstall } : {}
4561
+ };
4562
+ }
4794
4563
  function validManualTarget(next, version) {
4795
4564
  return version === next.recommendedVersion && Number.isFinite(next.checkedAt) && now() - next.checkedAt <= 65 * 6e4 && next.checkedAt <= now() + 6e4 && cliUpdate.compareExactVersions(next.currentVersion, version) === -1 && (!next.minimumVersion || [0, 1].includes(cliUpdate.compareExactVersions(version, next.minimumVersion) ?? -1));
4796
4565
  }
4797
4566
  function publish(state, version, detail) {
4798
4567
  if (!policy) return;
4799
- const fingerprint = JSON.stringify([policy.checkedAt, state, version, detail, manualVersion]);
4568
+ const fingerprint = JSON.stringify([policy.checkedAt, state, version, detail, manualVersion, autoUpdateEnabled]);
4800
4569
  if (fingerprint === lastPublished) return;
4801
4570
  lastPublished = fingerprint;
4802
4571
  outcome = { state, version, detail, at: now(), ...manualVersion ? { source: "manual" } : {} };
4803
- deps.publish({ ...policy, retrySupported: !blocked, manualUpdateSupported: !blocked, handoverHold, autoUpdate: outcome });
4572
+ deps.publish(snapshot());
4804
4573
  }
4805
4574
  async function evaluate() {
4806
4575
  if (!policy || running) return;
4807
4576
  const target = manualVersion ?? policy.autoUpdateVersion ?? null;
4808
4577
  if (outcome?.version === target && ["failed", "installed", "manual_required"].includes(outcome.state)) {
4809
- const fingerprint = JSON.stringify([policy.checkedAt, outcome.state, outcome.version, outcome.detail, manualVersion]);
4578
+ const fingerprint = JSON.stringify([policy.checkedAt, outcome.state, outcome.version, outcome.detail, manualVersion, autoUpdateEnabled]);
4810
4579
  if (fingerprint !== lastPublished) {
4811
4580
  lastPublished = fingerprint;
4812
- deps.publish({ ...policy, retrySupported: !blocked, manualUpdateSupported: !blocked, handoverHold, autoUpdate: outcome });
4581
+ deps.publish(snapshot());
4813
4582
  }
4814
4583
  return;
4815
4584
  }
@@ -4817,9 +4586,12 @@ function createUpdateController(deps) {
4817
4586
  publish("policy_stale", target);
4818
4587
  return;
4819
4588
  }
4820
- if (!manualVersion && !await deps.enabled()) {
4821
- publish("disabled", target);
4822
- return;
4589
+ if (!manualVersion) {
4590
+ autoUpdateEnabled = await deps.enabled();
4591
+ if (!autoUpdateEnabled) {
4592
+ publish("disabled", target);
4593
+ return;
4594
+ }
4823
4595
  }
4824
4596
  if (running) return;
4825
4597
  if (now() - policy.checkedAt > 65 * 6e4) {
@@ -4849,6 +4621,13 @@ function createUpdateController(deps) {
4849
4621
  return;
4850
4622
  }
4851
4623
  blocked = code === "blocked";
4624
+ if (code === 0) {
4625
+ lastInstall = { from: policy.currentVersion, to: target, at: now(), source: manualVersion ? "manual" : "auto" };
4626
+ try {
4627
+ deps.lastInstall?.write(lastInstall);
4628
+ } catch {
4629
+ }
4630
+ }
4852
4631
  publish(
4853
4632
  blocked ? "manual_required" : code === 0 ? "installed" : "failed",
4854
4633
  target,
@@ -4950,7 +4729,224 @@ function createUpdateController(deps) {
4950
4729
  handoverHold = { reason, at: now() };
4951
4730
  publish("failed", manualVersion ?? policy?.autoUpdateVersion ?? null, "handover_preflight_failed");
4952
4731
  }
4953
- return { refresh, retry, request, tick, withHandover, holdHandover, isRunning: () => running };
4732
+ return { refresh, retry, request, tick, withHandover, holdHandover, isRunning: () => running, currentTarget: () => policy?.autoUpdateVersion ?? null };
4733
+ }
4734
+
4735
+ function parseLastInstall(raw) {
4736
+ if (!raw || typeof raw !== "object") return null;
4737
+ const { from, to, at, source } = raw;
4738
+ if (typeof from !== "string" || typeof to !== "string" || !cliUpdate.parseExactVersion(from) || !cliUpdate.parseExactVersion(to)) return null;
4739
+ if (typeof at !== "number" || !Number.isFinite(at) || source !== "auto" && source !== "manual") return null;
4740
+ return { from, to, at, source };
4741
+ }
4742
+ function lastInstallStore(homeDir) {
4743
+ const file = path.join(homeDir, "cli-update-last.json");
4744
+ return {
4745
+ read() {
4746
+ try {
4747
+ return parseLastInstall(JSON.parse(node_fs.readFileSync(file, "utf8")));
4748
+ } catch {
4749
+ return null;
4750
+ }
4751
+ },
4752
+ write(record) {
4753
+ const tmp = `${file}.${process.pid}.tmp`;
4754
+ node_fs.writeFileSync(tmp, JSON.stringify(record));
4755
+ node_fs.renameSync(tmp, file);
4756
+ }
4757
+ };
4758
+ }
4759
+
4760
+ const ANNOUNCE_FIRST_DELAY_MS = 9e4;
4761
+ const ANNOUNCE_JITTER_MS = 6e4;
4762
+ const ANNOUNCE_FOLLOW_UP_MS = 5 * 6e4;
4763
+ function announcedVersion(payload) {
4764
+ if (!payload || typeof payload !== "object") return null;
4765
+ const raw = payload.autoUpdateVersion;
4766
+ if (typeof raw !== "string" || raw.length > 64) return null;
4767
+ return cliUpdate.parseExactVersion(raw)?.exact ?? null;
4768
+ }
4769
+ function createPolicyAnnouncementHandler(deps) {
4770
+ const random = deps.random ?? Math.random;
4771
+ const setTimer = deps.setTimer ?? ((fn, ms) => {
4772
+ const t = setTimeout(fn, ms);
4773
+ t.unref?.();
4774
+ return t;
4775
+ });
4776
+ const clearTimer = deps.clearTimer ?? ((handle) => clearTimeout(handle));
4777
+ let timer = null;
4778
+ let pending = null;
4779
+ let stopped = false;
4780
+ function schedule(version, ms, followUp) {
4781
+ if (timer !== null) clearTimer(timer);
4782
+ pending = version;
4783
+ timer = setTimer(() => {
4784
+ timer = null;
4785
+ void deps.refresh().catch(() => void 0).finally(() => {
4786
+ if (stopped || pending !== version) return;
4787
+ if (deps.currentTarget() === version || !followUp) {
4788
+ pending = null;
4789
+ return;
4790
+ }
4791
+ schedule(version, ANNOUNCE_FOLLOW_UP_MS, false);
4792
+ });
4793
+ }, ms);
4794
+ }
4795
+ return {
4796
+ /** Socket handler. Returns the scheduled delay (ms) or null when ignored — for logs and tests. */
4797
+ onAnnouncement(payload) {
4798
+ if (stopped) return null;
4799
+ const version = announcedVersion(payload);
4800
+ if (!version || version === deps.currentTarget() || version === pending) return null;
4801
+ const delay = ANNOUNCE_FIRST_DELAY_MS + Math.floor(random() * ANNOUNCE_JITTER_MS);
4802
+ schedule(version, delay, true);
4803
+ return delay;
4804
+ },
4805
+ stop() {
4806
+ stopped = true;
4807
+ if (timer !== null) clearTimer(timer);
4808
+ timer = null;
4809
+ }
4810
+ };
4811
+ }
4812
+
4813
+ const HARNESS_INSTALL_CATALOG = Object.freeze({
4814
+ claude: Object.freeze({ packages: [{ name: "@anthropic-ai/claude-code", spec: "latest" }], commands: ["claude"], minNode: "22.0.0", scripts: true }),
4815
+ codex: Object.freeze({ packages: [{ name: "@openai/codex", spec: "latest" }], commands: ["codex"], minNode: "16.0.0", scripts: false }),
4816
+ gemini: Object.freeze({ packages: [{ name: "@google/gemini-cli", spec: "latest" }], commands: ["gemini"], minNode: "20.0.0", scripts: false }),
4817
+ pi: Object.freeze({
4818
+ packages: [{ name: "@earendil-works/pi-coding-agent", spec: "latest" }, { name: "pi-acp", spec: "0.0.33" }],
4819
+ commands: ["pi", "pi-acp"],
4820
+ minNode: "22.19.0",
4821
+ scripts: false
4822
+ })
4823
+ });
4824
+ function harnessInstallArgs(id, prefix) {
4825
+ const entry = HARNESS_INSTALL_CATALOG[id];
4826
+ if (!entry) throw new Error("Unknown harness");
4827
+ const args = ["i", "-g", `--prefix=${prefix}`, "--no-fund", "--no-audit", ...entry.scripts ? [] : ["--ignore-scripts"], ...entry.packages.map((p) => `${p.name}@${p.spec}`)];
4828
+ if (args.some((arg) => !doctor.NPM_SAFE_ARG.test(arg))) throw new Error("Unsafe npm argument");
4829
+ return args;
4830
+ }
4831
+ function parseNode(version) {
4832
+ const m = /^v?(\d+)\.(\d+)\.(\d+)/.exec(version.trim());
4833
+ return m ? [Number(m[1]), Number(m[2]), Number(m[3])] : null;
4834
+ }
4835
+ function nodeSatisfies(current, minimum) {
4836
+ const a = parseNode(current);
4837
+ const b = parseNode(minimum);
4838
+ if (!a || !b) return false;
4839
+ for (let i = 0; i < 3; i++) if (a[i] !== b[i]) return a[i] > b[i];
4840
+ return true;
4841
+ }
4842
+
4843
+ async function defaultCanWrite(path) {
4844
+ try {
4845
+ await promises.access(path, promises.constants.W_OK);
4846
+ return true;
4847
+ } catch {
4848
+ return false;
4849
+ }
4850
+ }
4851
+ async function defaultIsPlainDirectory(path) {
4852
+ try {
4853
+ const s = await promises.lstat(path);
4854
+ return s.isDirectory() && !s.isSymbolicLink();
4855
+ } catch {
4856
+ return false;
4857
+ }
4858
+ }
4859
+ function redactNpmText(text) {
4860
+ 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, "~");
4861
+ }
4862
+ function summarizeNpmFailure(result) {
4863
+ const tail = `${result.stderr ?? ""}
4864
+ ${result.stdout ?? ""}`.trim().split("\n").filter((line) => /npm (error|ERR!)|EACCES|ENOTFOUND|ETARGET|E404|engine/i.test(line)).slice(-3).join(" | ");
4865
+ const code = result.code === null ? "npm_timeout" : `npm_exit_${result.code}`;
4866
+ const safe = redactNpmText(tail).slice(0, 300);
4867
+ return safe ? `${code}: ${safe}` : code;
4868
+ }
4869
+ function createHarnessInstaller(deps) {
4870
+ const now = deps.now ?? Date.now;
4871
+ const canWrite = deps.canWrite ?? defaultCanWrite;
4872
+ const isPlainDirectory = deps.isPlainDirectory ?? defaultIsPlainDirectory;
4873
+ let running = null;
4874
+ let blocked = false;
4875
+ const nodeVersion = deps.nodeVersion ?? process.versions.node;
4876
+ async function installPrefix() {
4877
+ const prefix = doctor.globalPrefixForPackage(deps.runningPackageDir);
4878
+ if (!prefix || !await isPlainDirectory(deps.runningPackageDir)) return { error: "install_location_unverified" };
4879
+ for (const path$1 of [path.dirname(deps.runningPackageDir), path.join(prefix, "bin")]) {
4880
+ if (!await canWrite(path$1)) return { error: "install_location_not_writable" };
4881
+ }
4882
+ return { prefix };
4883
+ }
4884
+ async function run(harness, prefix, startedAt) {
4885
+ const publish = (state, detail) => deps.publish({ harness, state, ...detail ? { detail } : {}, startedAt, at: now() });
4886
+ try {
4887
+ const args = harnessInstallArgs(harness, prefix);
4888
+ deps.log(`[HARNESS] installing ${harness}: npm ${args.join(" ")}`);
4889
+ const result = await deps.npm(args);
4890
+ if (result.terminationConfirmed === false) {
4891
+ blocked = true;
4892
+ deps.log(`[HARNESS] ${harness} install timed out and npm could not be confirmed stopped; holding the npm tree until the daemon restarts`);
4893
+ publish("failed", "installer_termination_unconfirmed");
4894
+ return;
4895
+ }
4896
+ if (result.code !== 0) {
4897
+ const detail = summarizeNpmFailure(result);
4898
+ deps.log(`[HARNESS] ${harness} install failed: ${detail}`);
4899
+ publish("failed", detail);
4900
+ return;
4901
+ }
4902
+ const missing = HARNESS_INSTALL_CATALOG[harness].commands.filter((command) => !deps.commandOnPath(command));
4903
+ if (missing.length > 0) {
4904
+ const detail = `not_on_path: ${missing.join(", ")}`;
4905
+ deps.log(`[HARNESS] ${harness} installed into ${prefix} but ${detail}`);
4906
+ publish("failed", detail);
4907
+ return;
4908
+ }
4909
+ await deps.afterInstall().catch((error) => deps.log(`[HARNESS] refresh after ${harness} install failed: ${String(error)}`));
4910
+ deps.log(`[HARNESS] ${harness} installed`);
4911
+ publish("installed");
4912
+ } catch (error) {
4913
+ deps.log(`[HARNESS] ${harness} install error: ${String(error)}`);
4914
+ publish("failed", "install_error");
4915
+ } finally {
4916
+ if (!blocked) running = null;
4917
+ }
4918
+ }
4919
+ return {
4920
+ /** True while npm may be writing the prefix — including after an unconfirmed kill. */
4921
+ isRunning: () => running !== null || blocked,
4922
+ async request(params) {
4923
+ const harness = params && typeof params === "object" ? params.harness : void 0;
4924
+ if (!index.isHarnessInstallId(harness)) {
4925
+ deps.log(`[HARNESS] refused install request for an unknown harness`);
4926
+ return { error: "unknown_harness" };
4927
+ }
4928
+ if (blocked) return { error: "installer_blocked" };
4929
+ if (running) return { error: "install_in_progress" };
4930
+ if (deps.cliUpdateBusy()) return { error: "cli_update_in_progress" };
4931
+ const minNode = HARNESS_INSTALL_CATALOG[harness].minNode;
4932
+ if (!nodeSatisfies(nodeVersion, minNode)) {
4933
+ deps.log(`[HARNESS] ${harness} install refused: needs Node ${minNode}+, daemon runs ${nodeVersion}`);
4934
+ return { error: "node_too_old", required: minNode, current: nodeVersion };
4935
+ }
4936
+ running = harness;
4937
+ const location = await installPrefix();
4938
+ if ("error" in location) {
4939
+ running = null;
4940
+ deps.log(`[HARNESS] ${harness} install refused: ${location.error}`);
4941
+ return location;
4942
+ }
4943
+ const startedAt = now();
4944
+ deps.log(`[HARNESS] owner requested ${harness} install`);
4945
+ deps.publish({ harness, state: "installing", startedAt, at: startedAt });
4946
+ void run(harness, location.prefix, startedAt);
4947
+ return { accepted: true, harness };
4948
+ }
4949
+ };
4954
4950
  }
4955
4951
 
4956
4952
  function buildPresenceBatch(live, previous) {
@@ -5181,7 +5177,7 @@ function parseJsonObject(text) {
5181
5177
  return null;
5182
5178
  }
5183
5179
  }
5184
- function resolveSdkClaudeBinary(platform = process.platform, arch = process.arch, resolver = (s) => module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('run-CAOCYYfT.cjs', document.baseURI).href))).resolve(s)) {
5180
+ function resolveSdkClaudeBinary(platform = process.platform, arch = process.arch, resolver = (s) => module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('run-C_FumCQk.cjs', document.baseURI).href))).resolve(s)) {
5185
5181
  const platformArch = `${platform}-${arch}`;
5186
5182
  const candidates = [
5187
5183
  `@anthropic-ai/claude-agent-sdk-${platformArch}/claude`,
@@ -6262,7 +6258,10 @@ function mergeRestoreMetadata(stale, reported) {
6262
6258
  }
6263
6259
 
6264
6260
  function teamInitialPrompt(goal, root, directory) {
6265
- 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.";
6261
+ 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.
6262
+
6263
+ ${resources.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.
6264
+ ${resources.TEAM_MEMBER_RULES}`;
6266
6265
  return `${resources.TEAM_SKILL}
6267
6266
 
6268
6267
  ## Your role
@@ -6276,42 +6275,6 @@ Delegate from this working directory unless a different repository was explicitl
6276
6275
  ${goal}`;
6277
6276
  }
6278
6277
 
6279
- function receiptPath(home, operationId) {
6280
- if (!/^[a-zA-Z0-9_-]{1,128}$/.test(operationId)) throw new Error("Invalid team operation id");
6281
- return path.join(home, "teams", "effects", `${operationId}.json`);
6282
- }
6283
- function readReceipt(home, id) {
6284
- try {
6285
- const value = JSON.parse(node_fs.readFileSync(receiptPath(home, id), "utf8"));
6286
- if (value.operationId !== id || typeof value.claimId !== "string" || !["prepared", "spawning", "spawned", "delivered", "completed"].includes(value.phase)) throw new Error("Invalid team effect receipt");
6287
- return value;
6288
- } catch (error) {
6289
- if (error.code === "ENOENT") return null;
6290
- throw error;
6291
- }
6292
- }
6293
- function writeReceipt$1(home, receipt) {
6294
- configuration.ensurePrivateDirectorySync(path.join(home, "teams", "effects"));
6295
- const target = receiptPath(home, receipt.operationId);
6296
- const tmp = `${target}.${process.pid}.tmp`;
6297
- configuration.writePrivateFileSync(tmp, JSON.stringify(receipt));
6298
- const file = node_fs.openSync(tmp, "r");
6299
- try {
6300
- node_fs.fsyncSync(file);
6301
- } finally {
6302
- node_fs.closeSync(file);
6303
- }
6304
- node_fs.renameSync(tmp, target);
6305
- if (process.platform !== "win32") {
6306
- const directory = node_fs.openSync(path.join(home, "teams", "effects"), "r");
6307
- try {
6308
- node_fs.fsyncSync(directory);
6309
- } finally {
6310
- node_fs.closeSync(directory);
6311
- }
6312
- }
6313
- }
6314
-
6315
6278
  const exec = node_util.promisify(node_child_process.execFile);
6316
6279
  const git = async (directory, args) => (await exec("git", ["-C", directory, ...args], { timeout: 3e4, maxBuffer: 2 * 1024 * 1024 })).stdout.trim();
6317
6280
  const gitRaw = async (directory, args) => (await exec("git", ["-C", directory, ...args], { timeout: 3e4, maxBuffer: 64 * 1024 * 1024 })).stdout;
@@ -6669,7 +6632,19 @@ Use the teams tools to inspect current task state before acting. This message is
6669
6632
  }, 5e3);
6670
6633
  timer.unref();
6671
6634
  void tick();
6672
- return { tick, report, hasSession: (id) => knownSessionIds.has(id), get busy() {
6635
+ async function recoverScope(operationId, sessionId, scopeFile) {
6636
+ const teams = (await http.get("/v1/teams")).data.teams ?? [];
6637
+ const team = teams.find((candidate) => candidate.archivedAt === void 0 && candidate.operations.some((op2) => op2.id === operationId));
6638
+ const op = team?.operations.find((candidate) => candidate.id === operationId);
6639
+ if (!team || !op || op.type !== "spawn" || op.machineId !== deps.machineId) throw new Error("No live team launch for this session");
6640
+ const bot = team.bots.find((candidate) => candidate.id === op.botId);
6641
+ if (!bot || bot.sessionId !== sessionId || bot.generation !== op.generation) throw new Error("Team binding changed; not restoring its scope");
6642
+ const claimed = await action(op.teamId, `claim-${op.id}`, { type: "claim-operation", operationId: op.id, machineId: deps.machineId, ...op.teamLaunchVersion === 1 ? { teamLaunchVersion: 1 } : {} });
6643
+ if (!claimed.credential?.token || claimed.credential.botId !== op.botId) throw new Error("Team did not reissue the member credential");
6644
+ configuration.ensurePrivateDirectorySync(path.join(home, "teams", "scopes"));
6645
+ configuration.writePrivateFileSync(scopeFile, JSON.stringify({ serverUrl: server, scopeToken: claimed.credential.token, teamId: op.teamId, botId: op.botId, taskId: op.taskId, attemptId: op.attemptId }));
6646
+ }
6647
+ return { tick, report, recoverScope, hasSession: (id) => knownSessionIds.has(id), get busy() {
6673
6648
  return running;
6674
6649
  }, stop: () => {
6675
6650
  closed = true;
@@ -8189,6 +8164,13 @@ async function startDaemon() {
8189
8164
  }
8190
8165
  };
8191
8166
  const resumeGates = /* @__PURE__ */ new Map();
8167
+ const teamRelaunchEnvFor = (happySessionId, metadata) => teamRelaunchEnv({
8168
+ home: configuration.configuration.happyHomeDir,
8169
+ sessionId: happySessionId,
8170
+ metadata,
8171
+ recover: teamWorker ? (operationId, scopeFile) => teamWorker.recoverScope(operationId, happySessionId, scopeFile) : void 0,
8172
+ log: (message) => logger.logger.debug(`[DAEMON RUN] ${message}`)
8173
+ });
8192
8174
  const resumeSession = async (happySessionId, options) => {
8193
8175
  let gate = resumeGates.get(happySessionId);
8194
8176
  if (!gate) {
@@ -8271,9 +8253,11 @@ async function startDaemon() {
8271
8253
  const permissionMode = spawnAgents.sanitizeSpawnPermissionMode(requestedMode);
8272
8254
  if (permissionMode) launch.args.push("--permission-mode", permissionMode);
8273
8255
  else if (requestedMode !== void 0) logger.logger.debug(`[DAEMON RUN] resume: ignoring invalid permission mode ${JSON.stringify(requestedMode)}`);
8256
+ const teamEnv = await teamRelaunchEnvFor(happySessionId, metadata);
8274
8257
  const result = await spawnTrackedHappyProcess({
8275
8258
  args: launch.args,
8276
8259
  cwd: launch.cwd,
8260
+ ...teamEnv.VH_TEAM_SCOPE_FILE ? { spawnedBy: "teams" } : {},
8277
8261
  env: {
8278
8262
  ...process.env,
8279
8263
  // B-297: the credentialStore=file shim must ride every spawn path,
@@ -8289,7 +8273,8 @@ async function startDaemon() {
8289
8273
  HAPPY_RECONNECT_ENCRYPTION_VARIANT: tracked.encryption.encryptionVariant,
8290
8274
  HAPPY_RECONNECT_SEQ: String(tracked.encryption.seq),
8291
8275
  HAPPY_RECONNECT_METADATA_VERSION: String(tracked.encryption.metadataVersion),
8292
- HAPPY_RECONNECT_AGENT_STATE_VERSION: String(tracked.encryption.agentStateVersion)
8276
+ HAPPY_RECONNECT_AGENT_STATE_VERSION: String(tracked.encryption.agentStateVersion),
8277
+ ...teamEnv
8293
8278
  }
8294
8279
  });
8295
8280
  if (result.type === "success") {
@@ -8407,9 +8392,11 @@ async function startDaemon() {
8407
8392
  const permissionMode = spawnAgents.sanitizeSpawnPermissionMode(options?.permissionMode);
8408
8393
  if (permissionMode) args.push("--permission-mode", permissionMode);
8409
8394
  restartCounts.set(happySessionId, recordRestartAttempt(prior));
8395
+ const teamEnv = await teamRelaunchEnvFor(happySessionId, metadata);
8410
8396
  const result = await spawnTrackedHappyProcess({
8411
8397
  args,
8412
8398
  cwd,
8399
+ ...teamEnv.VH_TEAM_SCOPE_FILE ? { spawnedBy: "teams" } : {},
8413
8400
  env: {
8414
8401
  ...process.env,
8415
8402
  // B-297: the credentialStore=file shim must ride every spawn path,
@@ -8424,7 +8411,8 @@ async function startDaemon() {
8424
8411
  HAPPY_RECONNECT_ENCRYPTION_VARIANT: tracked.encryption.encryptionVariant,
8425
8412
  HAPPY_RECONNECT_SEQ: String(tracked.encryption.seq),
8426
8413
  HAPPY_RECONNECT_METADATA_VERSION: String(tracked.encryption.metadataVersion),
8427
- HAPPY_RECONNECT_AGENT_STATE_VERSION: String(tracked.encryption.agentStateVersion)
8414
+ HAPPY_RECONNECT_AGENT_STATE_VERSION: String(tracked.encryption.agentStateVersion),
8415
+ ...teamEnv
8428
8416
  }
8429
8417
  });
8430
8418
  if (result.type === "success") {
@@ -8727,6 +8715,8 @@ async function startDaemon() {
8727
8715
  })).then(() => logger.logger.debug(`[DAEMON RUN] claimed the machine record for host ${initialMachineMetadata.host}`)).catch((error) => logger.logger.debug("[DAEMON RUN] could not refresh the recorded host:", error));
8728
8716
  }
8729
8717
  let lastUpdateLog = "";
8718
+ let harnessInstaller = null;
8719
+ let handoverInFlight = false;
8730
8720
  const updateController = createUpdateController({
8731
8721
  policy: () => cliUpdate.fetchCliUpdateState(configuration.configuration.serverUrl, _package.packageJson.version),
8732
8722
  enabled: async () => ((await persistence.readSettings()).cliAutoUpdate ?? "idle") !== "off",
@@ -8734,8 +8724,9 @@ async function startDaemon() {
8734
8724
  // handover (they talk to the server themselves) and web terminals live
8735
8725
  // in tmux, so neither is a reason to hold an update. B-507: a session
8736
8726
  // whose turn ended but still has background tasks is busy too.
8737
- idle: () => !turnActivity.hasActiveTurn() && !backgroundTasks.hasAny(),
8727
+ idle: () => !turnActivity.hasActiveTurn() && !backgroundTasks.hasAny() && !harnessInstaller?.isRunning(),
8738
8728
  install: installCliSafely,
8729
+ lastInstall: lastInstallStore(configuration.configuration.happyHomeDir),
8739
8730
  publish: (state) => {
8740
8731
  cliUpdateStateRef = state;
8741
8732
  fileState = { ...fileState, cliUpdate: state };
@@ -8782,10 +8773,33 @@ async function startDaemon() {
8782
8773
  logger.logger.warn(`[DAEMON RUN] Ignoring invalid HAPPY_CLI_UPDATE_CHECK_INTERVAL; using ${cliUpdate.DEFAULT_CLI_UPDATE_CHECK_INTERVAL_MS}ms`);
8783
8774
  }
8784
8775
  const cliUpdateInterval = setInterval(() => void refreshCliUpdate(), cliUpdateIntervalMs);
8776
+ const policyAnnouncements = createPolicyAnnouncementHandler({
8777
+ refresh: refreshCliUpdate,
8778
+ currentTarget: updateController.currentTarget
8779
+ });
8780
+ apiMachine.setCliUpdatePolicyHandler((payload) => {
8781
+ const delay = policyAnnouncements.onAnnouncement(payload);
8782
+ if (delay !== null) logger.logger.warn(`[DAEMON RUN] CLI update policy announced; re-checking in ${Math.round(delay / 1e3)}s`);
8783
+ });
8785
8784
  const stopAgentVersionChecks = mirrorProtocol.startAgentVersionChecks(
8786
8785
  (snapshot) => apiMachine.updateDaemonState((state) => ({ ...state, status: state?.status ?? "running", agentVersions: snapshot })),
8787
8786
  (error) => logger.logger.debug("[DAEMON RUN] Agent version check failed", error)
8788
8787
  );
8788
+ harnessInstaller = createHarnessInstaller({
8789
+ npm: runNpm,
8790
+ runningPackageDir: projectPath.projectPath(),
8791
+ commandOnPath: detectCLI.commandOnPath,
8792
+ cliUpdateBusy: () => updateController.isRunning() || handoverInFlight,
8793
+ publish: (state) => {
8794
+ void apiMachine.updateDaemonState((current) => ({ ...current, status: current?.status ?? "running", harnessInstall: state })).catch((error) => logger.logger.debug("[DAEMON RUN] harness install state publish failed", error));
8795
+ },
8796
+ afterInstall: async () => {
8797
+ apiMachine.recheckCliAvailability();
8798
+ await stopAgentVersionChecks.refresh();
8799
+ },
8800
+ log: (message) => logger.logger.warn(`[DAEMON RUN] ${message}`)
8801
+ });
8802
+ apiMachine.setHarnessInstallHandler((params) => harnessInstaller.request(params));
8789
8803
  const heartbeatIntervalMs = parseInt(process.env.HAPPY_DAEMON_HEARTBEAT_INTERVAL || "60000");
8790
8804
  const restartOnStaleVersionAndHeartbeat = setInterval(serialTask(async () => {
8791
8805
  try {
@@ -8802,53 +8816,59 @@ async function startDaemon() {
8802
8816
  }
8803
8817
  await updateController.tick();
8804
8818
  await updateController.withHandover(async () => {
8805
- let bundleReplaced = false;
8806
- if (initialBundleMtimeMs > 0) {
8807
- try {
8808
- const currentMtimeMs = require$$0.statSync(bundlePath).mtimeMs;
8809
- bundleReplaced = currentMtimeMs !== initialBundleMtimeMs;
8810
- } catch {
8811
- }
8812
- }
8813
- const relayMove = pendingRelayMove;
8814
- if ((bundleReplaced || relayMove) && !teamWorker?.busy && !automationRunner?.busy && !updateController.isRunning() && !turnActivity.hasActiveTurn() && !backgroundTasks.hasAny()) {
8815
- const preflight = bundleReplaced ? await preflightNewBundle(bundlePath) : { action: "proceed" };
8816
- if (preflight.action === "hold") {
8817
- if (lastHandoverHold !== preflight.reason) {
8818
- lastHandoverHold = preflight.reason;
8819
- logger.logger.warn(`[DAEMON RUN] holding handover: ${preflight.reason}`);
8819
+ handoverInFlight = true;
8820
+ try {
8821
+ let bundleReplaced = false;
8822
+ if (initialBundleMtimeMs > 0) {
8823
+ try {
8824
+ const currentMtimeMs = require$$0.statSync(bundlePath).mtimeMs;
8825
+ bundleReplaced = currentMtimeMs !== initialBundleMtimeMs;
8826
+ } catch {
8820
8827
  }
8821
- updateController.holdHandover(preflight.reason);
8822
- return;
8823
- }
8824
- if (teamWorker?.busy || automationRunner?.busy || updateController.isRunning() || turnActivity.hasActiveTurn() || backgroundTasks.hasAny()) return;
8825
- lastHandoverHold = null;
8826
- logger.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");
8827
- teamWorker?.stop();
8828
- automationRunner?.stop();
8829
- projectPresence?.stop();
8830
- clearInterval(restartOnStaleVersionAndHeartbeat);
8831
- clearInterval(cliUpdateInterval);
8832
- stopAgentVersionChecks();
8833
- claudeAuthService.stop();
8834
- apiMachine.shutdown();
8835
- await stopControlServer();
8836
- await controlClient.cleanupDaemonState();
8837
- await persistence.releaseDaemonLock(daemonLockHandle);
8838
- await stopCaffeinate();
8839
- if (daemonSupervisor$1 === "systemd") {
8840
- logger.logger.debug(`[DAEMON RUN] systemd-supervised: exiting ${HANDOVER_EXIT_CODE} for the unit to restart on the new bundle`);
8841
- process.exit(HANDOVER_EXIT_CODE);
8842
8828
  }
8843
- try {
8844
- spawnHappyCLI.spawnHappyCLI(["daemon", "start"], {
8845
- detached: true,
8846
- stdio: "ignore"
8847
- });
8848
- } catch (error) {
8849
- logger.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);
8829
+ const relayMove = pendingRelayMove;
8830
+ if ((bundleReplaced || relayMove) && !teamWorker?.busy && !automationRunner?.busy && !updateController.isRunning() && !harnessInstaller?.isRunning() && !turnActivity.hasActiveTurn() && !backgroundTasks.hasAny()) {
8831
+ const preflight = bundleReplaced ? await preflightNewBundle(bundlePath) : { action: "proceed" };
8832
+ if (preflight.action === "hold") {
8833
+ if (lastHandoverHold !== preflight.reason) {
8834
+ lastHandoverHold = preflight.reason;
8835
+ logger.logger.warn(`[DAEMON RUN] holding handover: ${preflight.reason}`);
8836
+ }
8837
+ updateController.holdHandover(preflight.reason);
8838
+ return;
8839
+ }
8840
+ if (teamWorker?.busy || automationRunner?.busy || updateController.isRunning() || harnessInstaller?.isRunning() || turnActivity.hasActiveTurn() || backgroundTasks.hasAny()) return;
8841
+ lastHandoverHold = null;
8842
+ logger.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");
8843
+ teamWorker?.stop();
8844
+ automationRunner?.stop();
8845
+ projectPresence?.stop();
8846
+ clearInterval(restartOnStaleVersionAndHeartbeat);
8847
+ clearInterval(cliUpdateInterval);
8848
+ policyAnnouncements.stop();
8849
+ stopAgentVersionChecks();
8850
+ claudeAuthService.stop();
8851
+ apiMachine.shutdown();
8852
+ await stopControlServer();
8853
+ await controlClient.cleanupDaemonState();
8854
+ await persistence.releaseDaemonLock(daemonLockHandle);
8855
+ await stopCaffeinate();
8856
+ if (daemonSupervisor$1 === "systemd") {
8857
+ logger.logger.debug(`[DAEMON RUN] systemd-supervised: exiting ${HANDOVER_EXIT_CODE} for the unit to restart on the new bundle`);
8858
+ process.exit(HANDOVER_EXIT_CODE);
8859
+ }
8860
+ try {
8861
+ spawnHappyCLI.spawnHappyCLI(["daemon", "start"], {
8862
+ detached: true,
8863
+ stdio: "ignore"
8864
+ });
8865
+ } catch (error) {
8866
+ logger.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);
8867
+ }
8868
+ process.exit(0);
8850
8869
  }
8851
- process.exit(0);
8870
+ } finally {
8871
+ handoverInFlight = false;
8852
8872
  }
8853
8873
  });
8854
8874
  const daemonState = await persistence.readDaemonState();
@@ -8915,7 +8935,8 @@ async function startDaemon() {
8915
8935
  log: (message) => logger.logger.debug(`[DAEMON RUN] ${message}`)
8916
8936
  });
8917
8937
  void apiMachine.updateMachineMetadata((current) => ({
8918
- ...current ?? initialMachineMetadata,
8938
+ // B-718: the running CLI version, not the one that first registered the row.
8939
+ ...machineMetadata.withRunningCliFacts(current, initialMachineMetadata),
8919
8940
  teamsVersion: 1,
8920
8941
  teamLaunchVersion: 1,
8921
8942
  // B-689: this daemon acts on the owner's retry-operation (web gates「重试启动」).
@@ -8923,7 +8944,9 @@ async function startDaemon() {
8923
8944
  // B-586: this daemon serves the worktree-* RPCs (web gates 「在新 worktree 中」 on it).
8924
8945
  worktreeVersion: mirrorProtocol.WORKTREE_CAPABILITY_VERSION,
8925
8946
  // B-688: this daemon serves the model-config-* RPCs (web gates 「模型与 Provider」 on it).
8926
- modelConfigVersion: MODEL_CONFIG_CAPABILITY_VERSION
8947
+ modelConfigVersion: MODEL_CONFIG_CAPABILITY_VERSION,
8948
+ // B-719: this daemon serves `harness-install` (web gates the Install / Update buttons on it).
8949
+ harnessInstallVersion: index.HARNESS_INSTALL_CAPABILITY_VERSION
8927
8950
  })).catch(() => logger.logger.debug("[DAEMON RUN] Teams capability publication will retry on reconnect"));
8928
8951
  const cleanupAndShutdown = async (source, errorMessage) => {
8929
8952
  webPanelsSync.stop();
@@ -8938,6 +8961,7 @@ async function startDaemon() {
8938
8961
  logger.logger.debug("[DAEMON RUN] Health check interval cleared");
8939
8962
  }
8940
8963
  clearInterval(cliUpdateInterval);
8964
+ policyAnnouncements.stop();
8941
8965
  stopAgentVersionChecks();
8942
8966
  await apiMachine.updateDaemonState((state) => ({
8943
8967
  ...state,