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