very-happy-cli 0.2.159 → 0.2.161
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-VsfkZiVF.mjs → AcpBackend-6U6pQgx1.mjs} +4 -2
- package/dist/{AcpBackend-DUYFlyfN.cjs → AcpBackend-Ba98QDif.cjs} +78 -76
- package/dist/{AcpSessionManager-Bdv8LWXh.cjs → AcpSessionManager-BAY1Pggx.cjs} +9 -9
- package/dist/{AcpSessionManager-rYtVb0kN.mjs → AcpSessionManager-CoMWFVw3.mjs} +1 -1
- package/dist/BasePermissionHandler-2R0uH2CI.cjs +157 -0
- package/dist/BasePermissionHandler-BvvhBTja.mjs +155 -0
- package/dist/BaseReasoningProcessor-DyhO1uoU.mjs +198 -0
- package/dist/BaseReasoningProcessor-fsVNmCEF.cjs +200 -0
- package/dist/RemoteModeDisplay-D-HewyXg.mjs +150 -0
- package/dist/RemoteModeDisplay-NC1gPHtj.cjs +153 -0
- package/dist/agentAttachments-Dbw8iMCZ.cjs +20 -0
- package/dist/agentAttachments-M_B5qEF2.mjs +18 -0
- package/dist/agentGuidance-B5NATuFa.cjs +28 -0
- package/dist/agentGuidance-CQuNlNx8.mjs +20 -0
- package/dist/{agentHome-CvsJyeQB.mjs → agentHome-Ck1i1TQB.mjs} +8 -22
- package/dist/{agentHome-CHh4lmjM.cjs → agentHome-DvNnGphb.cjs} +14 -28
- package/dist/api-B2vLMS-z.cjs +3346 -0
- package/dist/api-SffjRQ1b.mjs +3339 -0
- package/dist/{types-Dh-s4Lqs.mjs → apiMachine-BpW9t8MO.mjs} +9116 -16205
- package/dist/{types-BbvK1Tr_.cjs → apiMachine-gFA54X7y.cjs} +9104 -16247
- package/dist/assistantTools-BfA0R3_m.cjs +380 -0
- package/dist/assistantTools-CgiTbnWb.mjs +376 -0
- package/dist/attachmentContent-CSGxr71v.mjs +82 -0
- package/dist/attachmentContent-DMMZzevR.cjs +88 -0
- package/dist/attachmentLimits-CzVrqgLR.cjs +354 -0
- package/dist/attachmentLimits-yba1uHFO.mjs +348 -0
- package/dist/auth-Bt4ceBBy.cjs +227 -0
- package/dist/auth-CYHjQ8oQ.mjs +225 -0
- package/dist/auth-DdyVZSo6.mjs +218 -0
- package/dist/auth-x4CEoysH.cjs +220 -0
- package/dist/authRelay-BdYkC1Po.cjs +156 -0
- package/dist/authRelay-DSAvg1uv.mjs +143 -0
- package/dist/{auto-DKmEls8Z.cjs → auto-BDXDUnjB.cjs} +70 -79
- package/dist/{auto-nb8GhRRy.mjs → auto-CXLlRi3a.mjs} +27 -36
- package/dist/backgroundTaskActivity-8PdPxrvy.mjs +149 -0
- package/dist/backgroundTaskActivity-CBvUdj39.cjs +157 -0
- package/dist/bootstrap-BNQq05jR.mjs +112 -0
- package/dist/bootstrap-CkgIq8N3.cjs +116 -0
- package/dist/browser-9pyhQr1R.mjs +20 -0
- package/dist/browser-BDOlKWYe.cjs +22 -0
- package/dist/claudeCliPath-BqkaH2oR.mjs +8 -0
- package/dist/claudeCliPath-U4HHmV4r.cjs +10 -0
- package/dist/claudeLocal-BDuGFnjJ.cjs +343 -0
- package/dist/claudeLocal-CQa8qnwq.mjs +339 -0
- package/dist/claudeLocalLauncher-Bd5I2-j6.cjs +154 -0
- package/dist/claudeLocalLauncher-CDqQI9ak.mjs +152 -0
- package/dist/cliUpdate-DsKXPFYp.cjs +120 -0
- package/dist/cliUpdate-Y34VGe9Y.mjs +112 -0
- package/dist/client-Brn-UwVk.mjs +100 -0
- package/dist/client-CXb1VJui.mjs +112 -0
- package/dist/client-C_axwRry.cjs +104 -0
- package/dist/client-DOBWJL3u.cjs +119 -0
- package/dist/codex/happyMcpStdioBridge.cjs +33 -14
- package/dist/codex/happyMcpStdioBridge.mjs +26 -7
- package/dist/codexAppServerClient-ZzhUTWPo.cjs +1090 -0
- package/dist/codexAppServerClient-oeQKD6Qf.mjs +1088 -0
- package/dist/codexCommand-C7H0NpBe.cjs +2088 -0
- package/dist/codexCommand-CJ82bl3l.mjs +2086 -0
- package/dist/codexThreadFork-BCaEMBiM.mjs +338 -0
- package/dist/codexThreadFork-BZqF-9bX.cjs +350 -0
- package/dist/{config-ClLtM6F3.mjs → config-BglnktIo.mjs} +7 -40
- package/dist/{config-DLx-vW3I.cjs → config-CYpL1Ahd.cjs} +26 -59
- package/dist/configuration-DoWFJUPm.mjs +170 -0
- package/dist/configuration-v9f8LM9T.cjs +181 -0
- package/dist/connect-hgcNjyAB.mjs +682 -0
- package/dist/connect-yEvjdRvx.cjs +684 -0
- package/dist/constants-BC4St0xd.mjs +11 -0
- package/dist/constants-VJ0N6WuG.cjs +17 -0
- package/dist/contentLogMetadata-BlWI0egu.mjs +51 -0
- package/dist/contentLogMetadata-ZSFk_NjQ.cjs +54 -0
- package/dist/context-B45vMtSZ.cjs +69 -0
- package/dist/context-C9GHPLEm.mjs +65 -0
- package/dist/controlClient-DjoKTtgK.mjs +260 -0
- package/dist/controlClient-fEHE85bH.cjs +279 -0
- package/dist/createSessionMetadata-BRLB9CNO.cjs +58 -0
- package/dist/createSessionMetadata-mIeehclp.mjs +54 -0
- package/dist/defaultPermissionMode-BnxJPVHT.mjs +4 -0
- package/dist/defaultPermissionMode-CxmTXUQR.cjs +7 -0
- package/dist/detectCLI-CSGMlV51.mjs +58 -0
- package/dist/detectCLI-DR9ZaN6B.cjs +61 -0
- package/dist/deterministicJson-DKDP_mM-.cjs +507 -0
- package/dist/deterministicJson-p835AdAU.mjs +502 -0
- package/dist/doctor-BPkYWSuq.mjs +75 -0
- package/dist/doctor-BeUuQXFs.mjs +391 -0
- package/dist/doctor-hVNnSZJn.cjs +395 -0
- package/dist/doctor-u46qYYKX.cjs +79 -0
- package/dist/doctorReadiness-BCZGcN0a.cjs +151 -0
- package/dist/doctorReadiness-C208SMN9.mjs +142 -0
- package/dist/editPaths-BVfS_FFL.mjs +68 -0
- package/dist/editPaths-Bqp4OFPw.cjs +73 -0
- package/dist/ensureDaemonRunning-CO7d1gOC.mjs +61 -0
- package/dist/ensureDaemonRunning-Dg9aZfjs.cjs +64 -0
- package/dist/handleResumeCommand-BiTPK0qd.mjs +240 -0
- package/dist/handleResumeCommand-DnVmy7a3.cjs +245 -0
- package/dist/ids-B6ZC0HGt.cjs +13 -0
- package/dist/ids-DU6sLx4C.mjs +10 -0
- package/dist/index-B6UGRQ7h.mjs +102 -0
- package/dist/{index-2mCaeKyf.mjs → index-CAFwetLk.mjs} +68 -38
- package/dist/index-Cqx-Raq6.mjs +68 -0
- package/dist/index-D3kI0hDR.cjs +107 -0
- package/dist/index-DA7yINuk.cjs +994 -0
- package/dist/{index-BEOMECtE.cjs → index-LIn6nCcq.cjs} +121 -91
- package/dist/index-Pf2cV9cn.cjs +182 -0
- package/dist/index-SPGPenK5.mjs +158 -0
- package/dist/index-gNTWtSKN.cjs +71 -0
- package/dist/index-qdS1friN.mjs +949 -0
- package/dist/index.cjs +5 -46
- package/dist/index.mjs +5 -46
- package/dist/{install-BPhJppvd.mjs → install-BO_W7VhL.mjs} +37 -37
- package/dist/install-Bu85vlHD.cjs +88 -0
- package/dist/install-ClUUl3yp.mjs +86 -0
- package/dist/{install-BOHrUaFM.cjs → install-Cs5qAPrB.cjs} +38 -38
- package/dist/{installPiTools-DuZ6jZvX.cjs → installPiTools-BKnXjrVu.cjs} +2 -3
- package/dist/{installPiTools-Cj7yU9Fc.mjs → installPiTools-B_CdM5_2.mjs} +1 -2
- package/dist/{installTerminalHooks-Cou3WUf3.cjs → installTerminalHooks-BQZbvxsZ.cjs} +5 -40
- package/dist/{installTerminalHooks-WeznjkuC.mjs → installTerminalHooks-By7gBMEF.mjs} +3 -38
- package/dist/lib.cjs +28 -19
- package/dist/lib.d.cts +12 -2
- package/dist/lib.d.mts +12 -2
- package/dist/lib.mjs +23 -14
- package/dist/lidState-C2dI0FAN.mjs +297 -0
- package/dist/lidState-KHHu1015.cjs +305 -0
- package/dist/limits-C-a7gr04.cjs +43 -0
- package/dist/limits-DIo7rEBR.mjs +36 -0
- package/dist/localHappyAgentAuth-BXiHPXdC.cjs +86 -0
- package/dist/localHappyAgentAuth-DCsetiyG.mjs +82 -0
- package/dist/logger-Bc8-u1w0.cjs +275 -0
- package/dist/logger-fXoNzNuB.mjs +271 -0
- package/dist/loginShellEnv-BkuYtExZ.cjs +182 -0
- package/dist/loginShellEnv-Dw8M7tkI.mjs +167 -0
- package/dist/machineMetadata-BHjehxJY.mjs +38 -0
- package/dist/machineMetadata-BibDMFVx.cjs +41 -0
- package/dist/main-BktM4A1K.mjs +939 -0
- package/dist/main-CLaX-t-b.cjs +941 -0
- package/dist/manager-BQ4flvQZ.mjs +101 -0
- package/dist/manager-Cly8xVIR.cjs +105 -0
- package/dist/{mcp-Dy3f_K0h.mjs → mcp-CeUd9KDr.mjs} +27 -33
- package/dist/{mcp-CPYbtsYG.cjs → mcp-GBfE70nk.cjs} +42 -48
- package/dist/messageBuffer-BWlGgR3A.mjs +71 -0
- package/dist/messageBuffer-fJMH6uY8.cjs +73 -0
- package/dist/mirrorProtocol-BNLtoWjO.mjs +170 -0
- package/dist/mirrorProtocol-BUtmg1ws.cjs +186 -0
- package/dist/oneShotPrompts-CL0dip9C.cjs +16 -0
- package/dist/oneShotPrompts-DT4p6RNd.mjs +12 -0
- package/dist/package-Cl5BEP5x.cjs +173 -0
- package/dist/package-uOg9SCKi.mjs +171 -0
- package/dist/path-B7zW-WwE.cjs +18 -0
- package/dist/path-D2wdaNSk.mjs +15 -0
- package/dist/peerTools-CIeiy9wn.cjs +283 -0
- package/dist/peerTools-Dz3_7Bkk.mjs +272 -0
- package/dist/permissionMode-CH4FoZfU.mjs +80 -0
- package/dist/permissionMode-CrV-XuMs.cjs +87 -0
- package/dist/persistence-CBNMzSa8.cjs +414 -0
- package/dist/persistence-oET5n61f.mjs +372 -0
- package/dist/piExtension.mjs +2 -1
- package/dist/piRunnerArgs-Bf7RUF__.cjs +66 -0
- package/dist/piRunnerArgs-Dk1ncwHF.mjs +59 -0
- package/dist/{piRuntime-NtF0YqjH.cjs → piRuntime-B_6TE9Eb.cjs} +4 -4
- package/dist/{piRuntime-kLZNpAAl.mjs → piRuntime-BwRdhK_N.mjs} +2 -2
- package/dist/{piTerminal-B0fEOV_2.mjs → piTerminal-Bap3R8Re.mjs} +32 -35
- package/dist/{piTerminal-9J0GAKzb.cjs → piTerminal-CagmEoez.cjs} +32 -35
- package/dist/previewPath-B0rBdhy1.cjs +83 -0
- package/dist/previewPath-CXN4Vag0.mjs +81 -0
- package/dist/projectPath-BTBIMB57.mjs +10 -0
- package/dist/projectPath-ChLhdOyt.cjs +13 -0
- package/dist/registerCommonHandlers-BjSV2Skz.cjs +538 -0
- package/dist/registerCommonHandlers-CrgvJsOy.mjs +536 -0
- package/dist/registerKillSessionHandler-6TC2S3lf.cjs +366 -0
- package/dist/registerKillSessionHandler-Cbc8Rgtd.mjs +363 -0
- package/dist/remoteSessionClient-BNx78NLb.cjs +634 -0
- package/dist/remoteSessionClient-CO7qOjR0.mjs +612 -0
- package/dist/resources-BdFBQi8r.mjs +117 -0
- package/dist/resources-JDPugcDq.cjs +120 -0
- package/dist/run-2l5vSt2h.mjs +5743 -0
- package/dist/run-D5xAEWSB.cjs +5766 -0
- package/dist/runClaude-BX6ixjtW.cjs +894 -0
- package/dist/runClaude-BgN6xo05.mjs +892 -0
- package/dist/runClaudeDeps-Bp20MScE.cjs +4168 -0
- package/dist/runClaudeDeps-COBBhD9o.mjs +4129 -0
- package/dist/{runGemini-F-mqx3_K.cjs → runGemini-CSMPkt0p.cjs} +202 -175
- package/dist/{runGemini-DV6Ouadt.mjs → runGemini-Cc2jq-uh.mjs} +58 -31
- package/dist/{runOpenClaw-D7MlPS5n.mjs → runOpenClaw-CIlCwdW2.mjs} +31 -32
- package/dist/{runOpenClaw-Qqo9j63M.cjs → runOpenClaw-YBwua4Rf.cjs} +64 -65
- package/dist/sandbox-C4gXLP1p.cjs +187 -0
- package/dist/sandbox-CNkwOtHI.mjs +180 -0
- package/dist/selfCheck-CU8xSDsl.cjs +87 -0
- package/dist/selfCheck-hJ6PCh7v.mjs +84 -0
- package/dist/{send-CLhYXNgy.mjs → send-CGHcKZ-w.mjs} +12 -5
- package/dist/{send-4qJTCAYW.cjs → send-DY9Nr-x4.cjs} +16 -9
- package/dist/server-II51Mimf.cjs +407 -0
- package/dist/server-m2b5pamZ.mjs +404 -0
- package/dist/sessionLock-7ePAYDOg.cjs +205 -0
- package/dist/sessionLock-Cw6FmbMv.mjs +197 -0
- package/dist/sessionModeFile-BVCV5f7E.cjs +36 -0
- package/dist/sessionModeFile-CDmDyRkD.mjs +32 -0
- package/dist/sessionOps-C_f1wKOr.mjs +760 -0
- package/dist/sessionOps-DJdExmBy.cjs +783 -0
- package/dist/sessionScanner-CaAHLyNV.mjs +206 -0
- package/dist/sessionScanner-D1goKdd3.cjs +209 -0
- package/dist/{sessions-Bh1pyddi.cjs → sessions-DGs6ALNo.cjs} +37 -28
- package/dist/{sessions-CzIH2rGp.mjs → sessions-DnvmWUel.mjs} +17 -8
- package/dist/setupOfflineReconnection-CVViqZft.mjs +72 -0
- package/dist/setupOfflineReconnection-R7XuR_sj.cjs +74 -0
- package/dist/smallFastModel-BLNgMrwR.mjs +18 -0
- package/dist/smallFastModel-D_uxGMo3.cjs +21 -0
- package/dist/{spawn-CmFOSVdW.cjs → spawn-CXTH7qg1.cjs} +61 -65
- package/dist/{spawn-CPIWlPOM.mjs → spawn-DNYSvFtC.mjs} +35 -40
- package/dist/spawnAgents-BGQiEF-w.cjs +52 -0
- package/dist/spawnAgents-DZzZ9jMI.mjs +45 -0
- package/dist/spawnHappyCLI-BhYj84rv.cjs +66 -0
- package/dist/spawnHappyCLI-cqWQbwsb.mjs +64 -0
- package/dist/specialCommands-BSqhYg5X.mjs +52 -0
- package/dist/specialCommands-DSsTeRej.cjs +54 -0
- package/dist/startFileWatcher-CKKx-8ZO.cjs +71 -0
- package/dist/startFileWatcher-CdegMbJj.mjs +69 -0
- package/dist/startupPlan-BLMbfQIH.cjs +12 -0
- package/dist/startupPlan-DFMyNgAs.mjs +10 -0
- package/dist/streamRelay-BiKw59Cc.cjs +233 -0
- package/dist/streamRelay-FPVbHpbM.mjs +231 -0
- package/dist/sync-CVk4QERO.cjs +71 -0
- package/dist/sync-CxD-w_40.mjs +69 -0
- package/dist/systemPrompt-BygJcmbi.cjs +97 -0
- package/dist/systemPrompt-DmtypUc7.mjs +94 -0
- package/dist/{teams-CEahj4cm.mjs → teams-BOC6nIPL.mjs} +9 -40
- package/dist/{teams-CB1R9-Z9.cjs → teams-BsvpOBfn.cjs} +14 -45
- package/dist/template-BaHAmH2K.cjs +129 -0
- package/dist/template-CiOPa2rd.mjs +121 -0
- package/dist/{terminalTitleSuggest-DKgX3osW.mjs → terminalTitleSuggest-CeKsX_V6.mjs} +1 -9
- package/dist/{terminalTitleSuggest-Dji8I0Jy.cjs → terminalTitleSuggest-DhvRFX3H.cjs} +0 -10
- package/dist/terminals-B_bfC9c2.cjs +250 -0
- package/dist/terminals-Dlg0x3nY.mjs +235 -0
- package/dist/time-BZNdNiIV.mjs +38 -0
- package/dist/time-CNRE4ChD.cjs +42 -0
- package/dist/titleGenerator-CFRhU-xq.cjs +172 -0
- package/dist/titleGenerator-ox6ptCsp.mjs +166 -0
- package/dist/tmuxSocket-B8Yx20sT.mjs +19 -0
- package/dist/tmuxSocket-D-Xt6Vn-.cjs +24 -0
- package/dist/{todo-DqyRI4wP.cjs → todo-B-KB7oAB.cjs} +15 -12
- package/dist/{todo-Cknp5D4U.mjs → todo-C5KBVbUw.mjs} +11 -8
- package/dist/tools-BZabNIid.mjs +177 -0
- package/dist/tools-BmQQUATY.mjs +60 -0
- package/dist/tools-CsSovyNp.cjs +180 -0
- package/dist/tools-lMDra4Pv.cjs +63 -0
- package/dist/trimIdent-4gvk9Wk6.cjs +22 -0
- package/dist/trimIdent-BAjmv_TG.mjs +20 -0
- package/dist/types-CRS-srZK.cjs +66 -0
- package/dist/types-CkqAACXx.mjs +64 -0
- package/dist/uninstall-B1WLtC1d.mjs +47 -0
- package/dist/uninstall-fvA5aWpU.cjs +49 -0
- package/package.json +7 -7
- package/scripts/session_hook_forwarder.cjs +9 -2
- package/dist/agentGuidance-BRDN1NoZ.cjs +0 -2217
- package/dist/agentGuidance-KgvYuF3R.mjs +0 -2145
- package/dist/index-BF6WfgV5.mjs +0 -19624
- package/dist/index-C-JazrMB.mjs +0 -52
- package/dist/index-blDSadUR.cjs +0 -58
- package/dist/index-rH3DrQ5B.cjs +0 -19688
- package/dist/limits-BH5CV5vR.cjs +0 -2683
- package/dist/limits-CjRj8IX5.mjs +0 -2558
|
@@ -0,0 +1,612 @@
|
|
|
1
|
+
import { r as resumeDaemonSession, l as listDaemonSessions } from './controlClient-DjoKTtgK.mjs';
|
|
2
|
+
import { d as archiveSession, u as unarchiveSession, h as sessionLiveness, g as sendUserMessage, i as readSessionState, o as openUserScopedSocket } from './sessionOps-C_f1wKOr.mjs';
|
|
3
|
+
import { d as delay } from './time-BZNdNiIV.mjs';
|
|
4
|
+
import axios from 'axios';
|
|
5
|
+
import { hostname } from 'node:os';
|
|
6
|
+
import { c as configuration } from './configuration-DoWFJUPm.mjs';
|
|
7
|
+
import { readSettings, readCredentialsForConfiguredRelay } from './persistence-oET5n61f.mjs';
|
|
8
|
+
import { randomUUID } from 'node:crypto';
|
|
9
|
+
|
|
10
|
+
const ACTIVE_FRESH_WINDOW_MS = 15 * 60 * 1e3;
|
|
11
|
+
const DEFAULT_RESUME_WAIT_MS = 3e4;
|
|
12
|
+
const DEFAULT_RESUME_POLL_MS = 1e3;
|
|
13
|
+
function classifySessionState(input) {
|
|
14
|
+
const { liveHere, server, now } = input;
|
|
15
|
+
if (!server.found) return "not_found";
|
|
16
|
+
if (typeof server.archivedAt === "number") return "archived";
|
|
17
|
+
if (liveHere) return "live";
|
|
18
|
+
if (server.active && typeof server.activeAt === "number" && now - server.activeAt <= ACTIVE_FRESH_WINDOW_MS) return "live";
|
|
19
|
+
return "offline";
|
|
20
|
+
}
|
|
21
|
+
function explainNotLive(sessionId, status, resumeHint) {
|
|
22
|
+
switch (status) {
|
|
23
|
+
case "archived":
|
|
24
|
+
return `Session ${sessionId} is archived \u2014 no live wrapper will receive this message. ${resumeHint}`;
|
|
25
|
+
case "offline":
|
|
26
|
+
return `Session ${sessionId} is offline (its wrapper exited or timed out) \u2014 no live wrapper will receive this message. ${resumeHint}`;
|
|
27
|
+
case "not_found":
|
|
28
|
+
return `Session ${sessionId} was not found on this account \u2014 it cannot be messaged or resumed. Spawn a new session instead.`;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const CLI_RESUME_HINT = "Use --resume to bring it back on this machine, or spawn a new session.";
|
|
32
|
+
const defaultDeps$1 = {
|
|
33
|
+
listLive: () => listDaemonSessions(),
|
|
34
|
+
readState: (sessionId, persisted) => readSessionState(sessionId, persisted),
|
|
35
|
+
send: (sessionId, persisted, text, client, options) => sendUserMessage(sessionId, persisted, text, client, options),
|
|
36
|
+
unarchive: unarchiveSession,
|
|
37
|
+
archive: archiveSession,
|
|
38
|
+
resume: resumeDaemonSession,
|
|
39
|
+
sleep: (ms) => delay(ms).then(() => void 0),
|
|
40
|
+
now: () => Date.now()
|
|
41
|
+
};
|
|
42
|
+
async function deliverToSession(sessionId, persisted, text, client, options = {}, deps = {}) {
|
|
43
|
+
const d = { ...defaultDeps$1, ...deps };
|
|
44
|
+
const resumeHint = options.resumeHint ?? CLI_RESUME_HINT;
|
|
45
|
+
const classify = async () => {
|
|
46
|
+
const [live, server] = await Promise.all([d.listLive(), d.readState(sessionId, persisted)]);
|
|
47
|
+
return classifySessionState({ liveHere: sessionLiveness(live, sessionId).live, server, now: d.now() });
|
|
48
|
+
};
|
|
49
|
+
let status = await classify();
|
|
50
|
+
let resumed = false;
|
|
51
|
+
let resumeOutcome;
|
|
52
|
+
if (status !== "live") {
|
|
53
|
+
if (!options.resume || status === "not_found") {
|
|
54
|
+
return { sessionId, status, delivered: false, resumed: false, stored: false, error: explainNotLive(sessionId, status, resumeHint) };
|
|
55
|
+
}
|
|
56
|
+
const wasArchived = status === "archived";
|
|
57
|
+
if (wasArchived) {
|
|
58
|
+
try {
|
|
59
|
+
await d.unarchive(sessionId);
|
|
60
|
+
} catch (error) {
|
|
61
|
+
const message = `unarchive failed: ${error instanceof Error ? error.message : String(error)}`;
|
|
62
|
+
return { sessionId, status, delivered: false, resumed: false, stored: false, error: message, resume: { ok: false, error: message } };
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const outcome = await d.resume(sessionId, {
|
|
66
|
+
...typeof options.model === "string" ? { model: options.model } : {},
|
|
67
|
+
...options.permissionMode !== void 0 ? { permissionMode: options.permissionMode } : {}
|
|
68
|
+
});
|
|
69
|
+
if (!outcome.success) {
|
|
70
|
+
let message = outcome.error ?? "resume failed";
|
|
71
|
+
if (wasArchived) {
|
|
72
|
+
try {
|
|
73
|
+
await d.archive(sessionId);
|
|
74
|
+
} catch (error) {
|
|
75
|
+
message += `; failed to restore archive state: ${error instanceof Error ? error.message : String(error)}`;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return { sessionId, status, delivered: false, resumed: false, stored: false, error: message, resume: { ok: false, error: message } };
|
|
79
|
+
}
|
|
80
|
+
resumed = true;
|
|
81
|
+
resumeOutcome = { ok: true };
|
|
82
|
+
status = await waitUntilLive(classify, d, options);
|
|
83
|
+
if (status !== "live") {
|
|
84
|
+
const waited = Math.round((options.waitMs ?? DEFAULT_RESUME_WAIT_MS) / 1e3);
|
|
85
|
+
return {
|
|
86
|
+
sessionId,
|
|
87
|
+
status,
|
|
88
|
+
delivered: false,
|
|
89
|
+
resumed,
|
|
90
|
+
stored: false,
|
|
91
|
+
resume: resumeOutcome,
|
|
92
|
+
error: `Session ${sessionId} was resumed but no live wrapper showed up within ${waited}s (state: ${status}); message not sent`
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
await d.send(sessionId, persisted, text, client, {
|
|
97
|
+
...options.model !== void 0 ? { model: options.model } : {},
|
|
98
|
+
...options.sentFrom !== void 0 ? { sentFrom: options.sentFrom } : {}
|
|
99
|
+
});
|
|
100
|
+
let after = "live";
|
|
101
|
+
try {
|
|
102
|
+
after = await classify();
|
|
103
|
+
} catch {
|
|
104
|
+
after = "live";
|
|
105
|
+
}
|
|
106
|
+
if (after !== "live") {
|
|
107
|
+
return {
|
|
108
|
+
sessionId,
|
|
109
|
+
status: after,
|
|
110
|
+
delivered: false,
|
|
111
|
+
resumed,
|
|
112
|
+
stored: true,
|
|
113
|
+
...resumeOutcome ? { resume: resumeOutcome } : {},
|
|
114
|
+
error: `Session ${sessionId} went ${after} while sending: the message is stored server-side but no wrapper is attached to read it. ${after === "not_found" ? "Spawn a new session." : resumeHint}`
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
return { sessionId, status: "live", delivered: true, resumed, stored: true, ...resumeOutcome ? { resume: resumeOutcome } : {} };
|
|
118
|
+
}
|
|
119
|
+
async function waitUntilLive(classify, d, options) {
|
|
120
|
+
const waitMs = options.waitMs ?? DEFAULT_RESUME_WAIT_MS;
|
|
121
|
+
const pollMs = options.pollMs ?? DEFAULT_RESUME_POLL_MS;
|
|
122
|
+
const deadline = d.now() + waitMs;
|
|
123
|
+
let status = await classify();
|
|
124
|
+
while (status !== "live" && d.now() < deadline) {
|
|
125
|
+
await d.sleep(pollMs);
|
|
126
|
+
status = await classify();
|
|
127
|
+
}
|
|
128
|
+
return status;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const REMOTE_SESSION_OPS_MIN_CLI_VERSION = "0.2.157";
|
|
132
|
+
const REMOTE_OPS_PROTOCOL_VERSION = 1;
|
|
133
|
+
const REMOTE_TEXT_MAX_BYTES = 64 * 1024;
|
|
134
|
+
const REMOTE_TRANSCRIPT_MAX_BYTES = 200 * 1024;
|
|
135
|
+
const REMOTE_CALLS_PER_MINUTE = 60;
|
|
136
|
+
const REMOTE_WRITE_CALLS_PER_MINUTE = 30;
|
|
137
|
+
const REMOTE_LOCATE_MAX_IDS = 200;
|
|
138
|
+
const REMOTE_OP_DEADLINE_MS = 25e3;
|
|
139
|
+
const REMOTE_SEND_COMMIT_DEADLINE_MS = 18e3;
|
|
140
|
+
const REMOTE_RESUME_WAIT_MS = 1e4;
|
|
141
|
+
const REMOTE_WAIT_POLL_MS = 5e3;
|
|
142
|
+
const REMOTE_ANSWER_MAX_BYTES = 64 * 1024;
|
|
143
|
+
const REMOTE_TITLE_MAX_BYTES = 1024;
|
|
144
|
+
const REMOTE_SENT_FROM_ALLOWED = ["cli", "assistant", "automation"];
|
|
145
|
+
const LOCAL_ID_RE = /^[a-zA-Z0-9_.:-]{1,128}$/;
|
|
146
|
+
function remoteError(code, message) {
|
|
147
|
+
return { ok: false, error: { code, message } };
|
|
148
|
+
}
|
|
149
|
+
const SESSION_ID_RE = /^[a-zA-Z0-9_-]{1,128}$/;
|
|
150
|
+
const PEER_SCOPES = ["repo", "cwd", "machine"];
|
|
151
|
+
function isSessionId(value) {
|
|
152
|
+
return typeof value === "string" && SESSION_ID_RE.test(value);
|
|
153
|
+
}
|
|
154
|
+
function utf8Bytes(text) {
|
|
155
|
+
return Buffer.byteLength(text, "utf8");
|
|
156
|
+
}
|
|
157
|
+
function sanitizeSender(raw) {
|
|
158
|
+
if (!raw || typeof raw !== "object") return null;
|
|
159
|
+
const r = raw;
|
|
160
|
+
if (typeof r.sessionId !== "string" || r.sessionId.length === 0 || r.sessionId.length > 128) return null;
|
|
161
|
+
const optional = (key, max) => typeof r[key] === "string" && r[key].length > 0 ? { [key]: r[key].slice(0, max) } : {};
|
|
162
|
+
return {
|
|
163
|
+
sessionId: r.sessionId,
|
|
164
|
+
...optional("title", 200),
|
|
165
|
+
...optional("flavor", 32),
|
|
166
|
+
...optional("cwd", 1024),
|
|
167
|
+
...optional("machine", 128)
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
function guardRemoteSessionOpsRequest(method, raw, options) {
|
|
171
|
+
if (!options.enabled) {
|
|
172
|
+
return remoteError("disabled", 'Remote session operations are turned off on this machine (settings.json remoteSessionOps = "off")');
|
|
173
|
+
}
|
|
174
|
+
if (!raw || typeof raw !== "object") return remoteError("bad_request", "Request must be an object");
|
|
175
|
+
const r = raw;
|
|
176
|
+
if (r.v !== REMOTE_OPS_PROTOCOL_VERSION) return remoteError("bad_request", `Unsupported protocol version ${String(r.v)} (this daemon speaks v${REMOTE_OPS_PROTOCOL_VERSION})`);
|
|
177
|
+
const from = r.from;
|
|
178
|
+
if (!from || typeof from !== "object" || typeof from.host !== "string" || from.host.length === 0 || typeof from.cli !== "string") {
|
|
179
|
+
return remoteError("bad_request", "from.host and from.cli are required");
|
|
180
|
+
}
|
|
181
|
+
const caller = {
|
|
182
|
+
host: from.host.slice(0, 128),
|
|
183
|
+
cli: from.cli.slice(0, 32),
|
|
184
|
+
...typeof from.machineId === "string" && from.machineId.length > 0 ? { machineId: from.machineId.slice(0, 128) } : {},
|
|
185
|
+
...isSessionId(from.sessionId) ? { sessionId: from.sessionId } : {}
|
|
186
|
+
};
|
|
187
|
+
const args = r.args && typeof r.args === "object" ? r.args : {};
|
|
188
|
+
const limit = typeof args.limit === "number" && Number.isFinite(args.limit) && args.limit >= 1 ? Math.floor(args.limit) : void 0;
|
|
189
|
+
let clean;
|
|
190
|
+
switch (method) {
|
|
191
|
+
case "sessions.list": {
|
|
192
|
+
if (args.ids !== void 0) {
|
|
193
|
+
if (!Array.isArray(args.ids) || !args.ids.every(isSessionId)) return remoteError("bad_request", "ids must be an array of session ids");
|
|
194
|
+
if (args.ids.length > REMOTE_LOCATE_MAX_IDS) return remoteError("too_large", `ids: at most ${REMOTE_LOCATE_MAX_IDS} per call`);
|
|
195
|
+
}
|
|
196
|
+
const out = {
|
|
197
|
+
...args.ids !== void 0 ? { ids: args.ids } : {},
|
|
198
|
+
...args.all === true ? { all: true } : {},
|
|
199
|
+
...typeof args.tag === "string" && args.tag.length > 0 ? { tag: args.tag.slice(0, 128) } : {},
|
|
200
|
+
...limit !== void 0 ? { limit } : {}
|
|
201
|
+
};
|
|
202
|
+
clean = out;
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
205
|
+
case "sessions.read": {
|
|
206
|
+
if (!isSessionId(args.sessionId)) return remoteError("bad_request", "sessionId is required");
|
|
207
|
+
const out = { sessionId: args.sessionId, ...limit !== void 0 ? { limit } : {}, ...args.full === true ? { full: true } : {} };
|
|
208
|
+
clean = out;
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
211
|
+
case "sessions.send": {
|
|
212
|
+
if (!isSessionId(args.sessionId)) return remoteError("bad_request", "sessionId is required");
|
|
213
|
+
if (typeof args.text !== "string" || args.text.trim().length === 0) return remoteError("bad_request", "text must be non-empty");
|
|
214
|
+
if (utf8Bytes(args.text) > REMOTE_TEXT_MAX_BYTES) return remoteError("too_large", `text exceeds ${REMOTE_TEXT_MAX_BYTES} bytes`);
|
|
215
|
+
if (args.localId !== void 0 && (typeof args.localId !== "string" || !LOCAL_ID_RE.test(args.localId))) return remoteError("bad_request", "localId must be a short id");
|
|
216
|
+
const out = {
|
|
217
|
+
sessionId: args.sessionId,
|
|
218
|
+
text: args.text,
|
|
219
|
+
...args.resume === true ? { resume: true } : {},
|
|
220
|
+
...args.model === null || typeof args.model === "string" && args.model.length <= 128 ? { model: args.model } : {},
|
|
221
|
+
...REMOTE_SENT_FROM_ALLOWED.includes(args.sentFrom) ? { sentFrom: args.sentFrom } : {},
|
|
222
|
+
...typeof args.localId === "string" ? { localId: args.localId } : {}
|
|
223
|
+
};
|
|
224
|
+
clean = out;
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
case "sessions.peers": {
|
|
228
|
+
if (args.scope !== void 0 && !PEER_SCOPES.includes(args.scope)) return remoteError("bad_request", `scope must be one of ${PEER_SCOPES.join(", ")}`);
|
|
229
|
+
const out = {
|
|
230
|
+
...args.scope !== void 0 ? { scope: args.scope } : {},
|
|
231
|
+
...typeof args.cwd === "string" && args.cwd.length > 0 ? { cwd: args.cwd.slice(0, 4096) } : {}
|
|
232
|
+
};
|
|
233
|
+
clean = out;
|
|
234
|
+
break;
|
|
235
|
+
}
|
|
236
|
+
case "sessions.message": {
|
|
237
|
+
if (!isSessionId(args.to)) return remoteError("bad_request", "to is required");
|
|
238
|
+
if (typeof args.body !== "string" || args.body.trim().length === 0) return remoteError("bad_request", "body must be non-empty");
|
|
239
|
+
if (utf8Bytes(args.body) > REMOTE_TEXT_MAX_BYTES) return remoteError("too_large", `body exceeds ${REMOTE_TEXT_MAX_BYTES} bytes`);
|
|
240
|
+
const sender = sanitizeSender(args.from);
|
|
241
|
+
if (!sender) return remoteError("bad_request", "from.sessionId (the sender) is required");
|
|
242
|
+
const out = {
|
|
243
|
+
to: args.to,
|
|
244
|
+
body: args.body,
|
|
245
|
+
...typeof args.replyTo === "string" && args.replyTo.length > 0 ? { replyTo: args.replyTo.slice(0, 64) } : {},
|
|
246
|
+
// The recipient's header names the machine the call came
|
|
247
|
+
// from, as the caller identified itself — never a value the
|
|
248
|
+
// sender picked for someone else.
|
|
249
|
+
from: { ...sender, machine: caller.host }
|
|
250
|
+
};
|
|
251
|
+
clean = out;
|
|
252
|
+
break;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
return { ok: true, request: { v: REMOTE_OPS_PROTOCOL_VERSION, from: caller, args: clean } };
|
|
256
|
+
}
|
|
257
|
+
class RemoteCallBudget {
|
|
258
|
+
constructor(perMinute = REMOTE_CALLS_PER_MINUTE, now = Date.now) {
|
|
259
|
+
this.perMinute = perMinute;
|
|
260
|
+
this.now = now;
|
|
261
|
+
this.tokens = perMinute;
|
|
262
|
+
this.lastRefill = now();
|
|
263
|
+
}
|
|
264
|
+
tokens;
|
|
265
|
+
lastRefill;
|
|
266
|
+
/** Take one token; false = over budget (with ms until the next token). */
|
|
267
|
+
take() {
|
|
268
|
+
if (this.perMinute <= 0) return { ok: true };
|
|
269
|
+
const t = this.now();
|
|
270
|
+
const refill = (t - this.lastRefill) / 6e4 * this.perMinute;
|
|
271
|
+
if (refill > 0) {
|
|
272
|
+
this.tokens = Math.min(this.perMinute, this.tokens + refill);
|
|
273
|
+
this.lastRefill = t;
|
|
274
|
+
}
|
|
275
|
+
if (this.tokens >= 1) {
|
|
276
|
+
this.tokens -= 1;
|
|
277
|
+
return { ok: true };
|
|
278
|
+
}
|
|
279
|
+
return { ok: false, retryAfterMs: Math.ceil((1 - this.tokens) / this.perMinute * 6e4) };
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
function sanitizeAuditValue(value, max = 64) {
|
|
283
|
+
if (typeof value !== "string" || value.length === 0) return "?";
|
|
284
|
+
return value.slice(0, max).replace(/[^\x20-\x7e]/g, "?");
|
|
285
|
+
}
|
|
286
|
+
function formatRemoteAuditLine(input) {
|
|
287
|
+
const from = input.from;
|
|
288
|
+
const opt = (value) => value === void 0 ? "-" : sanitizeAuditValue(value);
|
|
289
|
+
return `[REMOTE SESSION OPS] ${sanitizeAuditValue(input.method)} from machine=${sanitizeAuditValue(from?.machineId)} host=${sanitizeAuditValue(from?.host)} cli=${sanitizeAuditValue(from?.cli, 32)} session=${opt(from?.sessionId)} target=${opt(input.sessionId)} \u2192 ${input.outcome} (${Math.max(0, Math.round(input.durationMs))}ms)`;
|
|
290
|
+
}
|
|
291
|
+
function capRemoteText(text, maxBytes, keep = "head") {
|
|
292
|
+
if (utf8Bytes(text) <= maxBytes) return text;
|
|
293
|
+
const buffer = Buffer.from(text, "utf8");
|
|
294
|
+
const marker = "\u2026";
|
|
295
|
+
const room = Math.max(0, maxBytes - Buffer.byteLength(marker));
|
|
296
|
+
const slice = keep === "head" ? buffer.subarray(0, room) : buffer.subarray(buffer.length - room);
|
|
297
|
+
const clean = slice.toString("utf8").replace(/^�+|�+$/g, "");
|
|
298
|
+
return keep === "head" ? clean + marker : marker + clean;
|
|
299
|
+
}
|
|
300
|
+
function capRemoteTranscript(transcript, maxBytes = REMOTE_TRANSCRIPT_MAX_BYTES) {
|
|
301
|
+
if (utf8Bytes(transcript) <= maxBytes) return { transcript, truncated: false };
|
|
302
|
+
const buffer = Buffer.from(transcript, "utf8");
|
|
303
|
+
const marker = "\u2026 [truncated by the remote machine: transcript exceeded the RPC budget; use --limit] \u2026\n";
|
|
304
|
+
const tail = buffer.subarray(buffer.length - (maxBytes - Buffer.byteLength(marker))).toString("utf8");
|
|
305
|
+
return { transcript: marker + tail.replace(/^�+/, ""), truncated: true };
|
|
306
|
+
}
|
|
307
|
+
function parseHappyClientVersion(value) {
|
|
308
|
+
if (typeof value !== "string") return null;
|
|
309
|
+
const match = value.match(/(\d+)\.(\d+)\.(\d+)/);
|
|
310
|
+
return match ? `${match[1]}.${match[2]}.${match[3]}` : null;
|
|
311
|
+
}
|
|
312
|
+
function compareVersions(a, b) {
|
|
313
|
+
const pa = a.split(".").map((n) => Number.parseInt(n, 10) || 0);
|
|
314
|
+
const pb = b.split(".").map((n) => Number.parseInt(n, 10) || 0);
|
|
315
|
+
for (let i = 0; i < 3; i++) {
|
|
316
|
+
const d = (pa[i] ?? 0) - (pb[i] ?? 0);
|
|
317
|
+
if (d !== 0) return d;
|
|
318
|
+
}
|
|
319
|
+
return 0;
|
|
320
|
+
}
|
|
321
|
+
function supportsRemoteSessionOps(cliVersion) {
|
|
322
|
+
if (!cliVersion) return null;
|
|
323
|
+
return compareVersions(cliVersion, REMOTE_SESSION_OPS_MIN_CLI_VERSION) >= 0;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const REMOTE_CLIENT_TAG = "session-ops";
|
|
327
|
+
const REMOTE_RPC_TIMEOUT_MS = 3e4;
|
|
328
|
+
const REMOTE_LOCATE_MAX_MACHINES = 8;
|
|
329
|
+
class RemoteSessionOpsError extends Error {
|
|
330
|
+
constructor(code, message, machineId, ambiguous = false) {
|
|
331
|
+
super(message);
|
|
332
|
+
this.code = code;
|
|
333
|
+
this.machineId = machineId;
|
|
334
|
+
this.ambiguous = ambiguous;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
async function bearerToken() {
|
|
338
|
+
const credentials = await readCredentialsForConfiguredRelay();
|
|
339
|
+
if (!credentials) throw new Error("CLI is not authenticated (no ~/.happy/access.key)");
|
|
340
|
+
return credentials.token;
|
|
341
|
+
}
|
|
342
|
+
async function listAccountMachines() {
|
|
343
|
+
const token = await bearerToken();
|
|
344
|
+
const response = await axios.get(`${configuration.serverUrl}/v1/machines`, {
|
|
345
|
+
headers: { "Authorization": `Bearer ${token}`, "X-Happy-Client": `${REMOTE_CLIENT_TAG}/${configuration.currentCliVersion}` },
|
|
346
|
+
timeout: 15e3
|
|
347
|
+
});
|
|
348
|
+
const rows = Array.isArray(response.data) ? response.data : [];
|
|
349
|
+
return rows.filter((row) => !!row && typeof row === "object" && typeof row.id === "string").map((row) => ({
|
|
350
|
+
id: row.id,
|
|
351
|
+
active: row.active === true,
|
|
352
|
+
...typeof row.activeAt === "number" ? { activeAt: row.activeAt } : {},
|
|
353
|
+
cliVersion: parseHappyClientVersion(row.lastHappyClient)
|
|
354
|
+
}));
|
|
355
|
+
}
|
|
356
|
+
async function callerIdentity(env = process.env) {
|
|
357
|
+
const settings = await readSettings().catch(() => null);
|
|
358
|
+
const sessionId = env.VH_PEER_SESSION_ID;
|
|
359
|
+
return {
|
|
360
|
+
...settings?.machineId ? { machineId: settings.machineId } : {},
|
|
361
|
+
host: hostname(),
|
|
362
|
+
cli: configuration.currentCliVersion,
|
|
363
|
+
...sessionId && /^[a-zA-Z0-9_-]{1,128}$/.test(sessionId) ? { sessionId } : {}
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
const defaultDeps = {
|
|
367
|
+
listMachines: listAccountMachines,
|
|
368
|
+
selfMachineId: async () => (await readSettings().catch(() => null))?.machineId,
|
|
369
|
+
openTransport: async () => {
|
|
370
|
+
const socket = await openUserScopedSocket(await bearerToken());
|
|
371
|
+
return {
|
|
372
|
+
call: (method, paramsJson) => socket.rpcCall({ method, params: paramsJson }, REMOTE_RPC_TIMEOUT_MS),
|
|
373
|
+
close: () => socket.close()
|
|
374
|
+
};
|
|
375
|
+
},
|
|
376
|
+
caller: () => callerIdentity(),
|
|
377
|
+
sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
378
|
+
now: () => Date.now()
|
|
379
|
+
};
|
|
380
|
+
function withDefaults(deps) {
|
|
381
|
+
return { ...defaultDeps, ...deps };
|
|
382
|
+
}
|
|
383
|
+
function pickRemoteCandidates(machines, selfId) {
|
|
384
|
+
const candidates = [];
|
|
385
|
+
const skipped = [];
|
|
386
|
+
for (const machine of machines) {
|
|
387
|
+
if (selfId && machine.id === selfId) {
|
|
388
|
+
skipped.push({ machine, reason: "self" });
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
if (!machine.active) {
|
|
392
|
+
skipped.push({ machine, reason: "offline" });
|
|
393
|
+
continue;
|
|
394
|
+
}
|
|
395
|
+
if (supportsRemoteSessionOps(machine.cliVersion) === false) {
|
|
396
|
+
skipped.push({ machine, reason: "too_old" });
|
|
397
|
+
continue;
|
|
398
|
+
}
|
|
399
|
+
candidates.push(machine);
|
|
400
|
+
}
|
|
401
|
+
candidates.sort((a, b) => (b.activeAt ?? 0) - (a.activeAt ?? 0));
|
|
402
|
+
return { candidates: candidates.slice(0, REMOTE_LOCATE_MAX_MACHINES), skipped };
|
|
403
|
+
}
|
|
404
|
+
function describeAge(activeAt, now) {
|
|
405
|
+
if (activeAt === void 0) return "never seen";
|
|
406
|
+
const minutes = Math.round((now - activeAt) / 6e4);
|
|
407
|
+
if (minutes < 2) return "seen just now";
|
|
408
|
+
if (minutes < 120) return `last seen ${minutes}m ago`;
|
|
409
|
+
if (minutes < 48 * 60) return `last seen ${Math.round(minutes / 60)}h ago`;
|
|
410
|
+
return `last seen ${Math.round(minutes / 1440)}d ago`;
|
|
411
|
+
}
|
|
412
|
+
async function resolveExplicitMachine(machineId, deps) {
|
|
413
|
+
const machines = await deps.listMachines();
|
|
414
|
+
const machine = machines.find((row) => row.id === machineId);
|
|
415
|
+
if (!machine) throw new RemoteSessionOpsError("unknown_machine", `Machine ${machineId} is not on this account`, machineId);
|
|
416
|
+
if (!machine.active) throw new RemoteSessionOpsError("offline", `Machine ${machineId} is offline (${describeAge(machine.activeAt, deps.now())}); its sessions cannot be reached until its daemon reconnects`, machineId);
|
|
417
|
+
if (supportsRemoteSessionOps(machine.cliVersion) === false) {
|
|
418
|
+
throw new RemoteSessionOpsError("too_old", `Machine ${machineId} runs CLI ${machine.cliVersion}; cross-machine session operations need \u2265 ${REMOTE_SESSION_OPS_MIN_CLI_VERSION} on the target`, machineId);
|
|
419
|
+
}
|
|
420
|
+
return machine;
|
|
421
|
+
}
|
|
422
|
+
async function callRemoteSessionOp(transport, machineId, method, args, from) {
|
|
423
|
+
const params = JSON.stringify({ v: REMOTE_OPS_PROTOCOL_VERSION, from, args });
|
|
424
|
+
let ack;
|
|
425
|
+
try {
|
|
426
|
+
ack = await transport.call(`${machineId}:${method}`, params);
|
|
427
|
+
} catch (error) {
|
|
428
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
429
|
+
throw new RemoteSessionOpsError("unreachable", `Machine ${machineId} did not answer ${method} (${reason})`, machineId, true);
|
|
430
|
+
}
|
|
431
|
+
if (!ack?.ok) {
|
|
432
|
+
const reason = ack?.error ?? "no ack";
|
|
433
|
+
if (/not available/i.test(reason)) {
|
|
434
|
+
throw new RemoteSessionOpsError("unreachable", `Machine ${machineId} did not answer ${method}: its daemon is offline, restarting, or runs a CLI older than ${REMOTE_SESSION_OPS_MIN_CLI_VERSION}`, machineId);
|
|
435
|
+
}
|
|
436
|
+
throw new RemoteSessionOpsError("unreachable", `Machine ${machineId}: ${reason}`, machineId, true);
|
|
437
|
+
}
|
|
438
|
+
let envelope;
|
|
439
|
+
try {
|
|
440
|
+
envelope = typeof ack.result === "string" ? JSON.parse(ack.result) : ack.result;
|
|
441
|
+
} catch {
|
|
442
|
+
throw new RemoteSessionOpsError("unreachable", `Machine ${machineId} returned an unreadable ${method} response`, machineId);
|
|
443
|
+
}
|
|
444
|
+
if (!envelope || typeof envelope !== "object") throw new RemoteSessionOpsError("unreachable", `Machine ${machineId} returned an empty ${method} response`, machineId);
|
|
445
|
+
if ("ok" in envelope) {
|
|
446
|
+
if (envelope.ok) return envelope.result;
|
|
447
|
+
throw new RemoteSessionOpsError(envelope.error.code, `Machine ${machineId}: ${envelope.error.message}`, machineId);
|
|
448
|
+
}
|
|
449
|
+
throw new RemoteSessionOpsError("internal", `Machine ${machineId}: ${envelope.error ?? "remote handler failed"}`, machineId);
|
|
450
|
+
}
|
|
451
|
+
async function locateRemoteSession(transport, sessionId, options, deps) {
|
|
452
|
+
const from = await deps.caller();
|
|
453
|
+
if (options.machineId) {
|
|
454
|
+
const machine = await resolveExplicitMachine(options.machineId, deps);
|
|
455
|
+
const listing = await callRemoteSessionOp(transport, machine.id, "sessions.list", { ids: [sessionId] }, from);
|
|
456
|
+
if (!listing.sessions.some((row) => row.id === sessionId)) {
|
|
457
|
+
throw new RemoteSessionOpsError("no_local_key", `Machine ${machine.id} (${listing.host}) has no key for session ${sessionId}: it was not spawned there (or is older than 14 days)`, machine.id);
|
|
458
|
+
}
|
|
459
|
+
return { machine, host: listing.host };
|
|
460
|
+
}
|
|
461
|
+
const { candidates, skipped } = pickRemoteCandidates(await deps.listMachines(), await deps.selfMachineId());
|
|
462
|
+
const failures = [];
|
|
463
|
+
for (const machine of candidates) {
|
|
464
|
+
try {
|
|
465
|
+
const listing = await callRemoteSessionOp(transport, machine.id, "sessions.list", { ids: [sessionId] }, from);
|
|
466
|
+
if (listing.sessions.some((row) => row.id === sessionId)) return { machine, host: listing.host };
|
|
467
|
+
failures.push(`${listing.host || machine.id}: not there`);
|
|
468
|
+
} catch (error) {
|
|
469
|
+
failures.push(`${machine.id}: ${error instanceof RemoteSessionOpsError ? error.code : error instanceof Error ? error.message : String(error)}`);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
const skippedText = skipped.filter((s) => s.reason !== "self").map((s) => `${s.machine.id}: ${s.reason}`);
|
|
473
|
+
const detail = [...failures, ...skippedText];
|
|
474
|
+
throw new RemoteSessionOpsError(
|
|
475
|
+
"not_located",
|
|
476
|
+
`Session ${sessionId} is not on this machine and no online machine of this account reports it${detail.length ? ` (${detail.join("; ")})` : " (no other machines are online)"}. Pass --machine <id> to name one, or bring its machine online.`
|
|
477
|
+
);
|
|
478
|
+
}
|
|
479
|
+
async function withRemoteTransport(deps, fn) {
|
|
480
|
+
const transport = await deps.openTransport();
|
|
481
|
+
try {
|
|
482
|
+
return await fn(transport);
|
|
483
|
+
} finally {
|
|
484
|
+
try {
|
|
485
|
+
transport.close();
|
|
486
|
+
} catch {
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
async function readRemoteTranscript(sessionId, options = {}, overrides = {}) {
|
|
491
|
+
const deps = withDefaults(overrides);
|
|
492
|
+
return withRemoteTransport(deps, async (transport) => {
|
|
493
|
+
const located = await locateRemoteSession(transport, sessionId, { machineId: options.machineId }, deps);
|
|
494
|
+
return callRemoteSessionOp(transport, located.machine.id, "sessions.read", {
|
|
495
|
+
sessionId,
|
|
496
|
+
...options.limit !== void 0 ? { limit: options.limit } : {},
|
|
497
|
+
...options.full ? { full: true } : {}
|
|
498
|
+
}, await deps.caller());
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
async function waitForRemoteTurnEnd(sessionId, options, overrides = {}) {
|
|
502
|
+
const deps = withDefaults(overrides);
|
|
503
|
+
const pollMs = options.pollMs ?? REMOTE_WAIT_POLL_MS;
|
|
504
|
+
return withRemoteTransport(deps, async (transport) => {
|
|
505
|
+
const located = await locateRemoteSession(transport, sessionId, { machineId: options.machineId }, deps);
|
|
506
|
+
const from = await deps.caller();
|
|
507
|
+
const deadline = deps.now() + options.timeoutMs;
|
|
508
|
+
const args = { sessionId, ...options.limit !== void 0 ? { limit: options.limit } : {}, ...options.full ? { full: true } : {} };
|
|
509
|
+
while (true) {
|
|
510
|
+
const read = await callRemoteSessionOp(transport, located.machine.id, "sessions.read", args, from);
|
|
511
|
+
if (read.turn.ended) return { read, timedOut: false };
|
|
512
|
+
if (deps.now() + pollMs > deadline) return { read, timedOut: true };
|
|
513
|
+
await deps.sleep(pollMs);
|
|
514
|
+
}
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
async function sendRemoteMessage(sessionId, text, options = {}, overrides = {}) {
|
|
518
|
+
const deps = withDefaults(overrides);
|
|
519
|
+
return withRemoteTransport(deps, async (transport) => {
|
|
520
|
+
const located = await locateRemoteSession(transport, sessionId, { machineId: options.machineId }, deps);
|
|
521
|
+
const from = await deps.caller();
|
|
522
|
+
const localId = options.localId ?? `remote-send-${randomUUID()}`;
|
|
523
|
+
const args = {
|
|
524
|
+
sessionId,
|
|
525
|
+
text,
|
|
526
|
+
localId,
|
|
527
|
+
...options.resume ? { resume: true } : {},
|
|
528
|
+
...options.model !== void 0 ? { model: options.model } : {},
|
|
529
|
+
...options.sentFrom !== void 0 ? { sentFrom: options.sentFrom } : {}
|
|
530
|
+
};
|
|
531
|
+
const attempt = () => callRemoteSessionOp(transport, located.machine.id, "sessions.send", args, from);
|
|
532
|
+
let result;
|
|
533
|
+
try {
|
|
534
|
+
result = await attempt();
|
|
535
|
+
} catch (error) {
|
|
536
|
+
const retryable = error instanceof RemoteSessionOpsError && (error.ambiguous || error.code === "timeout");
|
|
537
|
+
if (!retryable) throw error;
|
|
538
|
+
result = await attempt();
|
|
539
|
+
}
|
|
540
|
+
return { ...result, machine: { id: located.machine.id, host: located.host } };
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
async function sendRemotePeerMessage(args, options = {}, overrides = {}) {
|
|
544
|
+
const deps = withDefaults(overrides);
|
|
545
|
+
return withRemoteTransport(deps, async (transport) => {
|
|
546
|
+
const located = await locateRemoteSession(transport, args.to, { machineId: options.machineId }, deps);
|
|
547
|
+
const caller = await deps.caller();
|
|
548
|
+
const result = await callRemoteSessionOp(transport, located.machine.id, "sessions.message", {
|
|
549
|
+
to: args.to,
|
|
550
|
+
body: args.body,
|
|
551
|
+
...args.replyTo ? { replyTo: args.replyTo } : {},
|
|
552
|
+
from: { ...args.from, machine: args.from.machine ?? caller.host }
|
|
553
|
+
}, caller);
|
|
554
|
+
return { ...result, machine: { id: located.machine.id, host: located.host } };
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
async function listRemotePeers(machineId, options = {}, overrides = {}) {
|
|
558
|
+
const deps = withDefaults(overrides);
|
|
559
|
+
return withRemoteTransport(deps, async (transport) => {
|
|
560
|
+
const machine = await resolveExplicitMachine(machineId, deps);
|
|
561
|
+
return callRemoteSessionOp(transport, machine.id, "sessions.peers", {
|
|
562
|
+
...options.scope ? { scope: options.scope } : {},
|
|
563
|
+
...options.cwd ? { cwd: options.cwd } : {}
|
|
564
|
+
}, await deps.caller());
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
async function listRemoteSessions(machineId, options = {}, overrides = {}) {
|
|
568
|
+
const deps = withDefaults(overrides);
|
|
569
|
+
return withRemoteTransport(deps, async (transport) => {
|
|
570
|
+
const machine = await resolveExplicitMachine(machineId, deps);
|
|
571
|
+
return callRemoteSessionOp(transport, machine.id, "sessions.list", {
|
|
572
|
+
...options.tag ? { tag: options.tag } : {},
|
|
573
|
+
...options.limit !== void 0 ? { limit: options.limit } : {}
|
|
574
|
+
}, await deps.caller());
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
async function fillForeignAccountRows(rows, overrides = {}) {
|
|
578
|
+
const deps = withDefaults(overrides);
|
|
579
|
+
const pending = new Set(rows.filter((row) => !row.decryptable).map((row) => row.id));
|
|
580
|
+
const asked = [];
|
|
581
|
+
if (pending.size === 0) return { rows, asked, skipped: [] };
|
|
582
|
+
const { candidates, skipped } = pickRemoteCandidates(await deps.listMachines(), await deps.selfMachineId());
|
|
583
|
+
const filled = /* @__PURE__ */ new Map();
|
|
584
|
+
if (candidates.length > 0) {
|
|
585
|
+
await withRemoteTransport(deps, async (transport) => {
|
|
586
|
+
const from = await deps.caller();
|
|
587
|
+
for (const machine of candidates) {
|
|
588
|
+
if (pending.size === 0) break;
|
|
589
|
+
try {
|
|
590
|
+
const listing = await callRemoteSessionOp(transport, machine.id, "sessions.list", { ids: [...pending], all: true }, from);
|
|
591
|
+
let count = 0;
|
|
592
|
+
for (const row of listing.sessions) {
|
|
593
|
+
if (!pending.has(row.id)) continue;
|
|
594
|
+
pending.delete(row.id);
|
|
595
|
+
count++;
|
|
596
|
+
filled.set(row.id, { ...row, decryptable: false, readable: true, via: machine.id, machine: { id: machine.id, host: listing.host } });
|
|
597
|
+
}
|
|
598
|
+
asked.push({ machineId: machine.id, host: listing.host, filled: count });
|
|
599
|
+
} catch (error) {
|
|
600
|
+
asked.push({ machineId: machine.id, filled: 0, error: error instanceof Error ? error.message : String(error) });
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
return {
|
|
606
|
+
rows: rows.map((row) => filled.get(row.id) ?? row),
|
|
607
|
+
asked,
|
|
608
|
+
skipped: skipped.filter((s) => s.reason !== "self").map((s) => ({ machineId: s.machine.id, reason: s.reason }))
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
export { RemoteSessionOpsError as R, listRemotePeers as a, sendRemotePeerMessage as b, RemoteCallBudget as c, deliverToSession as d, REMOTE_WRITE_CALLS_PER_MINUTE as e, fillForeignAccountRows as f, guardRemoteSessionOpsRequest as g, remoteError as h, REMOTE_OP_DEADLINE_MS as i, formatRemoteAuditLine as j, REMOTE_SEND_COMMIT_DEADLINE_MS as k, listRemoteSessions as l, REMOTE_RESUME_WAIT_MS as m, capRemoteTranscript as n, capRemoteText as o, REMOTE_ANSWER_MAX_BYTES as p, REMOTE_TITLE_MAX_BYTES as q, readRemoteTranscript as r, sendRemoteMessage as s, waitForRemoteTurnEnd as w };
|