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