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