very-happy-cli 0.2.178 → 0.2.180
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-DCQXHRzT.mjs → AcpBackend-B8Zi30Zh.mjs} +135 -16
- package/dist/{AcpBackend-CDkAQXYf.cjs → AcpBackend-DfRDtMcG.cjs} +135 -16
- package/dist/{AcpSessionManager-DckmUZza.cjs → AcpSessionManager-CASKOnOj.cjs} +38 -4
- package/dist/{AcpSessionManager-BXrQcqwC.mjs → AcpSessionManager-i8Rab0xT.mjs} +38 -4
- package/dist/{BasePermissionHandler-BVlrEhIi.mjs → BasePermissionHandler-26fKbRCm.mjs} +5 -2
- package/dist/{BasePermissionHandler-DyLmKKtO.cjs → BasePermissionHandler-BlM_tfej.cjs} +5 -2
- package/dist/acpSessionResume-B9l54P-X.mjs +67 -0
- package/dist/acpSessionResume-CR5pmgVo.cjs +74 -0
- package/dist/{agentHome-CEp9RMyK.cjs → agentHome-C7Sb7-Tz.cjs} +4 -4
- package/dist/{agentHome-C1xi37as.mjs → agentHome-CmGy0NWv.mjs} +4 -4
- package/dist/{api-JANhFdZV.cjs → api-DM7b1OKj.cjs} +38 -262
- package/dist/{api-Dib0dmqC.mjs → api-Hov8p7CA.mjs} +32 -256
- package/dist/{apiMachine-DNTJidYH.mjs → apiMachine-CwGEhQDT.mjs} +62 -15
- package/dist/{apiMachine-QhK-sKcr.cjs → apiMachine-DkGRj-Ai.cjs} +64 -17
- package/dist/{attachmentContent-bpQTBJ0y.cjs → attachmentContent-CEm13_gF.cjs} +4 -4
- package/dist/{attachmentContent-DkkdpSSJ.mjs → attachmentContent-CIn4iPEk.mjs} +2 -2
- package/dist/{auth-BRjd2esb.mjs → auth-BG1DY0gj.mjs} +5 -5
- package/dist/{auth-DgIV0Cvt.cjs → auth-CBXnm-Mj.cjs} +5 -5
- package/dist/{auth-omWDtgR8.cjs → auth-Dawj7pP5.cjs} +8 -8
- package/dist/{auth-DNct7VUi.mjs → auth-rggxMpEE.mjs} +8 -8
- package/dist/{auto-DKGFOJBo.mjs → auto-CXXLXGeL.mjs} +19 -18
- package/dist/{auto-CI16FZmX.cjs → auto-CbYOfoPS.cjs} +19 -18
- package/dist/{backgroundTaskActivity-BVwJBl02.mjs → backgroundTaskActivity-8PdPxrvy.mjs} +1 -1
- package/dist/{bootstrap-DCQ87Aan.cjs → bootstrap-DoA7okld.cjs} +2 -2
- package/dist/{bootstrap-BUagGC_-.mjs → bootstrap-Dt5I1SOl.mjs} +2 -2
- package/dist/{browser-Dj_DYQEh.mjs → browser-DUNetFT4.mjs} +1 -1
- package/dist/{browser-CSKhc0Bl.cjs → browser-DZ6qRtfH.cjs} +1 -1
- package/dist/{claudeCliPath-U4HHmV4r.cjs → claudeCliPath-GaP3UUz1.cjs} +1 -1
- package/dist/{claudeLocal-CdIVnOZp.cjs → claudeLocal--DNyMOSv.cjs} +8 -7
- package/dist/{claudeLocal-CCCvAeMd.mjs → claudeLocal-OHWQigCl.mjs} +6 -5
- package/dist/{claudeLocalLauncher-BmrpaRYJ.mjs → claudeLocalLauncher-BStvfa7M.mjs} +11 -10
- package/dist/{claudeLocalLauncher-21-xh5gc.cjs → claudeLocalLauncher-BhvPbcyH.cjs} +13 -12
- package/dist/{claudeSessionFork-kYCP1IJB.cjs → claudeSessionFork-Be16vzGc.cjs} +1 -1
- package/dist/{claudeSessionFork-DvxMKAmY.mjs → claudeSessionFork-GAVWhiKG.mjs} +1 -1
- package/dist/{client-C4zKGjiJ.cjs → client-BWjaHKtj.cjs} +2 -2
- package/dist/client-Ca5Pcgy_.mjs +71 -0
- package/dist/{client-CnlkkrZO.mjs → client-CnB7kRCT.mjs} +2 -2
- package/dist/{client-BTX9hsbr.mjs → client-D4uPb1uu.mjs} +3 -3
- package/dist/client-DJplzPjD.cjs +76 -0
- package/dist/{client-Dnza1hgN.cjs → client-D_loZ9IK.cjs} +3 -3
- package/dist/codex/happyMcpStdioBridge.cjs +24 -15
- package/dist/codex/happyMcpStdioBridge.mjs +25 -16
- package/dist/{codexAppServerClient-DvkpQgyv.mjs → codexAppServerClient-CO4G6OS4.mjs} +3 -3
- package/dist/{codexAppServerClient-CRlBWGn3.cjs → codexAppServerClient-PE-12AsW.cjs} +3 -3
- package/dist/{codexCommand-DzxIWXIT.cjs → codexCommand-BNoD1t1C.cjs} +127 -116
- package/dist/{codexCommand-BxjogBzU.mjs → codexCommand-B_xUH79E.mjs} +77 -66
- package/dist/{codexTermination-C28FzlLb.cjs → codexTermination-BmBbizFK.cjs} +7 -2
- package/dist/{codexTermination-ySDNMEhd.mjs → codexTermination-Dx48ma6e.mjs} +7 -2
- package/dist/{config-BFB5uJ4t.cjs → config-CtwEUxUP.cjs} +26 -26
- package/dist/{config-DwfEpFr3.mjs → config-DSu0Blos.mjs} +3 -3
- package/dist/{configuration-4TGMrjul.cjs → configuration-CYO-kPOS.cjs} +1 -1
- package/dist/{configuration-DVrb61aY.mjs → configuration-PxzEJumr.mjs} +1 -1
- package/dist/{connect-C7FPY0PI.mjs → connect-BQrg3TB6.mjs} +15 -15
- package/dist/{connect-QH2VJGBG.cjs → connect-Dq7wndzR.cjs} +28 -28
- package/dist/{context-JoKu0V54.cjs → context-6TP-iz_c.cjs} +1 -1
- package/dist/{context-B3XPVJlj.mjs → context-CJGRRGWW.mjs} +1 -1
- package/dist/{controlClient-BTaZlbC6.cjs → controlClient-Bqv5v5OM.cjs} +3 -3
- package/dist/{controlClient-BYKyGyxj.mjs → controlClient-DoeYovwP.mjs} +4 -4
- package/dist/{createSessionMetadata-8Fuj2-IV.mjs → createSessionMetadata-DDIChCx8.mjs} +2 -2
- package/dist/{createSessionMetadata-B2NynaWZ.cjs → createSessionMetadata-xz767pcG.cjs} +3 -3
- package/dist/{defaultPermissionMode-BEWCqJU-.cjs → defaultPermissionMode-PkZGsQDk.cjs} +2 -2
- package/dist/{defaultPermissionMode-B8kJG9uI.mjs → defaultPermissionMode-i5KzdK09.mjs} +2 -2
- package/dist/{detectCLI-DR9ZaN6B.cjs → detectCLI-BuYR_Qxe.cjs} +4 -4
- package/dist/{deterministicJson-C5ZwZ7hu.mjs → deterministicJson-CTEJDqtS.mjs} +217 -12
- package/dist/{deterministicJson-BjNQsAjO.cjs → deterministicJson-xqA6DaOm.cjs} +222 -16
- package/dist/{doctor-BshRnjLi.cjs → doctor-BrgSlfXn.cjs} +9 -9
- package/dist/{doctor-0BiGwJXk.mjs → doctor-IctjVh9T.mjs} +8 -8
- package/dist/{doctorReadiness-BCZGcN0a.cjs → doctorReadiness-Dl6yRQCy.cjs} +1 -1
- package/dist/{downloadTarget-BN5vZt3K.cjs → downloadTarget-CEb9che-.cjs} +6 -6
- package/dist/{downloadTarget-DJPwAZlu.mjs → downloadTarget-jbOz1egC.mjs} +6 -6
- package/dist/{ensureDaemonRunning-C5QfToCe.cjs → ensureDaemonRunning-D-HyjGIX.cjs} +6 -6
- package/dist/{ensureDaemonRunning-CSmb_mqt.mjs → ensureDaemonRunning-b1e8LCSM.mjs} +5 -5
- package/dist/{handleResumeCommand-BwnUZmbo.cjs → handleResumeCommand-BvWVuRwH.cjs} +23 -7
- package/dist/{handleResumeCommand-CgjpsdDe.mjs → handleResumeCommand-DpaZvm2c.mjs} +22 -6
- package/dist/{index-piJPsTsm.cjs → index-B2p2Mut7.cjs} +259 -53
- package/dist/{index-BehVbAsA.mjs → index-BUNnpGFF.mjs} +256 -50
- package/dist/{index-Bvldtdny.mjs → index-Bsvbgh9e.mjs} +4 -4
- package/dist/{index-BYOZ_8WZ.cjs → index-C64Ufj4D.cjs} +4 -4
- package/dist/{index-Clqlu17M.mjs → index-C6Vn5Eac.mjs} +155 -34
- package/dist/index-CMf5VbzG.mjs +18 -0
- package/dist/index-DmTlDRSL.cjs +24 -0
- package/dist/{index-_l1Sdmbi.cjs → index-Dv0Q48P3.cjs} +156 -33
- package/dist/{index-BRClDe07.cjs → index-F2UejqYs.cjs} +3 -3
- package/dist/{index-BYgfern_.mjs → index-wCbtF4BK.mjs} +3 -3
- package/dist/index.cjs +2 -2
- package/dist/index.mjs +2 -2
- package/dist/{install-BdvKiOQw.mjs → install-BX3lv3NO.mjs} +1 -1
- package/dist/{install-Cf6AFSWC.cjs → install-BXvf2E3o.cjs} +1 -1
- package/dist/{install-TO4taKW7.cjs → install-C0ANcEAW.cjs} +7 -7
- package/dist/{install-fYWHrPcC.mjs → install-CdzSV7qO.mjs} +3 -3
- package/dist/{installPiTools-BKnXjrVu.cjs → installPiTools-CL_axh_6.cjs} +1 -1
- package/dist/{installTerminalHooks-BQZbvxsZ.cjs → installTerminalHooks-BETLKAtd.cjs} +3 -3
- package/dist/lib.cjs +14 -14
- package/dist/lib.d.cts +239 -2
- package/dist/lib.d.mts +239 -2
- package/dist/lib.mjs +14 -14
- package/dist/{lidState-BeOwj6eW.cjs → lidState-DWGnm2H4.cjs} +1 -1
- package/dist/{lidState-DNMlIhX0.mjs → lidState-bBrmaQd2.mjs} +1 -1
- package/dist/{localHappyAgentAuth-Br-mgogg.cjs → localHappyAgentAuth-DZHQnIW9.cjs} +1 -1
- package/dist/{localHappyAgentAuth-BezKSFDi.mjs → localHappyAgentAuth-Dn1jYDpt.mjs} +1 -1
- package/dist/{logger--vAVtXB_.mjs → logger-BUngm5Xg.mjs} +2 -2
- package/dist/{logger-DXTWlPzP.cjs → logger-CphW5nUE.cjs} +2 -2
- package/dist/machineMetadata-BQm9YTcI.cjs +79 -0
- package/dist/machineMetadata-BXN_bKU3.mjs +68 -0
- package/dist/{main-DoMFJ2Cl.cjs → main-B2UV51nK.cjs} +112 -132
- package/dist/{main-CshDb5-1.mjs → main-DGkdYfEq.mjs} +106 -126
- package/dist/{mcp-pNc1EbQf.cjs → mcp-BFvAEIRz.cjs} +15 -15
- package/dist/{mcp-DKT_lrvQ.mjs → mcp-CbTtbs0f.mjs} +15 -15
- package/dist/{mirrorProtocol-6IFGPpjJ.mjs → mirrorProtocol-BR4luBC0.mjs} +148 -7
- package/dist/{mirrorProtocol-qKflWTX_.cjs → mirrorProtocol-HKPivs5o.cjs} +150 -4
- package/dist/{package-B7B03ejW.mjs → package-7Ld1YMhh.mjs} +8 -7
- package/dist/{package-DY4zTbKS.cjs → package-KhLqBDX9.cjs} +8 -7
- package/dist/panels-C7IVqpJk.mjs +137 -0
- package/dist/panels-Njf6GelF.cjs +142 -0
- package/dist/{peerTools-3R9RUARX.mjs → peerTools-BOlDRyhi.mjs} +60 -10
- package/dist/{peerTools-iy3_6Yc5.cjs → peerTools-DQNlrcqM.cjs} +60 -9
- package/dist/{persistence-BfX72dne.cjs → persistence-9BMYspxG.cjs} +2 -2
- package/dist/{persistence-DTk4sNpd.mjs → persistence-BFUOHW5h.mjs} +3 -3
- package/dist/{piRunnerArgs-CRQVR9o-.cjs → piRunnerArgs-DGaq6VYM.cjs} +8 -2
- package/dist/{piRunnerArgs-AT8QJY7x.mjs → piRunnerArgs-H2pgpA5z.mjs} +7 -1
- package/dist/{piRuntime-BHRT5MKO.cjs → piRuntime-CT5xv_3h.cjs} +2 -2
- package/dist/{piRuntime-E44qTVQ4.mjs → piRuntime-CdN0JAsa.mjs} +2 -2
- package/dist/{piTerminal-hI4WFdWm.cjs → piTerminal-0FBtXTHK.cjs} +18 -18
- package/dist/{piTerminal-De8nbciM.mjs → piTerminal-C4m9YXAV.mjs} +18 -18
- package/dist/processTreeSnapshot-CuLJDNC-.mjs +101 -0
- package/dist/processTreeSnapshot-mDbnuojn.cjs +105 -0
- package/dist/projectPath-C3ioB4Bu.cjs +13 -0
- package/dist/prompt-BOouDaTM.mjs +99 -0
- package/dist/prompt-lgF9IE82.cjs +105 -0
- package/dist/{attachmentLimits-UyFn8fkQ.mjs → pushNotifications-DCiBEVeC.mjs} +307 -3
- package/dist/{attachmentLimits-SqeAHrVC.cjs → pushNotifications-DI92havG.cjs} +308 -2
- package/dist/{index-DFPT4HOO.cjs → query-BETKBrnz.cjs} +25 -5
- package/dist/{index-CEjJnq05.mjs → query-ywZbQUzR.mjs} +26 -3
- package/dist/{rateLimitWindows-XrJiiBpU.mjs → rateLimitWindows-BkfuUB1a.mjs} +1 -1
- package/dist/{rateLimitWindows-Czb7_7cK.cjs → rateLimitWindows-YRIYVdo4.cjs} +1 -1
- package/dist/{registerCommonHandlers-C0_fyBQ0.cjs → registerCommonHandlers-C7a_dUWs.cjs} +30 -30
- package/dist/{registerCommonHandlers-CvIedVRf.mjs → registerCommonHandlers-CTkCvjxt.mjs} +2 -2
- package/dist/{registerKillSessionHandler-DYl7l4Ts.cjs → registerKillSessionHandler-CIsvBrVe.cjs} +6 -6
- package/dist/{registerKillSessionHandler-gwJ5lTf0.mjs → registerKillSessionHandler-CaqqRYq6.mjs} +6 -6
- package/dist/{remoteSessionClient-BRTIOX5b.cjs → remoteSessionClient-D843fkzL.cjs} +5 -5
- package/dist/{remoteSessionClient-DRb2Jr5s.mjs → remoteSessionClient-PP-mOKmw.mjs} +5 -5
- package/dist/{reportProgressTool-B9nCzbkL.cjs → reportProgressTool-14AXr959.cjs} +84 -2
- package/dist/{reportProgressTool-D5tEoXMW.mjs → reportProgressTool-ZOCNjWHr.mjs} +82 -3
- package/dist/{resources-JDPugcDq.cjs → resources-CvIlkzpP.cjs} +30 -0
- package/dist/{resources-BdFBQi8r.mjs → resources-y9jfyO-_.mjs} +30 -0
- package/dist/{run-CKe_WeLW.cjs → run-CAOCYYfT.cjs} +2806 -110
- package/dist/{run-BQ_FGaOL.mjs → run-Dajw2E5j.mjs} +2787 -91
- package/dist/{runClaude-zrrNcQC_.mjs → runClaude-C6oxWYbp.mjs} +49 -47
- package/dist/{runClaude-MHI5IKM-.cjs → runClaude-CRRKt-tm.cjs} +50 -48
- package/dist/{runClaudeDeps-RRw-Nusc.cjs → runClaudeDeps-B1ysqTvx.cjs} +310 -237
- package/dist/{runClaudeDeps-CVBCokOS.mjs → runClaudeDeps-BcZ68cX3.mjs} +297 -226
- package/dist/{runGemini-kipag2ag.mjs → runGemini-D7yKc4wD.mjs} +61 -55
- package/dist/{runGemini-D7Lg9yCr.cjs → runGemini-DtKFXqf-.cjs} +89 -83
- package/dist/{runOpenClaw-C_lRY3Do.mjs → runOpenClaw-C8ilyLP9.mjs} +19 -19
- package/dist/{runOpenClaw-DCcQ0L2N.cjs → runOpenClaw-Cq29mryb.cjs} +20 -20
- package/dist/{sandbox-BSyXFVFp.mjs → sandbox-CbCutma4.mjs} +4 -4
- package/dist/{sandbox-dMwPTmjm.cjs → sandbox-gIj-EuF4.cjs} +4 -4
- package/dist/{selfCheck-DkzDDUsE.cjs → selfCheck-BTGBVthv.cjs} +39 -38
- package/dist/selfCheck-Smt-5Xiq.mjs +85 -0
- package/dist/{send-dxC0miIA.cjs → send-CZf5lLw_.cjs} +8 -8
- package/dist/{send-C-k9nsd3.mjs → send-YZRMQvMJ.mjs} +9 -9
- package/dist/{server-DxoTYNlw.mjs → server-BqwXRzFZ.mjs} +4 -4
- package/dist/{server-B-W6P7Qr.cjs → server-CknFq6xG.cjs} +6 -6
- package/dist/sessionExitLifecycle-CzkLIgNZ.mjs +142 -0
- package/dist/sessionExitLifecycle-DFltxXU8.cjs +148 -0
- package/dist/{sessionLock-DWX6BbiU.cjs → sessionLock-DRZUcKHT.cjs} +38 -5
- package/dist/{sessionLock-DBdtZGOL.mjs → sessionLock-nBXdJxJm.mjs} +36 -5
- package/dist/{sessionOps-BAnr7n8t.cjs → sessionOps-BrGiU9-s.cjs} +10 -5
- package/dist/{sessionOps-BVoXe12O.mjs → sessionOps-C58iDeD5.mjs} +11 -6
- package/dist/{sessionScanner-BtxoeluY.mjs → sessionScanner-Bm57LEXe.mjs} +3 -3
- package/dist/{sessionScanner-CDOYsEUD.cjs → sessionScanner-CaO8bLaG.cjs} +3 -3
- package/dist/{sessions-C3sZJkzI.mjs → sessions-BXyBHo8n.mjs} +40 -18
- package/dist/{sessions-hAitoN-m.cjs → sessions-uzqPCc37.cjs} +39 -16
- package/dist/{setupOfflineReconnection-_eD4cuUT.mjs → setupOfflineReconnection-1Qlek3wl.mjs} +2 -2
- package/dist/{setupOfflineReconnection-C2EeYTFH.cjs → setupOfflineReconnection-DvvVX3TB.cjs} +2 -2
- package/dist/{spawn-Bc9v7pIf.cjs → spawn-DBkodJEO.cjs} +49 -18
- package/dist/{spawn-qNrB2CD_.mjs → spawn-vn93PhYL.mjs} +49 -18
- package/dist/spawnAgents-CgPMWexP.mjs +79 -0
- package/dist/{spawnAgents-BVEaz35W.cjs → spawnAgents-DLNMlhWJ.cjs} +37 -1
- package/dist/{spawnHappyCLI-C2stUioM.mjs → spawnHappyCLI-BjFZc_7V.mjs} +1 -1
- package/dist/{spawnHappyCLI-fzUagkKV.cjs → spawnHappyCLI-DnUZv0X2.cjs} +2 -2
- package/dist/{specialCommands-BJ-nqY4s.cjs → specialCommands-BRufxlqe.cjs} +3 -3
- package/dist/{specialCommands-C6W-rVyh.mjs → specialCommands-D3ND9bdg.mjs} +3 -3
- package/dist/{startFileWatcher-Bqlqjt93.cjs → startFileWatcher-D9cYzZOq.cjs} +1 -1
- package/dist/{startFileWatcher-CR09Snmz.mjs → startFileWatcher-DXJ7yB5v.mjs} +1 -1
- package/dist/startupSessionCreate-kdr3m1uX.cjs +26 -0
- package/dist/startupSessionCreate-uZ6BM0ty.mjs +24 -0
- package/dist/{streamRelay-CJoMk_d2.mjs → streamRelay-DnMCMMfV.mjs} +1 -1
- package/dist/{streamRelay-9kBPpbvU.cjs → streamRelay-Dy6amlzx.cjs} +1 -1
- package/dist/{sync-Dhmtk66S.cjs → sync-Bn-idAoy.cjs} +1 -1
- package/dist/{sync-DbW07iQS.mjs → sync-BvVhDO40.mjs} +1 -1
- package/dist/{systemPrompt-CQcuUmVp.mjs → systemPrompt-CIZcJuJF.mjs} +4 -3
- package/dist/{systemPrompt-3PYi3EUY.cjs → systemPrompt-DlyEk35T.cjs} +4 -3
- package/dist/{teams-DpxLcUU_.cjs → teams-BLeRpDxv.cjs} +8 -8
- package/dist/{teams-CwF4Z2fA.mjs → teams-DLY3B5yi.mjs} +8 -8
- package/dist/{time-A6zR3Iy6.cjs → time-Cjv0zxQO.cjs} +1 -1
- package/dist/{time-BuGWTVP6.mjs → time-D3ETL7Jb.mjs} +1 -1
- package/dist/{titleGenerator-D26gvNaS.cjs → titleGenerator-Cgp5ihFb.cjs} +10 -9
- package/dist/{titleGenerator-BGEzNWC9.mjs → titleGenerator-Uzbd4utr.mjs} +9 -8
- package/dist/{todo-DrZc76H5.mjs → todo-C9Ga-oEM.mjs} +6 -6
- package/dist/{todo-73g3NCIO.cjs → todo-pOMcEECE.cjs} +6 -6
- package/dist/{tools-CxJp4T1T.cjs → tools-BL7Byl_x.cjs} +1 -1
- package/dist/{tools-BtsrQnNS.mjs → tools-Cho7iIWf.mjs} +1 -1
- package/dist/{uninstall-DWNYvewQ.cjs → uninstall-CbayEVoV.cjs} +6 -6
- package/dist/{uninstall-DOb2mTwV.mjs → uninstall-CvZHVY9C.mjs} +3 -3
- package/dist/webPanels-B2_C2-3g.mjs +49 -0
- package/dist/webPanels-FCgFKvVe.cjs +53 -0
- package/package.json +8 -7
- package/dist/machineMetadata-B1H9gNbN.mjs +0 -39
- package/dist/machineMetadata-DcUupMje.cjs +0 -42
- package/dist/messageBuffer-BWlGgR3A.mjs +0 -71
- package/dist/messageBuffer-fJMH6uY8.cjs +0 -73
- package/dist/projectPath-ChLhdOyt.cjs +0 -13
- package/dist/selfCheck-CF7XGJPb.mjs +0 -84
- package/dist/sessionModeFile-ByLbXTbg.mjs +0 -32
- package/dist/sessionModeFile-CLLdidiJ.cjs +0 -36
- package/dist/spawnAgents-Bw6j4tqz.mjs +0 -45
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import {
|
|
2
|
+
import { p as AgentVersionsSchema } from './index-C6Vn5Eac.mjs';
|
|
3
|
+
import axios from 'axios';
|
|
4
|
+
import { l as logger } from './logger-BUngm5Xg.mjs';
|
|
5
|
+
import { c as configuration } from './configuration-PxzEJumr.mjs';
|
|
6
|
+
import { c as contentLogMetadata } from './contentLogMetadata-BlWI0egu.mjs';
|
|
3
7
|
|
|
4
8
|
z.object({
|
|
5
9
|
teamsVersion: z.number().int().positive().optional(),
|
|
6
10
|
teamLaunchVersion: z.number().int().positive().optional(),
|
|
11
|
+
/** B-689: daemon acts on the owner's team `retry-operation`. Absent on old daemons. */
|
|
12
|
+
teamRetryVersion: z.number().int().positive().optional(),
|
|
7
13
|
/** B-586: daemon serves the `worktree-*` RPCs. Absent on old daemons. */
|
|
8
14
|
worktreeVersion: z.number().int().positive().optional(),
|
|
15
|
+
/** B-688: daemon serves the `model-config-*` RPCs (harness model/provider config). */
|
|
16
|
+
modelConfigVersion: z.number().int().positive().optional(),
|
|
9
17
|
host: z.string(),
|
|
10
18
|
platform: z.string(),
|
|
11
19
|
happyCliVersion: z.string(),
|
|
@@ -254,7 +262,21 @@ z.object({
|
|
|
254
262
|
idleMs: z.number(),
|
|
255
263
|
rpcAvailable: z.boolean(),
|
|
256
264
|
detectedAt: z.number()
|
|
257
|
-
}).passthrough().optional()
|
|
265
|
+
}).passthrough().optional(),
|
|
266
|
+
/**
|
|
267
|
+
* B-510: web panels declared in this machine's local settings.json
|
|
268
|
+
* (`webPanels`), normalized by webPanels/webPanels.ts. Read-only mirror —
|
|
269
|
+
* nothing on the web/server side can write the local declaration.
|
|
270
|
+
*/
|
|
271
|
+
webPanels: z.object({
|
|
272
|
+
panels: z.array(z.object({
|
|
273
|
+
id: z.string(),
|
|
274
|
+
title: z.string(),
|
|
275
|
+
url: z.string(),
|
|
276
|
+
embed: z.boolean()
|
|
277
|
+
})),
|
|
278
|
+
updatedAt: z.number()
|
|
279
|
+
}).optional()
|
|
258
280
|
});
|
|
259
281
|
const MessageMetaSchema = z.object({
|
|
260
282
|
sentFrom: z.string().optional(),
|
|
@@ -383,4 +405,286 @@ const MAX_CHAT_ATTACHMENT_SOURCE_BYTES = 50 * 1024 * 1024;
|
|
|
383
405
|
const CHAT_ATTACHMENT_ENCRYPTION_OVERHEAD_BYTES = 40;
|
|
384
406
|
const MAX_CHAT_ATTACHMENT_ENCRYPTED_BYTES = MAX_CHAT_ATTACHMENT_SOURCE_BYTES + CHAT_ATTACHMENT_ENCRYPTION_OVERHEAD_BYTES;
|
|
385
407
|
|
|
386
|
-
|
|
408
|
+
function getSessionTitle(metadata) {
|
|
409
|
+
const summaryText = metadata?.summary?.text?.trim();
|
|
410
|
+
if (summaryText) {
|
|
411
|
+
return summaryText;
|
|
412
|
+
}
|
|
413
|
+
const path = metadata?.path?.trim();
|
|
414
|
+
if (!path) {
|
|
415
|
+
return "Session";
|
|
416
|
+
}
|
|
417
|
+
const segments = path.split(/[\\/]/).filter(Boolean);
|
|
418
|
+
return segments[segments.length - 1] || "Session";
|
|
419
|
+
}
|
|
420
|
+
function getSessionNotificationUrl(data) {
|
|
421
|
+
const sessionId = data?.sessionId;
|
|
422
|
+
if (typeof sessionId !== "string") {
|
|
423
|
+
return null;
|
|
424
|
+
}
|
|
425
|
+
const trimmedSessionId = sessionId.trim();
|
|
426
|
+
if (!trimmedSessionId) {
|
|
427
|
+
return null;
|
|
428
|
+
}
|
|
429
|
+
return `/session/${encodeURIComponent(trimmedSessionId)}`;
|
|
430
|
+
}
|
|
431
|
+
function getSessionNotificationTitle(kind) {
|
|
432
|
+
switch (kind) {
|
|
433
|
+
case "done":
|
|
434
|
+
return "It's ready!";
|
|
435
|
+
case "permission":
|
|
436
|
+
return "Permission request";
|
|
437
|
+
case "question":
|
|
438
|
+
return "Clarification needed";
|
|
439
|
+
case "attention":
|
|
440
|
+
return "Needs you";
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
function getSessionNotificationBody(metadata) {
|
|
444
|
+
return getSessionTitle(metadata);
|
|
445
|
+
}
|
|
446
|
+
function getSessionNotificationCopy(kind, metadata) {
|
|
447
|
+
return {
|
|
448
|
+
title: getSessionNotificationTitle(kind),
|
|
449
|
+
body: getSessionNotificationBody(metadata)
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
class PushNotificationClient {
|
|
453
|
+
token;
|
|
454
|
+
baseUrl;
|
|
455
|
+
expoLoad = null;
|
|
456
|
+
constructor(token, baseUrl = "https://api.cluster-fluster.com") {
|
|
457
|
+
this.token = token;
|
|
458
|
+
this.baseUrl = baseUrl;
|
|
459
|
+
}
|
|
460
|
+
loadExpo() {
|
|
461
|
+
if (!this.expoLoad) {
|
|
462
|
+
const load = import('expo-server-sdk').then(({ Expo }) => ({ Expo, expo: new Expo() }));
|
|
463
|
+
load.catch(() => {
|
|
464
|
+
if (this.expoLoad === load) this.expoLoad = null;
|
|
465
|
+
});
|
|
466
|
+
this.expoLoad = load;
|
|
467
|
+
}
|
|
468
|
+
return this.expoLoad;
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* Fetch all push tokens for the authenticated user.
|
|
472
|
+
* Retries up to 3 times with exponential backoff on transient errors.
|
|
473
|
+
*/
|
|
474
|
+
async fetchPushTokens() {
|
|
475
|
+
const maxAttempts = 3;
|
|
476
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
477
|
+
try {
|
|
478
|
+
const response = await axios.get(
|
|
479
|
+
`${this.baseUrl}/v1/push-tokens`,
|
|
480
|
+
{
|
|
481
|
+
headers: {
|
|
482
|
+
"Authorization": `Bearer ${this.token}`,
|
|
483
|
+
"Content-Type": "application/json",
|
|
484
|
+
"X-Happy-Client": `cli-daemon/${configuration.currentCliVersion}`
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
);
|
|
488
|
+
logger.debug(`Fetched ${response.data.tokens.length} push tokens`);
|
|
489
|
+
response.data.tokens.forEach((token, index) => {
|
|
490
|
+
logger.debug(`[PUSH] Token ${index + 1}: id=${token.id}, created=${new Date(token.createdAt).toISOString()}, updated=${new Date(token.updatedAt).toISOString()}`);
|
|
491
|
+
});
|
|
492
|
+
return response.data.tokens;
|
|
493
|
+
} catch (error) {
|
|
494
|
+
logger.debug(`[PUSH] [ERROR] Failed to fetch push tokens (attempt ${attempt}/${maxAttempts}):`, error);
|
|
495
|
+
if (attempt < maxAttempts) {
|
|
496
|
+
const delay = 1e3 * Math.pow(2, attempt - 1);
|
|
497
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
logger.debug("[PUSH] [ERROR] All push token fetch attempts failed");
|
|
502
|
+
return [];
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* Send push notification via Expo Push API with retry
|
|
506
|
+
* @param messages - Array of push messages to send
|
|
507
|
+
*/
|
|
508
|
+
async sendPushNotifications(messages) {
|
|
509
|
+
logger.debug(`Sending ${messages.length} push notifications`);
|
|
510
|
+
const { Expo, expo } = await this.loadExpo();
|
|
511
|
+
const validMessages = messages.filter((message) => {
|
|
512
|
+
if (Array.isArray(message.to)) {
|
|
513
|
+
return message.to.every((token) => Expo.isExpoPushToken(token));
|
|
514
|
+
}
|
|
515
|
+
return Expo.isExpoPushToken(message.to);
|
|
516
|
+
});
|
|
517
|
+
if (validMessages.length === 0) {
|
|
518
|
+
logger.debug("No valid Expo push tokens found");
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
const chunks = expo.chunkPushNotifications(validMessages);
|
|
522
|
+
for (const chunk of chunks) {
|
|
523
|
+
const startTime = Date.now();
|
|
524
|
+
const timeout = 3e5;
|
|
525
|
+
let attempt = 0;
|
|
526
|
+
while (true) {
|
|
527
|
+
try {
|
|
528
|
+
const ticketChunk = await expo.sendPushNotificationsAsync(chunk);
|
|
529
|
+
const errors = ticketChunk.filter((ticket) => ticket.status === "error");
|
|
530
|
+
if (errors.length > 0) {
|
|
531
|
+
const errorDetails = errors.map((e) => ({ message: e.message, details: e.details }));
|
|
532
|
+
logger.debug("[PUSH] Some notifications failed:", errorDetails);
|
|
533
|
+
}
|
|
534
|
+
if (errors.length === ticketChunk.length) {
|
|
535
|
+
throw new Error("All push notifications in chunk failed");
|
|
536
|
+
}
|
|
537
|
+
break;
|
|
538
|
+
} catch (error) {
|
|
539
|
+
const elapsed = Date.now() - startTime;
|
|
540
|
+
if (elapsed >= timeout) {
|
|
541
|
+
logger.debug("[PUSH] Timeout reached after 5 minutes, giving up on chunk");
|
|
542
|
+
break;
|
|
543
|
+
}
|
|
544
|
+
attempt++;
|
|
545
|
+
const delay = Math.min(1e3 * Math.pow(2, attempt), 3e4);
|
|
546
|
+
const remainingTime = timeout - elapsed;
|
|
547
|
+
const waitTime = Math.min(delay, remainingTime);
|
|
548
|
+
if (waitTime > 0) {
|
|
549
|
+
logger.debug(`[PUSH] Retrying in ${waitTime}ms (attempt ${attempt})`);
|
|
550
|
+
await new Promise((resolve) => setTimeout(resolve, waitTime));
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
logger.debug(`Push notifications sent successfully`);
|
|
556
|
+
}
|
|
557
|
+
/**
|
|
558
|
+
* Send a push notification to all registered devices for the user
|
|
559
|
+
* @param title - Notification title
|
|
560
|
+
* @param body - Notification body
|
|
561
|
+
* @param data - Additional data to send with the notification
|
|
562
|
+
*/
|
|
563
|
+
sendToAllDevices(title, body, data) {
|
|
564
|
+
logger.debug("[PUSH] sendToAllDevices called:", {
|
|
565
|
+
title: contentLogMetadata(title),
|
|
566
|
+
body: contentLogMetadata(body),
|
|
567
|
+
dataKeyCount: data ? Object.keys(data).length : 0
|
|
568
|
+
});
|
|
569
|
+
(async () => {
|
|
570
|
+
try {
|
|
571
|
+
logger.debug("[PUSH] Fetching push tokens...");
|
|
572
|
+
const tokens = await this.fetchPushTokens();
|
|
573
|
+
logger.debug(`[PUSH] Fetched ${tokens.length} push tokens`);
|
|
574
|
+
tokens.forEach((token, index) => {
|
|
575
|
+
logger.debug(`[PUSH] Using token ${index + 1}: id=${token.id}`);
|
|
576
|
+
});
|
|
577
|
+
if (tokens.length === 0) {
|
|
578
|
+
logger.debug("No push tokens found for user");
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
const messages = tokens.map((token, index) => {
|
|
582
|
+
logger.debug(`[PUSH] Creating message ${index + 1} for token`);
|
|
583
|
+
return {
|
|
584
|
+
to: token.token,
|
|
585
|
+
title,
|
|
586
|
+
body: body && body.length > 0 ? body : void 0,
|
|
587
|
+
data,
|
|
588
|
+
// TODO: For brutalist session artwork, attach rich media via a public HTTPS image URL.
|
|
589
|
+
// Bundled app asset paths / require(...) / local file paths will not work in push payloads.
|
|
590
|
+
// iOS also needs a Notification Service Extension to render richContent.image reliably.
|
|
591
|
+
sound: "default",
|
|
592
|
+
priority: "high"
|
|
593
|
+
};
|
|
594
|
+
});
|
|
595
|
+
logger.debug(`[PUSH] Sending ${messages.length} push notifications...`);
|
|
596
|
+
await this.sendPushNotifications(messages);
|
|
597
|
+
logger.debug("[PUSH] Push notifications sent successfully");
|
|
598
|
+
} catch (error) {
|
|
599
|
+
logger.debug("[PUSH] Error sending to all devices:", error);
|
|
600
|
+
}
|
|
601
|
+
})();
|
|
602
|
+
}
|
|
603
|
+
/**
|
|
604
|
+
* Routes session-event pushes through the server so it can apply
|
|
605
|
+
* presence-based suppression (active desktop/web, mobile foreground).
|
|
606
|
+
* Falls back to direct Expo send only when sessionId is missing — that
|
|
607
|
+
* shouldn't happen for session notifications but guards against regressions.
|
|
608
|
+
*/
|
|
609
|
+
sendSessionNotification(params) {
|
|
610
|
+
const { title, body } = getSessionNotificationCopy(params.kind, params.metadata);
|
|
611
|
+
const sessionTitle = getSessionNotificationBody(params.metadata);
|
|
612
|
+
const url = getSessionNotificationUrl(params.data);
|
|
613
|
+
const payloadData = {
|
|
614
|
+
...params.data,
|
|
615
|
+
kind: params.kind,
|
|
616
|
+
sessionTitle,
|
|
617
|
+
...url ? { url } : {}
|
|
618
|
+
};
|
|
619
|
+
const sessionId = typeof params.data?.sessionId === "string" ? params.data.sessionId : null;
|
|
620
|
+
if (!sessionId) {
|
|
621
|
+
logger.debug("[PUSH] sendSessionNotification: missing sessionId, falling back to direct send");
|
|
622
|
+
this.sendToAllDevices(title, body, payloadData);
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
void (async () => {
|
|
626
|
+
try {
|
|
627
|
+
await axios.post(
|
|
628
|
+
`${this.baseUrl}/v1/sessions/${encodeURIComponent(sessionId)}/push-event`,
|
|
629
|
+
{
|
|
630
|
+
kind: params.kind,
|
|
631
|
+
title,
|
|
632
|
+
body,
|
|
633
|
+
data: payloadData
|
|
634
|
+
},
|
|
635
|
+
{
|
|
636
|
+
headers: {
|
|
637
|
+
"Authorization": `Bearer ${this.token}`,
|
|
638
|
+
"Content-Type": "application/json",
|
|
639
|
+
"X-Happy-Client": `cli-daemon/${configuration.currentCliVersion}`
|
|
640
|
+
},
|
|
641
|
+
timeout: 15e3
|
|
642
|
+
}
|
|
643
|
+
);
|
|
644
|
+
logger.debug(`[PUSH] sendSessionNotification dispatched via server (kind=${params.kind})`);
|
|
645
|
+
} catch (error) {
|
|
646
|
+
logger.debug("[PUSH] sendSessionNotification failed:", error);
|
|
647
|
+
}
|
|
648
|
+
})();
|
|
649
|
+
}
|
|
650
|
+
/**
|
|
651
|
+
* B-510 `request_human` / `very-happy attention --session`: an awaited
|
|
652
|
+
* push-event `attention` (title "Needs you", body = the reason, data.url =
|
|
653
|
+
* panel deep link or the session). A server that predates the kind answers
|
|
654
|
+
* 400; then the same push goes out as `question`, so the person is still
|
|
655
|
+
* reached (compat matrix in specs/2026-09-machine-web-panels.md).
|
|
656
|
+
*/
|
|
657
|
+
async sendAttention(params) {
|
|
658
|
+
const post = (kind) => axios.post(
|
|
659
|
+
`${this.baseUrl}/v1/sessions/${encodeURIComponent(params.sessionId)}/push-event`,
|
|
660
|
+
{
|
|
661
|
+
kind,
|
|
662
|
+
title: getSessionNotificationTitle(kind),
|
|
663
|
+
body: params.reason,
|
|
664
|
+
data: { sessionId: params.sessionId, kind, url: params.url, reason: params.reason, ...params.sessionTitle ? { sessionTitle: params.sessionTitle } : {} }
|
|
665
|
+
},
|
|
666
|
+
{
|
|
667
|
+
headers: {
|
|
668
|
+
"Authorization": `Bearer ${this.token}`,
|
|
669
|
+
"Content-Type": "application/json",
|
|
670
|
+
"X-Happy-Client": `cli-daemon/${configuration.currentCliVersion}`
|
|
671
|
+
},
|
|
672
|
+
timeout: 15e3
|
|
673
|
+
}
|
|
674
|
+
);
|
|
675
|
+
try {
|
|
676
|
+
await post("attention");
|
|
677
|
+
return { sent: true, kind: "attention" };
|
|
678
|
+
} catch (error) {
|
|
679
|
+
if (error?.response?.status !== 400) return { sent: false, error: error?.response ? `HTTP ${error.response.status}` : String(error?.message ?? error) };
|
|
680
|
+
}
|
|
681
|
+
try {
|
|
682
|
+
await post("question");
|
|
683
|
+
return { sent: true, kind: "question" };
|
|
684
|
+
} catch (error) {
|
|
685
|
+
return { sent: false, error: error?.response ? `HTTP ${error.response.status}` : String(error?.message ?? error) };
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
export { AsyncLock as A, FileEventMessageSchema as F, MAX_CHAT_ATTACHMENT_SOURCE_BYTES as M, PushNotificationClient as P, UserMessageSchema as U, MAX_CHAT_ATTACHMENT_ENCRYPTED_BYTES as a, getSessionNotificationBody as g };
|
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var z = require('zod');
|
|
4
|
-
var index = require('./index-
|
|
4
|
+
var index = require('./index-Dv0Q48P3.cjs');
|
|
5
|
+
var axios = require('axios');
|
|
6
|
+
var logger = require('./logger-CphW5nUE.cjs');
|
|
7
|
+
var configuration = require('./configuration-CYO-kPOS.cjs');
|
|
8
|
+
var contentLogMetadata = require('./contentLogMetadata-ZSFk_NjQ.cjs');
|
|
5
9
|
|
|
6
10
|
z.z.object({
|
|
7
11
|
teamsVersion: z.z.number().int().positive().optional(),
|
|
8
12
|
teamLaunchVersion: z.z.number().int().positive().optional(),
|
|
13
|
+
/** B-689: daemon acts on the owner's team `retry-operation`. Absent on old daemons. */
|
|
14
|
+
teamRetryVersion: z.z.number().int().positive().optional(),
|
|
9
15
|
/** B-586: daemon serves the `worktree-*` RPCs. Absent on old daemons. */
|
|
10
16
|
worktreeVersion: z.z.number().int().positive().optional(),
|
|
17
|
+
/** B-688: daemon serves the `model-config-*` RPCs (harness model/provider config). */
|
|
18
|
+
modelConfigVersion: z.z.number().int().positive().optional(),
|
|
11
19
|
host: z.z.string(),
|
|
12
20
|
platform: z.z.string(),
|
|
13
21
|
happyCliVersion: z.z.string(),
|
|
@@ -256,7 +264,21 @@ z.z.object({
|
|
|
256
264
|
idleMs: z.z.number(),
|
|
257
265
|
rpcAvailable: z.z.boolean(),
|
|
258
266
|
detectedAt: z.z.number()
|
|
259
|
-
}).passthrough().optional()
|
|
267
|
+
}).passthrough().optional(),
|
|
268
|
+
/**
|
|
269
|
+
* B-510: web panels declared in this machine's local settings.json
|
|
270
|
+
* (`webPanels`), normalized by webPanels/webPanels.ts. Read-only mirror —
|
|
271
|
+
* nothing on the web/server side can write the local declaration.
|
|
272
|
+
*/
|
|
273
|
+
webPanels: z.z.object({
|
|
274
|
+
panels: z.z.array(z.z.object({
|
|
275
|
+
id: z.z.string(),
|
|
276
|
+
title: z.z.string(),
|
|
277
|
+
url: z.z.string(),
|
|
278
|
+
embed: z.z.boolean()
|
|
279
|
+
})),
|
|
280
|
+
updatedAt: z.z.number()
|
|
281
|
+
}).optional()
|
|
260
282
|
});
|
|
261
283
|
const MessageMetaSchema = z.z.object({
|
|
262
284
|
sentFrom: z.z.string().optional(),
|
|
@@ -385,8 +407,292 @@ const MAX_CHAT_ATTACHMENT_SOURCE_BYTES = 50 * 1024 * 1024;
|
|
|
385
407
|
const CHAT_ATTACHMENT_ENCRYPTION_OVERHEAD_BYTES = 40;
|
|
386
408
|
const MAX_CHAT_ATTACHMENT_ENCRYPTED_BYTES = MAX_CHAT_ATTACHMENT_SOURCE_BYTES + CHAT_ATTACHMENT_ENCRYPTION_OVERHEAD_BYTES;
|
|
387
409
|
|
|
410
|
+
function getSessionTitle(metadata) {
|
|
411
|
+
const summaryText = metadata?.summary?.text?.trim();
|
|
412
|
+
if (summaryText) {
|
|
413
|
+
return summaryText;
|
|
414
|
+
}
|
|
415
|
+
const path = metadata?.path?.trim();
|
|
416
|
+
if (!path) {
|
|
417
|
+
return "Session";
|
|
418
|
+
}
|
|
419
|
+
const segments = path.split(/[\\/]/).filter(Boolean);
|
|
420
|
+
return segments[segments.length - 1] || "Session";
|
|
421
|
+
}
|
|
422
|
+
function getSessionNotificationUrl(data) {
|
|
423
|
+
const sessionId = data?.sessionId;
|
|
424
|
+
if (typeof sessionId !== "string") {
|
|
425
|
+
return null;
|
|
426
|
+
}
|
|
427
|
+
const trimmedSessionId = sessionId.trim();
|
|
428
|
+
if (!trimmedSessionId) {
|
|
429
|
+
return null;
|
|
430
|
+
}
|
|
431
|
+
return `/session/${encodeURIComponent(trimmedSessionId)}`;
|
|
432
|
+
}
|
|
433
|
+
function getSessionNotificationTitle(kind) {
|
|
434
|
+
switch (kind) {
|
|
435
|
+
case "done":
|
|
436
|
+
return "It's ready!";
|
|
437
|
+
case "permission":
|
|
438
|
+
return "Permission request";
|
|
439
|
+
case "question":
|
|
440
|
+
return "Clarification needed";
|
|
441
|
+
case "attention":
|
|
442
|
+
return "Needs you";
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
function getSessionNotificationBody(metadata) {
|
|
446
|
+
return getSessionTitle(metadata);
|
|
447
|
+
}
|
|
448
|
+
function getSessionNotificationCopy(kind, metadata) {
|
|
449
|
+
return {
|
|
450
|
+
title: getSessionNotificationTitle(kind),
|
|
451
|
+
body: getSessionNotificationBody(metadata)
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
class PushNotificationClient {
|
|
455
|
+
token;
|
|
456
|
+
baseUrl;
|
|
457
|
+
expoLoad = null;
|
|
458
|
+
constructor(token, baseUrl = "https://api.cluster-fluster.com") {
|
|
459
|
+
this.token = token;
|
|
460
|
+
this.baseUrl = baseUrl;
|
|
461
|
+
}
|
|
462
|
+
loadExpo() {
|
|
463
|
+
if (!this.expoLoad) {
|
|
464
|
+
const load = import('expo-server-sdk').then(({ Expo }) => ({ Expo, expo: new Expo() }));
|
|
465
|
+
load.catch(() => {
|
|
466
|
+
if (this.expoLoad === load) this.expoLoad = null;
|
|
467
|
+
});
|
|
468
|
+
this.expoLoad = load;
|
|
469
|
+
}
|
|
470
|
+
return this.expoLoad;
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Fetch all push tokens for the authenticated user.
|
|
474
|
+
* Retries up to 3 times with exponential backoff on transient errors.
|
|
475
|
+
*/
|
|
476
|
+
async fetchPushTokens() {
|
|
477
|
+
const maxAttempts = 3;
|
|
478
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
479
|
+
try {
|
|
480
|
+
const response = await axios.get(
|
|
481
|
+
`${this.baseUrl}/v1/push-tokens`,
|
|
482
|
+
{
|
|
483
|
+
headers: {
|
|
484
|
+
"Authorization": `Bearer ${this.token}`,
|
|
485
|
+
"Content-Type": "application/json",
|
|
486
|
+
"X-Happy-Client": `cli-daemon/${configuration.configuration.currentCliVersion}`
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
);
|
|
490
|
+
logger.logger.debug(`Fetched ${response.data.tokens.length} push tokens`);
|
|
491
|
+
response.data.tokens.forEach((token, index) => {
|
|
492
|
+
logger.logger.debug(`[PUSH] Token ${index + 1}: id=${token.id}, created=${new Date(token.createdAt).toISOString()}, updated=${new Date(token.updatedAt).toISOString()}`);
|
|
493
|
+
});
|
|
494
|
+
return response.data.tokens;
|
|
495
|
+
} catch (error) {
|
|
496
|
+
logger.logger.debug(`[PUSH] [ERROR] Failed to fetch push tokens (attempt ${attempt}/${maxAttempts}):`, error);
|
|
497
|
+
if (attempt < maxAttempts) {
|
|
498
|
+
const delay = 1e3 * Math.pow(2, attempt - 1);
|
|
499
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
logger.logger.debug("[PUSH] [ERROR] All push token fetch attempts failed");
|
|
504
|
+
return [];
|
|
505
|
+
}
|
|
506
|
+
/**
|
|
507
|
+
* Send push notification via Expo Push API with retry
|
|
508
|
+
* @param messages - Array of push messages to send
|
|
509
|
+
*/
|
|
510
|
+
async sendPushNotifications(messages) {
|
|
511
|
+
logger.logger.debug(`Sending ${messages.length} push notifications`);
|
|
512
|
+
const { Expo, expo } = await this.loadExpo();
|
|
513
|
+
const validMessages = messages.filter((message) => {
|
|
514
|
+
if (Array.isArray(message.to)) {
|
|
515
|
+
return message.to.every((token) => Expo.isExpoPushToken(token));
|
|
516
|
+
}
|
|
517
|
+
return Expo.isExpoPushToken(message.to);
|
|
518
|
+
});
|
|
519
|
+
if (validMessages.length === 0) {
|
|
520
|
+
logger.logger.debug("No valid Expo push tokens found");
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
const chunks = expo.chunkPushNotifications(validMessages);
|
|
524
|
+
for (const chunk of chunks) {
|
|
525
|
+
const startTime = Date.now();
|
|
526
|
+
const timeout = 3e5;
|
|
527
|
+
let attempt = 0;
|
|
528
|
+
while (true) {
|
|
529
|
+
try {
|
|
530
|
+
const ticketChunk = await expo.sendPushNotificationsAsync(chunk);
|
|
531
|
+
const errors = ticketChunk.filter((ticket) => ticket.status === "error");
|
|
532
|
+
if (errors.length > 0) {
|
|
533
|
+
const errorDetails = errors.map((e) => ({ message: e.message, details: e.details }));
|
|
534
|
+
logger.logger.debug("[PUSH] Some notifications failed:", errorDetails);
|
|
535
|
+
}
|
|
536
|
+
if (errors.length === ticketChunk.length) {
|
|
537
|
+
throw new Error("All push notifications in chunk failed");
|
|
538
|
+
}
|
|
539
|
+
break;
|
|
540
|
+
} catch (error) {
|
|
541
|
+
const elapsed = Date.now() - startTime;
|
|
542
|
+
if (elapsed >= timeout) {
|
|
543
|
+
logger.logger.debug("[PUSH] Timeout reached after 5 minutes, giving up on chunk");
|
|
544
|
+
break;
|
|
545
|
+
}
|
|
546
|
+
attempt++;
|
|
547
|
+
const delay = Math.min(1e3 * Math.pow(2, attempt), 3e4);
|
|
548
|
+
const remainingTime = timeout - elapsed;
|
|
549
|
+
const waitTime = Math.min(delay, remainingTime);
|
|
550
|
+
if (waitTime > 0) {
|
|
551
|
+
logger.logger.debug(`[PUSH] Retrying in ${waitTime}ms (attempt ${attempt})`);
|
|
552
|
+
await new Promise((resolve) => setTimeout(resolve, waitTime));
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
logger.logger.debug(`Push notifications sent successfully`);
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* Send a push notification to all registered devices for the user
|
|
561
|
+
* @param title - Notification title
|
|
562
|
+
* @param body - Notification body
|
|
563
|
+
* @param data - Additional data to send with the notification
|
|
564
|
+
*/
|
|
565
|
+
sendToAllDevices(title, body, data) {
|
|
566
|
+
logger.logger.debug("[PUSH] sendToAllDevices called:", {
|
|
567
|
+
title: contentLogMetadata.contentLogMetadata(title),
|
|
568
|
+
body: contentLogMetadata.contentLogMetadata(body),
|
|
569
|
+
dataKeyCount: data ? Object.keys(data).length : 0
|
|
570
|
+
});
|
|
571
|
+
(async () => {
|
|
572
|
+
try {
|
|
573
|
+
logger.logger.debug("[PUSH] Fetching push tokens...");
|
|
574
|
+
const tokens = await this.fetchPushTokens();
|
|
575
|
+
logger.logger.debug(`[PUSH] Fetched ${tokens.length} push tokens`);
|
|
576
|
+
tokens.forEach((token, index) => {
|
|
577
|
+
logger.logger.debug(`[PUSH] Using token ${index + 1}: id=${token.id}`);
|
|
578
|
+
});
|
|
579
|
+
if (tokens.length === 0) {
|
|
580
|
+
logger.logger.debug("No push tokens found for user");
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
const messages = tokens.map((token, index) => {
|
|
584
|
+
logger.logger.debug(`[PUSH] Creating message ${index + 1} for token`);
|
|
585
|
+
return {
|
|
586
|
+
to: token.token,
|
|
587
|
+
title,
|
|
588
|
+
body: body && body.length > 0 ? body : void 0,
|
|
589
|
+
data,
|
|
590
|
+
// TODO: For brutalist session artwork, attach rich media via a public HTTPS image URL.
|
|
591
|
+
// Bundled app asset paths / require(...) / local file paths will not work in push payloads.
|
|
592
|
+
// iOS also needs a Notification Service Extension to render richContent.image reliably.
|
|
593
|
+
sound: "default",
|
|
594
|
+
priority: "high"
|
|
595
|
+
};
|
|
596
|
+
});
|
|
597
|
+
logger.logger.debug(`[PUSH] Sending ${messages.length} push notifications...`);
|
|
598
|
+
await this.sendPushNotifications(messages);
|
|
599
|
+
logger.logger.debug("[PUSH] Push notifications sent successfully");
|
|
600
|
+
} catch (error) {
|
|
601
|
+
logger.logger.debug("[PUSH] Error sending to all devices:", error);
|
|
602
|
+
}
|
|
603
|
+
})();
|
|
604
|
+
}
|
|
605
|
+
/**
|
|
606
|
+
* Routes session-event pushes through the server so it can apply
|
|
607
|
+
* presence-based suppression (active desktop/web, mobile foreground).
|
|
608
|
+
* Falls back to direct Expo send only when sessionId is missing — that
|
|
609
|
+
* shouldn't happen for session notifications but guards against regressions.
|
|
610
|
+
*/
|
|
611
|
+
sendSessionNotification(params) {
|
|
612
|
+
const { title, body } = getSessionNotificationCopy(params.kind, params.metadata);
|
|
613
|
+
const sessionTitle = getSessionNotificationBody(params.metadata);
|
|
614
|
+
const url = getSessionNotificationUrl(params.data);
|
|
615
|
+
const payloadData = {
|
|
616
|
+
...params.data,
|
|
617
|
+
kind: params.kind,
|
|
618
|
+
sessionTitle,
|
|
619
|
+
...url ? { url } : {}
|
|
620
|
+
};
|
|
621
|
+
const sessionId = typeof params.data?.sessionId === "string" ? params.data.sessionId : null;
|
|
622
|
+
if (!sessionId) {
|
|
623
|
+
logger.logger.debug("[PUSH] sendSessionNotification: missing sessionId, falling back to direct send");
|
|
624
|
+
this.sendToAllDevices(title, body, payloadData);
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
void (async () => {
|
|
628
|
+
try {
|
|
629
|
+
await axios.post(
|
|
630
|
+
`${this.baseUrl}/v1/sessions/${encodeURIComponent(sessionId)}/push-event`,
|
|
631
|
+
{
|
|
632
|
+
kind: params.kind,
|
|
633
|
+
title,
|
|
634
|
+
body,
|
|
635
|
+
data: payloadData
|
|
636
|
+
},
|
|
637
|
+
{
|
|
638
|
+
headers: {
|
|
639
|
+
"Authorization": `Bearer ${this.token}`,
|
|
640
|
+
"Content-Type": "application/json",
|
|
641
|
+
"X-Happy-Client": `cli-daemon/${configuration.configuration.currentCliVersion}`
|
|
642
|
+
},
|
|
643
|
+
timeout: 15e3
|
|
644
|
+
}
|
|
645
|
+
);
|
|
646
|
+
logger.logger.debug(`[PUSH] sendSessionNotification dispatched via server (kind=${params.kind})`);
|
|
647
|
+
} catch (error) {
|
|
648
|
+
logger.logger.debug("[PUSH] sendSessionNotification failed:", error);
|
|
649
|
+
}
|
|
650
|
+
})();
|
|
651
|
+
}
|
|
652
|
+
/**
|
|
653
|
+
* B-510 `request_human` / `very-happy attention --session`: an awaited
|
|
654
|
+
* push-event `attention` (title "Needs you", body = the reason, data.url =
|
|
655
|
+
* panel deep link or the session). A server that predates the kind answers
|
|
656
|
+
* 400; then the same push goes out as `question`, so the person is still
|
|
657
|
+
* reached (compat matrix in specs/2026-09-machine-web-panels.md).
|
|
658
|
+
*/
|
|
659
|
+
async sendAttention(params) {
|
|
660
|
+
const post = (kind) => axios.post(
|
|
661
|
+
`${this.baseUrl}/v1/sessions/${encodeURIComponent(params.sessionId)}/push-event`,
|
|
662
|
+
{
|
|
663
|
+
kind,
|
|
664
|
+
title: getSessionNotificationTitle(kind),
|
|
665
|
+
body: params.reason,
|
|
666
|
+
data: { sessionId: params.sessionId, kind, url: params.url, reason: params.reason, ...params.sessionTitle ? { sessionTitle: params.sessionTitle } : {} }
|
|
667
|
+
},
|
|
668
|
+
{
|
|
669
|
+
headers: {
|
|
670
|
+
"Authorization": `Bearer ${this.token}`,
|
|
671
|
+
"Content-Type": "application/json",
|
|
672
|
+
"X-Happy-Client": `cli-daemon/${configuration.configuration.currentCliVersion}`
|
|
673
|
+
},
|
|
674
|
+
timeout: 15e3
|
|
675
|
+
}
|
|
676
|
+
);
|
|
677
|
+
try {
|
|
678
|
+
await post("attention");
|
|
679
|
+
return { sent: true, kind: "attention" };
|
|
680
|
+
} catch (error) {
|
|
681
|
+
if (error?.response?.status !== 400) return { sent: false, error: error?.response ? `HTTP ${error.response.status}` : String(error?.message ?? error) };
|
|
682
|
+
}
|
|
683
|
+
try {
|
|
684
|
+
await post("question");
|
|
685
|
+
return { sent: true, kind: "question" };
|
|
686
|
+
} catch (error) {
|
|
687
|
+
return { sent: false, error: error?.response ? `HTTP ${error.response.status}` : String(error?.message ?? error) };
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
|
|
388
692
|
exports.AsyncLock = AsyncLock;
|
|
389
693
|
exports.FileEventMessageSchema = FileEventMessageSchema;
|
|
390
694
|
exports.MAX_CHAT_ATTACHMENT_ENCRYPTED_BYTES = MAX_CHAT_ATTACHMENT_ENCRYPTED_BYTES;
|
|
391
695
|
exports.MAX_CHAT_ATTACHMENT_SOURCE_BYTES = MAX_CHAT_ATTACHMENT_SOURCE_BYTES;
|
|
696
|
+
exports.PushNotificationClient = PushNotificationClient;
|
|
392
697
|
exports.UserMessageSchema = UserMessageSchema;
|
|
698
|
+
exports.getSessionNotificationBody = getSessionNotificationBody;
|