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,894 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var os = require('node:os');
|
|
4
|
+
var sessionLock = require('./sessionLock-7ePAYDOg.cjs');
|
|
5
|
+
var node_crypto = require('node:crypto');
|
|
6
|
+
var api = require('./api-B2vLMS-z.cjs');
|
|
7
|
+
var logger = require('./logger-Bc8-u1w0.cjs');
|
|
8
|
+
var _package = require('./package-Cl5BEP5x.cjs');
|
|
9
|
+
var persistence = require('./persistence-CBNMzSa8.cjs');
|
|
10
|
+
var createSessionMetadata = require('./createSessionMetadata-BRLB9CNO.cjs');
|
|
11
|
+
var index = require('./index-DA7yINuk.cjs');
|
|
12
|
+
var configuration = require('./configuration-v9f8LM9T.cjs');
|
|
13
|
+
var controlClient = require('./controlClient-fEHE85bH.cjs');
|
|
14
|
+
var machineMetadata = require('./machineMetadata-BibDMFVx.cjs');
|
|
15
|
+
var projectPath = require('./projectPath-ChLhdOyt.cjs');
|
|
16
|
+
var path = require('node:path');
|
|
17
|
+
var permissionMode = require('./permissionMode-CrV-XuMs.cjs');
|
|
18
|
+
var authRelay = require('./authRelay-BdYkC1Po.cjs');
|
|
19
|
+
var promises = require('node:fs/promises');
|
|
20
|
+
var defaultPermissionMode = require('./defaultPermissionMode-CxmTXUQR.cjs');
|
|
21
|
+
var attachmentContent = require('./attachmentContent-DMMZzevR.cjs');
|
|
22
|
+
var startupPlan = require('./startupPlan-BLMbfQIH.cjs');
|
|
23
|
+
require('node:fs');
|
|
24
|
+
require('axios');
|
|
25
|
+
require('node:events');
|
|
26
|
+
require('socket.io-client');
|
|
27
|
+
require('./attachmentLimits-CzVrqgLR.cjs');
|
|
28
|
+
require('zod');
|
|
29
|
+
require('./limits-C-a7gr04.cjs');
|
|
30
|
+
require('./time-CNRE4ChD.cjs');
|
|
31
|
+
require('./lidState-KHHu1015.cjs');
|
|
32
|
+
require('os');
|
|
33
|
+
require('child_process');
|
|
34
|
+
require('./registerCommonHandlers-BjSV2Skz.cjs');
|
|
35
|
+
require('util');
|
|
36
|
+
require('fs/promises');
|
|
37
|
+
require('crypto');
|
|
38
|
+
require('path');
|
|
39
|
+
require('cross-spawn');
|
|
40
|
+
require('@paralleldrive/cuid2');
|
|
41
|
+
require('./sync-CVk4QERO.cjs');
|
|
42
|
+
require('./backgroundTaskActivity-CBvUdj39.cjs');
|
|
43
|
+
require('./contentLogMetadata-ZSFk_NjQ.cjs');
|
|
44
|
+
require('chalk');
|
|
45
|
+
require('node:util');
|
|
46
|
+
require('./detectCLI-DR9ZaN6B.cjs');
|
|
47
|
+
require('fs');
|
|
48
|
+
require('./localHappyAgentAuth-BXiHPXdC.cjs');
|
|
49
|
+
require('tweetnacl');
|
|
50
|
+
require('url');
|
|
51
|
+
|
|
52
|
+
const DEFAULT_CLAUDE_MODEL = void 0;
|
|
53
|
+
const DEFAULT_CLAUDE_EFFORT = void 0;
|
|
54
|
+
const importRunClaudeDeps = () => Promise.resolve().then(function () { return require('./runClaudeDeps-Bp20MScE.cjs'); });
|
|
55
|
+
async function runClaude(credentials, options = {}) {
|
|
56
|
+
logger.logger.debug(`[CLAUDE] ===== CLAUDE MODE STARTING =====`);
|
|
57
|
+
logger.logger.debug(`[CLAUDE] This is the Claude agent, NOT Gemini`);
|
|
58
|
+
options = { ...options, claudeEnvVars: { ...options.claudeEnvVars, HAPPY_MANAGED: "1" } };
|
|
59
|
+
const prewarmSpawnEnv = {};
|
|
60
|
+
for (const key of ["HAPPY_CLAUDE_AUTH_STATUS", "HAPPY_FIRST_MESSAGE_FROM_CLI"]) {
|
|
61
|
+
prewarmSpawnEnv[key] = process.env[key];
|
|
62
|
+
delete process.env[key];
|
|
63
|
+
}
|
|
64
|
+
const workingDirectory = process.cwd();
|
|
65
|
+
const isAssistantVariant = process.env.HAPPY_SESSION_VARIANT === "assistant";
|
|
66
|
+
if (isAssistantVariant) {
|
|
67
|
+
try {
|
|
68
|
+
const { bootstrapAssistantHome } = await Promise.resolve().then(function () { return require('./bootstrap-CkgIq8N3.cjs'); });
|
|
69
|
+
await bootstrapAssistantHome();
|
|
70
|
+
} catch (error) {
|
|
71
|
+
logger.logger.debug("[START] Assistant home bootstrap failed:", error);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
logger.logger.debug(`[START] Happy process started pid=${process.pid} node=${process.version} cwd=${workingDirectory}`);
|
|
75
|
+
logger.logger.debug(`[START] Options: startedBy=${options.startedBy}, startingMode=${options.startingMode}`);
|
|
76
|
+
if (options.startedBy === "daemon" && options.startingMode === "local") {
|
|
77
|
+
throw new Error("Daemon-spawned sessions cannot use local/interactive mode. Use --happy-starting-mode remote or spawn sessions directly from terminal.");
|
|
78
|
+
}
|
|
79
|
+
let depsLoadPromise = null;
|
|
80
|
+
const loadDeps = () => {
|
|
81
|
+
if (!depsLoadPromise) {
|
|
82
|
+
depsLoadPromise = importRunClaudeDeps();
|
|
83
|
+
depsLoadPromise.catch(() => {
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
return depsLoadPromise;
|
|
87
|
+
};
|
|
88
|
+
const reconnectRequested = Boolean(
|
|
89
|
+
process.env.HAPPY_RECONNECT_SESSION_ID && process.env.HAPPY_RECONNECT_ENCRYPTION_KEY && process.env.HAPPY_RECONNECT_ENCRYPTION_VARIANT
|
|
90
|
+
);
|
|
91
|
+
const startupPlan$1 = startupPlan.planClaudeStartup({ startedBy: options.startedBy, reconnect: reconnectRequested });
|
|
92
|
+
api.connectionState.setBackend("Claude");
|
|
93
|
+
const api$1 = await api.ApiClient.create(credentials);
|
|
94
|
+
let state = {};
|
|
95
|
+
const settings = await persistence.readSettings();
|
|
96
|
+
let machineId = settings?.machineId;
|
|
97
|
+
const sandboxConfig = options.noSandbox ? void 0 : settings?.sandboxConfig;
|
|
98
|
+
const sandboxEnabled = Boolean(sandboxConfig?.enabled);
|
|
99
|
+
const initialPermissionMode = permissionMode.applySandboxPermissionPolicy(
|
|
100
|
+
permissionMode.resolveInitialClaudePermissionMode(options.permissionMode ?? defaultPermissionMode.DEFAULT_CLAUDE_PERMISSION_MODE, options.claudeArgs),
|
|
101
|
+
sandboxEnabled
|
|
102
|
+
);
|
|
103
|
+
const dangerouslySkipPermissions = initialPermissionMode === "bypassPermissions" || initialPermissionMode === "yolo" || sandboxEnabled || Boolean(options.claudeArgs?.includes("--dangerously-skip-permissions"));
|
|
104
|
+
if (!machineId) {
|
|
105
|
+
console.error(`[START] No machine ID found in settings, which is unexpected since authAndSetupMachineIfNeeded should have created it. Please report this issue on https://github.com/Mereithhh/very-happy/issues`);
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
logger.logger.debug(`Using machineId: ${machineId}`);
|
|
109
|
+
const sessionTag = node_crypto.randomUUID();
|
|
110
|
+
if (startupPlan$1.registerMachine) {
|
|
111
|
+
await api$1.getOrCreateMachine({
|
|
112
|
+
machineId,
|
|
113
|
+
metadata: machineMetadata.getInitialMachineMetadata()
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
const forkedFromSessionId = process.env.HAPPY_FORKED_FROM_SESSION_ID;
|
|
117
|
+
const forkedFromMessageId = process.env.HAPPY_FORKED_FROM_MESSAGE_ID;
|
|
118
|
+
const importedFromClaudeSessionId = process.env.HAPPY_IMPORTED_FROM_CLAUDE_SESSION_ID;
|
|
119
|
+
const importTitle = process.env.HAPPY_IMPORT_TITLE;
|
|
120
|
+
const originTags = createSessionMetadata.spawnOriginTags(process.env, "claude");
|
|
121
|
+
let metadata = {
|
|
122
|
+
path: workingDirectory,
|
|
123
|
+
host: os.hostname(),
|
|
124
|
+
version: _package.packageJson.version,
|
|
125
|
+
os: os.platform(),
|
|
126
|
+
machineId,
|
|
127
|
+
homeDir: os.homedir(),
|
|
128
|
+
happyHomeDir: configuration.configuration.happyHomeDir,
|
|
129
|
+
happyLibDir: projectPath.projectPath(),
|
|
130
|
+
happyToolsDir: path.resolve(projectPath.projectPath(), "tools", "unpacked"),
|
|
131
|
+
startedFromDaemon: options.startedBy === "daemon",
|
|
132
|
+
hostPid: process.pid,
|
|
133
|
+
startedBy: options.startedBy || "terminal",
|
|
134
|
+
// Initialize lifecycle state
|
|
135
|
+
lifecycleState: "running",
|
|
136
|
+
lifecycleStateSince: Date.now(),
|
|
137
|
+
flavor: "claude",
|
|
138
|
+
...process.env.VH_TEAM_OPERATION_ID ? { teamOperationId: process.env.VH_TEAM_OPERATION_ID } : {},
|
|
139
|
+
// Advertise composer support before the first SDK response. Otherwise
|
|
140
|
+
// a brand-new session cannot pick a PDF until after sending once.
|
|
141
|
+
attachmentKinds: [...attachmentContent.CLAUDE_ATTACHMENT_KINDS],
|
|
142
|
+
queueCancellation: true,
|
|
143
|
+
capabilities: ["claude-steer-v1", "claude-live-permission-v1", "claude-live-permission-v2", "claude-btw-v1", "claude-runtime-controls-v1", "claude-opus-5-5-v1", index.PROMPT_QUEUE_CAPABILITY],
|
|
144
|
+
// Effective mode this process enforces. Kept current by
|
|
145
|
+
// publishPermissionMode below; the web renders it instead of guessing.
|
|
146
|
+
permissionMode: permissionMode.mapToClaudeMode(initialPermissionMode ?? "default"),
|
|
147
|
+
sandbox: sandboxConfig?.enabled ? sandboxConfig : null,
|
|
148
|
+
dangerouslySkipPermissions,
|
|
149
|
+
...forkedFromSessionId ? { parentSessionId: forkedFromSessionId } : {},
|
|
150
|
+
...forkedFromMessageId ? { forkedFromMessageId } : {},
|
|
151
|
+
...importedFromClaudeSessionId ? { importedFromClaudeSessionId } : {},
|
|
152
|
+
...importTitle ? { summary: { text: importTitle, updatedAt: Date.now() } } : {},
|
|
153
|
+
...isAssistantVariant ? { variant: "assistant" } : {},
|
|
154
|
+
// B-091/B-303: dispatched sessions (daemon spawn RPC exports
|
|
155
|
+
// HAPPY_SPAWNED_BY, B-069) are born tagged with their spawn origin so
|
|
156
|
+
// every list shows where the work came from. NOT on the meta-agent
|
|
157
|
+
// itself — that one is the variant above and never joins the lists at
|
|
158
|
+
// all. (Shared helper: the other flavors get the same tag via
|
|
159
|
+
// createSessionMetadata.)
|
|
160
|
+
...originTags ? { tags: originTags } : {}
|
|
161
|
+
};
|
|
162
|
+
const reconnectSessionId = process.env.HAPPY_RECONNECT_SESSION_ID;
|
|
163
|
+
const reconnectKeyBase64 = process.env.HAPPY_RECONNECT_ENCRYPTION_KEY;
|
|
164
|
+
const reconnectVariant = process.env.HAPPY_RECONNECT_ENCRYPTION_VARIANT;
|
|
165
|
+
const reconnectSeq = process.env.HAPPY_RECONNECT_SEQ;
|
|
166
|
+
const reconnectMetadataVersion = process.env.HAPPY_RECONNECT_METADATA_VERSION;
|
|
167
|
+
const reconnectAgentStateVersion = process.env.HAPPY_RECONNECT_AGENT_STATE_VERSION;
|
|
168
|
+
let response;
|
|
169
|
+
if (reconnectSessionId && reconnectKeyBase64 && reconnectVariant) {
|
|
170
|
+
logger.logger.debug(`[START] Reconnecting to existing session ${reconnectSessionId}`);
|
|
171
|
+
response = {
|
|
172
|
+
id: reconnectSessionId,
|
|
173
|
+
seq: parseInt(reconnectSeq || "0", 10),
|
|
174
|
+
encryptionKey: authRelay.decodeBase64(reconnectKeyBase64),
|
|
175
|
+
encryptionVariant: reconnectVariant,
|
|
176
|
+
metadata,
|
|
177
|
+
metadataVersion: parseInt(reconnectMetadataVersion || "0", 10),
|
|
178
|
+
agentState: state,
|
|
179
|
+
agentStateVersion: parseInt(reconnectAgentStateVersion || "0", 10)
|
|
180
|
+
};
|
|
181
|
+
} else {
|
|
182
|
+
response = await api$1.getOrCreateSession({ tag: sessionTag, metadata, state });
|
|
183
|
+
}
|
|
184
|
+
if (!response) {
|
|
185
|
+
let offlineSessionId = null;
|
|
186
|
+
const { claudeLocal } = await Promise.resolve().then(function () { return require('./claudeLocal-BDuGFnjJ.cjs'); });
|
|
187
|
+
const { createSessionScanner: createSessionScanner2 } = await loadDeps();
|
|
188
|
+
const reconnection = api.startOfflineReconnection({
|
|
189
|
+
serverUrl: configuration.configuration.serverUrl,
|
|
190
|
+
onReconnected: async () => {
|
|
191
|
+
const resp = await api$1.getOrCreateSession({ tag: node_crypto.randomUUID(), metadata, state });
|
|
192
|
+
if (!resp) throw new Error("Server unavailable");
|
|
193
|
+
const session2 = api$1.sessionSyncClient(resp);
|
|
194
|
+
const scanner = await createSessionScanner2({
|
|
195
|
+
sessionId: null,
|
|
196
|
+
workingDirectory,
|
|
197
|
+
onMessage: (msg) => session2.sendClaudeSessionMessage(msg)
|
|
198
|
+
});
|
|
199
|
+
if (offlineSessionId) scanner.onNewSession(offlineSessionId);
|
|
200
|
+
return { session: session2, scanner };
|
|
201
|
+
},
|
|
202
|
+
onNotify: console.log,
|
|
203
|
+
onCleanup: () => {
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
try {
|
|
207
|
+
await claudeLocal({
|
|
208
|
+
path: workingDirectory,
|
|
209
|
+
sessionId: null,
|
|
210
|
+
onSessionFound: (id) => {
|
|
211
|
+
offlineSessionId = id;
|
|
212
|
+
},
|
|
213
|
+
onThinkingChange: () => {
|
|
214
|
+
},
|
|
215
|
+
abort: new AbortController().signal,
|
|
216
|
+
claudeEnvVars: options.claudeEnvVars,
|
|
217
|
+
claudeArgs: options.claudeArgs,
|
|
218
|
+
mcpServers: {},
|
|
219
|
+
allowedTools: [],
|
|
220
|
+
sandboxConfig
|
|
221
|
+
});
|
|
222
|
+
} finally {
|
|
223
|
+
reconnection.cancel();
|
|
224
|
+
}
|
|
225
|
+
process.exit(0);
|
|
226
|
+
}
|
|
227
|
+
logger.logger.debug(`Session created: ${response.id}`);
|
|
228
|
+
await sessionLock.claimSessionOrExit(response.id, { takeover: !!reconnectSessionId, flavor: "claude" });
|
|
229
|
+
if (reconnectSessionId && !await api$1.reactivateSession(response.id)) {
|
|
230
|
+
throw new Error(`Failed to reactivate archived session ${response.id}`);
|
|
231
|
+
}
|
|
232
|
+
let reconnectSeeded = false;
|
|
233
|
+
if (reconnectSessionId) {
|
|
234
|
+
const snapshot = await api$1.getSession(response.id, response.encryptionKey, response.encryptionVariant);
|
|
235
|
+
if (snapshot.ok) {
|
|
236
|
+
response = sessionLock.applyServerSnapshot(response, snapshot.session);
|
|
237
|
+
reconnectSeeded = true;
|
|
238
|
+
logger.logger.debug(`[START] Reconnect seeded from server: seq=${snapshot.session.seq} metadataVersion=${snapshot.session.metadataVersion}`);
|
|
239
|
+
} else {
|
|
240
|
+
response = sessionLock.withoutServerSnapshot(response);
|
|
241
|
+
logger.logger.debug(`[START] Reconnect without server snapshot (${snapshot.reason}); skipping existing messages`);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
const earlySession = startupPlan$1.sessionClientBeforeWebhook ? api$1.sessionSyncClient(response) : null;
|
|
245
|
+
try {
|
|
246
|
+
logger.logger.debug(`[START] Reporting session ${response.id} to daemon`);
|
|
247
|
+
const result = await controlClient.notifyDaemonSessionStarted(response.id, metadata, {
|
|
248
|
+
encryptionKey: authRelay.encodeBase64(response.encryptionKey),
|
|
249
|
+
encryptionVariant: response.encryptionVariant,
|
|
250
|
+
seq: response.seq,
|
|
251
|
+
metadataVersion: response.metadataVersion,
|
|
252
|
+
agentStateVersion: response.agentStateVersion
|
|
253
|
+
});
|
|
254
|
+
if (result.error) {
|
|
255
|
+
logger.logger.debug(`[START] Failed to report to daemon (may not be running):`, result.error);
|
|
256
|
+
} else {
|
|
257
|
+
logger.logger.debug(`[START] Reported session ${response.id} to daemon`);
|
|
258
|
+
}
|
|
259
|
+
} catch (error) {
|
|
260
|
+
logger.logger.debug("[START] Failed to report to daemon (may not be running):", error);
|
|
261
|
+
}
|
|
262
|
+
loadDeps();
|
|
263
|
+
if (!startupPlan$1.registerMachine) {
|
|
264
|
+
void Promise.resolve().then(() => api$1.getOrCreateMachine({ machineId, metadata: machineMetadata.getInitialMachineMetadata() })).catch((error) => logger.logger.debug("[START] Background machine registration failed:", error));
|
|
265
|
+
}
|
|
266
|
+
let deps;
|
|
267
|
+
try {
|
|
268
|
+
deps = await loadDeps();
|
|
269
|
+
} catch (error) {
|
|
270
|
+
logger.logger.warn(`[START] Failed to load the Claude runner modules for session ${response.id}; marking it offline and exiting: ${error instanceof Error ? error.message : String(error)}`);
|
|
271
|
+
try {
|
|
272
|
+
await earlySession?.close();
|
|
273
|
+
} catch {
|
|
274
|
+
}
|
|
275
|
+
try {
|
|
276
|
+
await api$1.deactivateSession(response.id);
|
|
277
|
+
} catch {
|
|
278
|
+
}
|
|
279
|
+
process.exit(1);
|
|
280
|
+
}
|
|
281
|
+
const {
|
|
282
|
+
loop,
|
|
283
|
+
MessageQueue2,
|
|
284
|
+
claudeModeHash,
|
|
285
|
+
parseSpecialCommand,
|
|
286
|
+
getEnvironmentInfo,
|
|
287
|
+
startHappyServer,
|
|
288
|
+
startHookServer,
|
|
289
|
+
EditReportThrottle,
|
|
290
|
+
extractClaudeEditPaths,
|
|
291
|
+
reportSessionEditToDaemon,
|
|
292
|
+
generateHookSettingsFile,
|
|
293
|
+
cleanupHookSettingsFile,
|
|
294
|
+
registerKillSessionHandler,
|
|
295
|
+
SessionExitGate,
|
|
296
|
+
exitIntentFromArchiveFlag,
|
|
297
|
+
settleSessionOnExit,
|
|
298
|
+
registerSideQuestionHandler,
|
|
299
|
+
writeSideQuestionSettingsFile,
|
|
300
|
+
claudeCheckSession,
|
|
301
|
+
createSessionScanner,
|
|
302
|
+
getProjectPath,
|
|
303
|
+
RawJSONLinesSchema,
|
|
304
|
+
TitleGenerator,
|
|
305
|
+
BoardAnalyzer,
|
|
306
|
+
FileRateLimiter,
|
|
307
|
+
createSelfReportState,
|
|
308
|
+
withAssistantDenylist,
|
|
309
|
+
contentLogMetadata,
|
|
310
|
+
resolveMessageMode,
|
|
311
|
+
claudePrewarmEligibility,
|
|
312
|
+
claudePrewarmSessionEligibility,
|
|
313
|
+
prewarmCacheFile,
|
|
314
|
+
readPrewarmCache,
|
|
315
|
+
writePrewarmCache,
|
|
316
|
+
prewarmPredictionMeta,
|
|
317
|
+
evaluatePrewarmPrediction,
|
|
318
|
+
acquirePrewarmSlot,
|
|
319
|
+
prewarmSlotDir,
|
|
320
|
+
PrewarmHookGate,
|
|
321
|
+
createClaudePrewarmLease,
|
|
322
|
+
disposeAllClaudePrewarms,
|
|
323
|
+
formatPrewarmLine
|
|
324
|
+
} = deps;
|
|
325
|
+
logger.logger.debugLargeJson("[START] Happy process started", getEnvironmentInfo());
|
|
326
|
+
const session = earlySession ?? api$1.sessionSyncClient(response, reconnectSeeded ? { initialSeq: response.seq } : void 0);
|
|
327
|
+
if (reconnectSessionId) {
|
|
328
|
+
session.suppressNextArchiveSignal();
|
|
329
|
+
if (!reconnectSeeded) session.skipExistingMessages();
|
|
330
|
+
void session.cancelUndeliveredQueuedInputs();
|
|
331
|
+
session.updateMetadata((meta) => ({
|
|
332
|
+
...sessionLock.mergeReconnectMetadata(meta, metadata, Date.now()),
|
|
333
|
+
queueCancellation: true,
|
|
334
|
+
// B-262 batch 2 (B5): a reconnected process re-publishes the mode
|
|
335
|
+
// IT enforces; the server copy may still carry the previous
|
|
336
|
+
// process's value.
|
|
337
|
+
permissionMode: permissionMode.mapToClaudeMode(initialPermissionMode ?? "default")
|
|
338
|
+
}));
|
|
339
|
+
}
|
|
340
|
+
const forkClaudeSessionId = process.env.HAPPY_FORK_CLAUDE_SESSION_ID;
|
|
341
|
+
if (!reconnectSessionId && forkClaudeSessionId) {
|
|
342
|
+
const jsonlPath = path.join(getProjectPath(workingDirectory), `${forkClaudeSessionId}.jsonl`);
|
|
343
|
+
try {
|
|
344
|
+
const file = await promises.readFile(jsonlPath, "utf-8");
|
|
345
|
+
const lines = file.split("\n");
|
|
346
|
+
let backfilled = 0;
|
|
347
|
+
for (const line of lines) {
|
|
348
|
+
if (line.trim().length === 0) continue;
|
|
349
|
+
let parsed;
|
|
350
|
+
try {
|
|
351
|
+
parsed = JSON.parse(line);
|
|
352
|
+
} catch {
|
|
353
|
+
continue;
|
|
354
|
+
}
|
|
355
|
+
const result = RawJSONLinesSchema.safeParse(parsed);
|
|
356
|
+
if (!result.success) continue;
|
|
357
|
+
session.sendClaudeSessionMessage(result.data);
|
|
358
|
+
backfilled += 1;
|
|
359
|
+
}
|
|
360
|
+
logger.logger.debug(`[FORK BACKFILL] Replayed ${backfilled} historical messages from ${jsonlPath}`);
|
|
361
|
+
session.updateMetadata((meta) => ({ ...meta, claudeSessionId: forkClaudeSessionId }));
|
|
362
|
+
} catch (error) {
|
|
363
|
+
logger.logger.debug(`[FORK BACKFILL] Failed to read ${jsonlPath}:`, error);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
const recentAppPromptsMaxAgeMs = 5 * 60 * 1e3;
|
|
367
|
+
const recentAppPrompts = [];
|
|
368
|
+
const promptDedupeKey = (text) => attachmentContent.stripAttachmentManifest(text).trim();
|
|
369
|
+
const recordAppPrompt = (text) => {
|
|
370
|
+
const now = Date.now();
|
|
371
|
+
recentAppPrompts.push({ text: promptDedupeKey(text), addedAt: now });
|
|
372
|
+
const cutoff = now - recentAppPromptsMaxAgeMs;
|
|
373
|
+
while (recentAppPrompts.length > 0 && recentAppPrompts[0].addedAt < cutoff) {
|
|
374
|
+
recentAppPrompts.shift();
|
|
375
|
+
}
|
|
376
|
+
};
|
|
377
|
+
const consumeAppPrompt = (text) => {
|
|
378
|
+
const cutoff = Date.now() - recentAppPromptsMaxAgeMs;
|
|
379
|
+
const key = promptDedupeKey(text);
|
|
380
|
+
for (let i = 0; i < recentAppPrompts.length; i++) {
|
|
381
|
+
const entry = recentAppPrompts[i];
|
|
382
|
+
if (entry.addedAt < cutoff) continue;
|
|
383
|
+
if (entry.text === key) {
|
|
384
|
+
recentAppPrompts.splice(i, 1);
|
|
385
|
+
return true;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
return false;
|
|
389
|
+
};
|
|
390
|
+
let currentRunMode = options.startingMode ?? "local";
|
|
391
|
+
const titleGenerator = new TitleGenerator(session);
|
|
392
|
+
const boardSelfReport = createSelfReportState();
|
|
393
|
+
const boardAnalyzer = new BoardAnalyzer(session, {
|
|
394
|
+
enabled: settings?.boardLlm === true,
|
|
395
|
+
selfReportState: boardSelfReport,
|
|
396
|
+
fetchTasks: async () => {
|
|
397
|
+
const item = await api$1.kvGet("vh.board-tasks.v1");
|
|
398
|
+
if (!item) return null;
|
|
399
|
+
try {
|
|
400
|
+
const parsed = JSON.parse(Buffer.from(item.value, "base64").toString("utf8"));
|
|
401
|
+
if (!Array.isArray(parsed.tasks)) return null;
|
|
402
|
+
return parsed.tasks.filter((t) => typeof t?.id === "string" && typeof t?.title === "string" && t.status === "open").map((t) => ({ id: t.id, title: t.title }));
|
|
403
|
+
} catch {
|
|
404
|
+
return null;
|
|
405
|
+
}
|
|
406
|
+
},
|
|
407
|
+
rateLimiter: new FileRateLimiter(path.resolve(configuration.configuration.happyHomeDir, "board-analyzer-rate.json"))
|
|
408
|
+
});
|
|
409
|
+
session.on("claude-session-message", (body) => boardAnalyzer.noteClaudeMessage(body));
|
|
410
|
+
session.on("turn-ended", () => boardAnalyzer.onTurnEnd());
|
|
411
|
+
const editThrottle = new EditReportThrottle();
|
|
412
|
+
session.on("claude-session-message", (body) => {
|
|
413
|
+
try {
|
|
414
|
+
for (const edit of editThrottle.take(extractClaudeEditPaths(body))) {
|
|
415
|
+
reportSessionEditToDaemon({ sessionId: response.id, path: edit.path, tool: edit.tool, cwd: workingDirectory, ...edit.at !== void 0 ? { at: edit.at } : {} });
|
|
416
|
+
}
|
|
417
|
+
} catch (error) {
|
|
418
|
+
logger.logger.debug("[START] edit report failed:", error);
|
|
419
|
+
}
|
|
420
|
+
});
|
|
421
|
+
options = { ...options, claudeEnvVars: { ...options.claudeEnvVars, VH_PEER_SESSION_ID: response.id } };
|
|
422
|
+
const initialScannerSessionId = forkClaudeSessionId ?? (metadata.claudeSessionId ?? null);
|
|
423
|
+
const remoteScanner = await createSessionScanner({
|
|
424
|
+
sessionId: initialScannerSessionId,
|
|
425
|
+
workingDirectory,
|
|
426
|
+
onMessage: (raw) => {
|
|
427
|
+
if (currentRunMode !== "remote") return;
|
|
428
|
+
if (raw.type !== "user") return;
|
|
429
|
+
if (raw.isSidechain) return;
|
|
430
|
+
const content = raw.message?.content;
|
|
431
|
+
if (typeof content !== "string") return;
|
|
432
|
+
if (content.trim().length === 0) return;
|
|
433
|
+
if (consumeAppPrompt(content)) return;
|
|
434
|
+
titleGenerator.maybeGenerate(content);
|
|
435
|
+
boardAnalyzer.noteUserMessage(content);
|
|
436
|
+
session.sendClaudeSessionMessage(raw);
|
|
437
|
+
}
|
|
438
|
+
});
|
|
439
|
+
const happyServer = await startHappyServer(session, { assistant: isAssistantVariant, selfReportState: boardSelfReport });
|
|
440
|
+
logger.logger.debug(`[START] Happy MCP server started at ${happyServer.url}`);
|
|
441
|
+
let currentSession = null;
|
|
442
|
+
const prewarmHookGate = new PrewarmHookGate();
|
|
443
|
+
let scannerAnnouncedSessionId = null;
|
|
444
|
+
const announceSessionToScanner = (sessionId) => {
|
|
445
|
+
if (sessionId === scannerAnnouncedSessionId) return;
|
|
446
|
+
scannerAnnouncedSessionId = sessionId;
|
|
447
|
+
void Promise.resolve(remoteScanner.onNewSession(sessionId, { treatExistingAsProcessed: true })).catch((error) => logger.logger.debug("[START] remote scanner onNewSession failed:", error));
|
|
448
|
+
};
|
|
449
|
+
const hookServer = await startHookServer({
|
|
450
|
+
onSessionHook: (sessionId, data, hookMeta) => {
|
|
451
|
+
if (!prewarmHookGate.accepts(hookMeta?.source)) {
|
|
452
|
+
logger.logger.debug(`[START] Session hook from an unadopted warm Claude process ignored (${hookMeta?.source})`);
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
logger.logger.debug(`[START] Session hook received: ${sessionId}`, contentLogMetadata(data));
|
|
456
|
+
announceSessionToScanner(sessionId);
|
|
457
|
+
if (currentSession) {
|
|
458
|
+
const previousSessionId = currentSession.sessionId;
|
|
459
|
+
if (previousSessionId !== sessionId) {
|
|
460
|
+
logger.logger.debug(`[START] Claude session ID changed: ${previousSessionId} -> ${sessionId}`);
|
|
461
|
+
currentSession.onSessionFound(sessionId);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
});
|
|
466
|
+
logger.logger.debug(`[START] Hook server started on port ${hookServer.port}`);
|
|
467
|
+
const hookSettingsPath = generateHookSettingsFile(hookServer.port);
|
|
468
|
+
logger.logger.debug(`[START] Generated hook settings file: ${hookSettingsPath}`);
|
|
469
|
+
const logPath = logger.logger.logFilePath;
|
|
470
|
+
logger.logger.infoDeveloper(`Session: ${response.id}`);
|
|
471
|
+
logger.logger.infoDeveloper(`Logs: ${logPath}`);
|
|
472
|
+
session.updateAgentState((currentState) => ({
|
|
473
|
+
...currentState,
|
|
474
|
+
controlledByUser: options.startingMode !== "remote"
|
|
475
|
+
}));
|
|
476
|
+
const messageQueue = new MessageQueue2(claudeModeHash);
|
|
477
|
+
const promptQueueDrain = session.attachPromptQueueDrain({
|
|
478
|
+
isIdle: () => messageQueue.size() === 0 && messageQueue.isWaiting()
|
|
479
|
+
});
|
|
480
|
+
messageQueue.setOnWait(() => promptQueueDrain.onIdle());
|
|
481
|
+
messageQueue.setOnDiscard((entries) => {
|
|
482
|
+
const byReason = /* @__PURE__ */ new Map();
|
|
483
|
+
for (const entry of entries) {
|
|
484
|
+
if (!entry.sourceId) continue;
|
|
485
|
+
const list = byReason.get(entry.reason) ?? [];
|
|
486
|
+
list.push(entry.sourceId);
|
|
487
|
+
byReason.set(entry.reason, list);
|
|
488
|
+
}
|
|
489
|
+
for (const [reason, keys] of byReason) session.sendQueueCancelReason(keys, reason);
|
|
490
|
+
});
|
|
491
|
+
session.rpcHandlerManager.registerHandler("cancelQueuedMessage", async (params) => ({
|
|
492
|
+
removed: messageQueue.cancelPending(
|
|
493
|
+
typeof params?.localKey === "string" ? params.localKey : void 0,
|
|
494
|
+
typeof params?.text === "string" ? params.text : void 0
|
|
495
|
+
)
|
|
496
|
+
}));
|
|
497
|
+
let currentPermissionMode = initialPermissionMode;
|
|
498
|
+
let publishedPermissionMode = permissionMode.mapToClaudeMode(initialPermissionMode ?? "default");
|
|
499
|
+
const publishPermissionMode = (mode) => {
|
|
500
|
+
currentPermissionMode = mode;
|
|
501
|
+
const published = permissionMode.mapToClaudeMode(mode ?? "default");
|
|
502
|
+
if (published === publishedPermissionMode) return;
|
|
503
|
+
publishedPermissionMode = published;
|
|
504
|
+
session.updateMetadata((meta) => ({ ...meta, permissionMode: published }));
|
|
505
|
+
};
|
|
506
|
+
const publishEffectivePermissionMode = (effective) => {
|
|
507
|
+
const decision = permissionMode.reconcilePublishedPermissionMode({
|
|
508
|
+
intent: permissionMode.mapToClaudeMode(currentPermissionMode ?? "default"),
|
|
509
|
+
published: publishedPermissionMode,
|
|
510
|
+
effective
|
|
511
|
+
});
|
|
512
|
+
if (!decision) return;
|
|
513
|
+
if (decision.mismatch) {
|
|
514
|
+
logger.logger.warn(`[loop] Claude Code enforces permission mode '${effective}' but this process asked for '${permissionMode.mapToClaudeMode(currentPermissionMode ?? "default")}' \u2014 check ~/.claude settings (permissions.deny/ask/defaultMode/disableBypassPermissionsMode). Publishing the effective mode.`);
|
|
515
|
+
}
|
|
516
|
+
if (decision.publish !== publishedPermissionMode) {
|
|
517
|
+
publishedPermissionMode = decision.publish;
|
|
518
|
+
session.updateMetadata((meta) => ({ ...meta, permissionMode: decision.publish }));
|
|
519
|
+
}
|
|
520
|
+
};
|
|
521
|
+
let lastExplicitModeSwitchAt = 0;
|
|
522
|
+
let currentModel = options.model ?? DEFAULT_CLAUDE_MODEL;
|
|
523
|
+
let currentFallbackModel = void 0;
|
|
524
|
+
let currentCustomSystemPrompt = void 0;
|
|
525
|
+
let currentAppendSystemPrompt = void 0;
|
|
526
|
+
let currentAllowedTools = void 0;
|
|
527
|
+
let currentDisallowedTools = withAssistantDenylist(void 0, isAssistantVariant);
|
|
528
|
+
let currentEffort = DEFAULT_CLAUDE_EFFORT;
|
|
529
|
+
registerSideQuestionHandler(session.rpcHandlerManager, {
|
|
530
|
+
getClaudeSessionId: () => {
|
|
531
|
+
const id = currentSession?.sessionId ?? null;
|
|
532
|
+
return id && claudeCheckSession(id, workingDirectory) ? id : null;
|
|
533
|
+
},
|
|
534
|
+
getModel: () => currentModel,
|
|
535
|
+
cwd: workingDirectory,
|
|
536
|
+
getEnv: () => options.claudeEnvVars,
|
|
537
|
+
settingsPath: writeSideQuestionSettingsFile(configuration.configuration.happyHomeDir),
|
|
538
|
+
// B-482: in remote mode the launcher parks the live Query's /btw entry
|
|
539
|
+
// on the session while the Query is alive; local mode never sets it.
|
|
540
|
+
getLiveQuery: () => currentSession?.getSideQuestionLive() ?? null
|
|
541
|
+
});
|
|
542
|
+
const resetCurrentModeDefaults = () => {
|
|
543
|
+
currentModel = options.model ?? DEFAULT_CLAUDE_MODEL;
|
|
544
|
+
currentFallbackModel = void 0;
|
|
545
|
+
currentCustomSystemPrompt = void 0;
|
|
546
|
+
currentAppendSystemPrompt = void 0;
|
|
547
|
+
currentAllowedTools = void 0;
|
|
548
|
+
currentDisallowedTools = withAssistantDenylist(void 0, isAssistantVariant);
|
|
549
|
+
currentEffort = DEFAULT_CLAUDE_EFFORT;
|
|
550
|
+
logger.logger.debug("[loop] Reset current mode defaults after abort");
|
|
551
|
+
};
|
|
552
|
+
const currentModeState = () => ({
|
|
553
|
+
permissionMode: currentPermissionMode,
|
|
554
|
+
model: currentModel,
|
|
555
|
+
fallbackModel: currentFallbackModel,
|
|
556
|
+
customSystemPrompt: currentCustomSystemPrompt,
|
|
557
|
+
appendSystemPrompt: currentAppendSystemPrompt,
|
|
558
|
+
allowedTools: currentAllowedTools,
|
|
559
|
+
disallowedTools: currentDisallowedTools,
|
|
560
|
+
effort: currentEffort
|
|
561
|
+
});
|
|
562
|
+
const prewarmCachePath = prewarmCacheFile(configuration.configuration.happyHomeDir);
|
|
563
|
+
let prewarmCache = readPrewarmCache(prewarmCachePath);
|
|
564
|
+
const prewarmSessionInput = {
|
|
565
|
+
env: { ...process.env, ...prewarmSpawnEnv },
|
|
566
|
+
startedBy: options.startedBy,
|
|
567
|
+
startingMode: options.startingMode,
|
|
568
|
+
claudeArgs: options.claudeArgs,
|
|
569
|
+
setting: settings?.claudePrewarm
|
|
570
|
+
};
|
|
571
|
+
const prewarmCtx = () => ({ sandboxEnabled, isAssistantVariant, staleModeSnapshot: false });
|
|
572
|
+
const predictFirstMessageMode = () => prewarmCache ? resolveMessageMode(currentModeState(), prewarmPredictionMeta(prewarmCache), prewarmCtx()).mode : null;
|
|
573
|
+
let firstMessageScored = false;
|
|
574
|
+
const scoreFirstMessage = (meta, predicted, actual) => {
|
|
575
|
+
if (firstMessageScored) return;
|
|
576
|
+
firstMessageScored = true;
|
|
577
|
+
if (meta?.sentFrom !== "web") return;
|
|
578
|
+
if (!claudePrewarmSessionEligibility({ ...prewarmSessionInput, env: { ...process.env, ...prewarmSpawnEnv } }).eligible) return;
|
|
579
|
+
const { outcome, next } = evaluatePrewarmPrediction({ predicted, actual, meta, previous: prewarmCache });
|
|
580
|
+
logger.logger.debug(`[CLAUDE PREWARM] prediction ${outcome}${next ? ` consecutiveMisses=${next.consecutiveMisses}` : ""}`);
|
|
581
|
+
if (next && writePrewarmCache(prewarmCachePath, next)) prewarmCache = next;
|
|
582
|
+
};
|
|
583
|
+
let userMessageSeen = false;
|
|
584
|
+
const prewarmTagFiles = /* @__PURE__ */ new Set();
|
|
585
|
+
const createPrewarm = () => {
|
|
586
|
+
const eligibility = claudePrewarmEligibility({
|
|
587
|
+
...prewarmSessionInput,
|
|
588
|
+
env: { ...process.env, ...prewarmSpawnEnv },
|
|
589
|
+
cache: prewarmCache
|
|
590
|
+
});
|
|
591
|
+
if (!eligibility.eligible) {
|
|
592
|
+
logger.logger.debug(formatPrewarmLine("skipped", eligibility.reason));
|
|
593
|
+
return null;
|
|
594
|
+
}
|
|
595
|
+
if (userMessageSeen) {
|
|
596
|
+
logger.logger.debug(formatPrewarmLine("skipped", "message-already-received"));
|
|
597
|
+
return null;
|
|
598
|
+
}
|
|
599
|
+
const slot = acquirePrewarmSlot(prewarmSlotDir(configuration.configuration.happyHomeDir));
|
|
600
|
+
if (!slot) {
|
|
601
|
+
logger.logger.debug(formatPrewarmLine("skipped", "concurrency-limit"));
|
|
602
|
+
return null;
|
|
603
|
+
}
|
|
604
|
+
const predicted = predictFirstMessageMode();
|
|
605
|
+
if (!predicted) {
|
|
606
|
+
slot.release();
|
|
607
|
+
return null;
|
|
608
|
+
}
|
|
609
|
+
const tag = prewarmHookGate.newTag();
|
|
610
|
+
let tagHookSettingsPath;
|
|
611
|
+
try {
|
|
612
|
+
tagHookSettingsPath = generateHookSettingsFile(hookServer.port, tag);
|
|
613
|
+
} catch (error) {
|
|
614
|
+
slot.release();
|
|
615
|
+
logger.logger.debug(formatPrewarmLine("skipped", "hook-settings"));
|
|
616
|
+
return null;
|
|
617
|
+
}
|
|
618
|
+
prewarmTagFiles.add(tagHookSettingsPath);
|
|
619
|
+
return createClaudePrewarmLease({
|
|
620
|
+
mode: predicted,
|
|
621
|
+
hookSettingsPath: tagHookSettingsPath,
|
|
622
|
+
tag,
|
|
623
|
+
gate: prewarmHookGate,
|
|
624
|
+
slot,
|
|
625
|
+
// Only removed on discard: an adopted process keeps reading its
|
|
626
|
+
// settings file (later SessionStart events, e.g. /compact); it goes
|
|
627
|
+
// with the other hook files at exit.
|
|
628
|
+
cleanupHookSettings: () => {
|
|
629
|
+
cleanupHookSettingsFile(tagHookSettingsPath);
|
|
630
|
+
prewarmTagFiles.delete(tagHookSettingsPath);
|
|
631
|
+
},
|
|
632
|
+
log: (line) => logger.logger.debug(line)
|
|
633
|
+
});
|
|
634
|
+
};
|
|
635
|
+
const cleanupPrewarm = () => {
|
|
636
|
+
disposeAllClaudePrewarms("shutdown");
|
|
637
|
+
for (const file of prewarmTagFiles) cleanupHookSettingsFile(file);
|
|
638
|
+
prewarmTagFiles.clear();
|
|
639
|
+
};
|
|
640
|
+
session.onFileEvent((fileEvent) => {
|
|
641
|
+
const ev = fileEvent.content.data.ev;
|
|
642
|
+
logger.logger.debug(`[loop] File event received: ${ev.name} (${ev.size} bytes, ref: ${ev.ref})`);
|
|
643
|
+
const downloadPromise = (async () => {
|
|
644
|
+
try {
|
|
645
|
+
const decrypted = await session.downloadAndDecryptAttachment(ev.ref);
|
|
646
|
+
if (!decrypted) {
|
|
647
|
+
logger.logger.debug(`[loop] Failed to decrypt attachment: ${ev.name}`);
|
|
648
|
+
return null;
|
|
649
|
+
}
|
|
650
|
+
logger.logger.debug(`[loop] Attachment decrypted: ${ev.name} (${decrypted.length} bytes)`);
|
|
651
|
+
return { data: decrypted, mimeType: ev.mimeType ?? "image/jpeg", name: ev.name };
|
|
652
|
+
} catch (error) {
|
|
653
|
+
logger.logger.debug(`[loop] Failed to download attachment: ${ev.name}`, { error });
|
|
654
|
+
return null;
|
|
655
|
+
}
|
|
656
|
+
})();
|
|
657
|
+
session.trackAttachmentDownload(downloadPromise);
|
|
658
|
+
});
|
|
659
|
+
session.onUserMessage(async (message) => {
|
|
660
|
+
userMessageSeen = true;
|
|
661
|
+
if (message?.content?.text) {
|
|
662
|
+
titleGenerator.maybeGenerate(message.content.text);
|
|
663
|
+
boardAnalyzer.noteUserMessage(message.content.text);
|
|
664
|
+
}
|
|
665
|
+
const attachmentsForThisMessage = await session.drainAttachmentsForUserMessage();
|
|
666
|
+
const messageCreatedAt = message.createdAt;
|
|
667
|
+
const staleModeSnapshot = message.meta?.permissionMode !== void 0 && typeof messageCreatedAt === "number" && messageCreatedAt < lastExplicitModeSwitchAt;
|
|
668
|
+
if (staleModeSnapshot) {
|
|
669
|
+
logger.logger.debug(`[loop] Ignoring stale meta.permissionMode=${message.meta?.permissionMode} (message ${messageCreatedAt} predates explicit switch ${lastExplicitModeSwitchAt}); keeping ${currentPermissionMode}`);
|
|
670
|
+
}
|
|
671
|
+
const previousPermissionMode = currentPermissionMode;
|
|
672
|
+
const predictedFirstMode = firstMessageScored ? null : predictFirstMessageMode();
|
|
673
|
+
const resolved = resolveMessageMode(currentModeState(), message.meta, { sandboxEnabled, isAssistantVariant, staleModeSnapshot });
|
|
674
|
+
if ("publishPermissionMode" in resolved) {
|
|
675
|
+
publishPermissionMode(resolved.publishPermissionMode);
|
|
676
|
+
const ignoredDefaultDowngrade = (previousPermissionMode === "bypassPermissions" || previousPermissionMode === "yolo") && message.meta?.permissionMode === "default" && currentPermissionMode === previousPermissionMode;
|
|
677
|
+
logger.logger.debug(ignoredDefaultDowngrade ? `[loop] Ignoring permission mode downgrade from ${previousPermissionMode} to default` : `[loop] Permission mode updated from user message to: ${currentPermissionMode}`);
|
|
678
|
+
}
|
|
679
|
+
currentModel = resolved.next.model;
|
|
680
|
+
currentFallbackModel = resolved.next.fallbackModel;
|
|
681
|
+
currentCustomSystemPrompt = resolved.next.customSystemPrompt;
|
|
682
|
+
currentAppendSystemPrompt = resolved.next.appendSystemPrompt;
|
|
683
|
+
currentAllowedTools = resolved.next.allowedTools;
|
|
684
|
+
currentDisallowedTools = resolved.next.disallowedTools;
|
|
685
|
+
currentEffort = resolved.next.effort;
|
|
686
|
+
if (resolved.ignoredEffort !== void 0) {
|
|
687
|
+
logger.logger.debug(`[loop] Ignoring invalid effort from user message: ${resolved.ignoredEffort}`);
|
|
688
|
+
}
|
|
689
|
+
logger.logger.debug(`[loop] Message mode: permission=${resolved.mode.permissionMode} model=${resolved.mode.model || "default"} effort=${resolved.mode.effort ?? "default"} appendSystemPrompt=${resolved.mode.appendSystemPrompt ? "set" : "none"} customSystemPrompt=${resolved.mode.customSystemPrompt ? "set" : "none"}`);
|
|
690
|
+
scoreFirstMessage(message.meta, predictedFirstMode, resolved.mode);
|
|
691
|
+
const resolvedMode = resolved.mode;
|
|
692
|
+
const specialCommand = parseSpecialCommand(message.content.text);
|
|
693
|
+
if (specialCommand.type === "compact") {
|
|
694
|
+
logger.logger.debug("[start] Detected /compact command");
|
|
695
|
+
const enhancedMode2 = resolvedMode;
|
|
696
|
+
messageQueue.pushIsolateAndClear(specialCommand.originalMessage || message.content.text, enhancedMode2, attachmentsForThisMessage, message.localKey);
|
|
697
|
+
logger.logger.debug("[start] /compact command pushed to queue:", {
|
|
698
|
+
...contentLogMetadata(message.content.text),
|
|
699
|
+
attachmentCount: attachmentsForThisMessage?.length ?? 0,
|
|
700
|
+
hasMetadata: Boolean(message.meta)
|
|
701
|
+
});
|
|
702
|
+
return;
|
|
703
|
+
}
|
|
704
|
+
if (specialCommand.type === "clear") {
|
|
705
|
+
logger.logger.debug("[start] Detected /clear command");
|
|
706
|
+
const enhancedMode2 = resolvedMode;
|
|
707
|
+
messageQueue.pushIsolateAndClear(specialCommand.originalMessage || message.content.text, enhancedMode2, attachmentsForThisMessage, message.localKey);
|
|
708
|
+
logger.logger.debug("[start] /clear command pushed to queue:", {
|
|
709
|
+
...contentLogMetadata(message.content.text),
|
|
710
|
+
attachmentCount: attachmentsForThisMessage?.length ?? 0,
|
|
711
|
+
hasMetadata: Boolean(message.meta)
|
|
712
|
+
});
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
if (specialCommand.type === "mcp" || specialCommand.type === "skills") {
|
|
716
|
+
if (currentRunMode === "local") {
|
|
717
|
+
logger.logger.debug(`[start] /${specialCommand.type} in local mode \u2014 passing through to Claude Code`);
|
|
718
|
+
} else {
|
|
719
|
+
logger.logger.debug(`[start] Detected /${specialCommand.type} command in remote mode`);
|
|
720
|
+
const metadata2 = session.getMetadata();
|
|
721
|
+
let responseText;
|
|
722
|
+
if (specialCommand.type === "mcp") {
|
|
723
|
+
const servers = metadata2?.mcpServers;
|
|
724
|
+
if (servers && servers.length > 0) {
|
|
725
|
+
responseText = "**MCP Servers**\n\n" + servers.map((s) => `- **${s.name}** \u2014 ${s.status}`).join("\n");
|
|
726
|
+
} else {
|
|
727
|
+
responseText = "No MCP servers configured. Session may still be initializing \u2014 try again after sending a message.";
|
|
728
|
+
}
|
|
729
|
+
} else {
|
|
730
|
+
const skills = metadata2?.skills ?? metadata2?.slashCommands;
|
|
731
|
+
if (skills && skills.length > 0) {
|
|
732
|
+
responseText = "**Available Skills**\n\n" + skills.map((s) => `- /${s}`).join("\n");
|
|
733
|
+
} else {
|
|
734
|
+
responseText = "No skills available. Session may still be initializing \u2014 try again after sending a message.";
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
session.sendClaudeSessionMessage({
|
|
738
|
+
type: "assistant",
|
|
739
|
+
uuid: node_crypto.randomUUID(),
|
|
740
|
+
parentUuid: null,
|
|
741
|
+
isSidechain: false,
|
|
742
|
+
sessionId: session.sessionId || "unknown",
|
|
743
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
744
|
+
message: {
|
|
745
|
+
role: "assistant",
|
|
746
|
+
model: "system",
|
|
747
|
+
content: [{ type: "text", text: responseText }]
|
|
748
|
+
}
|
|
749
|
+
});
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
const enhancedMode = resolvedMode;
|
|
754
|
+
if (message.meta?.delivery === "steer" && currentRunMode === "remote") {
|
|
755
|
+
const accepted = await currentSession?.trySteer({
|
|
756
|
+
message: message.content.text,
|
|
757
|
+
mode: enhancedMode,
|
|
758
|
+
attachments: attachmentsForThisMessage
|
|
759
|
+
}) === true;
|
|
760
|
+
if (accepted) {
|
|
761
|
+
logger.logger.debug("User message steered into current turn:", {
|
|
762
|
+
...contentLogMetadata(message.content.text),
|
|
763
|
+
attachmentCount: attachmentsForThisMessage?.length ?? 0
|
|
764
|
+
});
|
|
765
|
+
return;
|
|
766
|
+
}
|
|
767
|
+
logger.logger.debug("[start] Steer arrived without a compatible live turn; falling back to queue");
|
|
768
|
+
}
|
|
769
|
+
messageQueue.push(message.content.text, enhancedMode, attachmentsForThisMessage, message.localKey);
|
|
770
|
+
logger.logger.debug("User message pushed to queue:", {
|
|
771
|
+
...contentLogMetadata(message.content.text),
|
|
772
|
+
attachmentCount: attachmentsForThisMessage?.length ?? 0,
|
|
773
|
+
hasMetadata: Boolean(message.meta)
|
|
774
|
+
});
|
|
775
|
+
});
|
|
776
|
+
const exitGate = new SessionExitGate();
|
|
777
|
+
const cleanup = async (opts = { archive: true }) => {
|
|
778
|
+
const intent = exitGate.claim(exitIntentFromArchiveFlag(opts.archive));
|
|
779
|
+
if (intent === null) {
|
|
780
|
+
logger.logger.debug(`[START] Termination (archive=${opts.archive ?? true}) ignored: cleanup already running as ${exitGate.current}`);
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
783
|
+
logger.logger.debug(`[START] Received termination signal, cleaning up (archive=${intent === "archive"})...`);
|
|
784
|
+
promptQueueDrain.close();
|
|
785
|
+
cleanupPrewarm();
|
|
786
|
+
try {
|
|
787
|
+
if (session) {
|
|
788
|
+
currentSession?.cleanup();
|
|
789
|
+
await settleSessionOnExit(session.sessionId, intent, {
|
|
790
|
+
updateMetadata: (handler) => session.updateMetadata(handler),
|
|
791
|
+
sendSessionDeath: () => session.sendSessionDeath(),
|
|
792
|
+
archiveSession: (id) => api$1.archiveSession(id),
|
|
793
|
+
deactivateSession: (id) => api$1.deactivateSession(id),
|
|
794
|
+
log: (message, ...args) => logger.logger.debug(message, ...args)
|
|
795
|
+
});
|
|
796
|
+
await session.flush();
|
|
797
|
+
await session.close();
|
|
798
|
+
}
|
|
799
|
+
happyServer.stop();
|
|
800
|
+
hookServer.stop();
|
|
801
|
+
cleanupHookSettingsFile(hookSettingsPath);
|
|
802
|
+
await remoteScanner.cleanup();
|
|
803
|
+
logger.logger.debug("[START] Cleanup complete, exiting");
|
|
804
|
+
process.exit(0);
|
|
805
|
+
} catch (error) {
|
|
806
|
+
logger.logger.debug("[START] Error during cleanup:", error);
|
|
807
|
+
process.exit(1);
|
|
808
|
+
}
|
|
809
|
+
};
|
|
810
|
+
process.on("SIGTERM", () => {
|
|
811
|
+
void cleanup({ archive: false });
|
|
812
|
+
});
|
|
813
|
+
process.on("SIGINT", () => {
|
|
814
|
+
void cleanup({ archive: false });
|
|
815
|
+
});
|
|
816
|
+
process.on("uncaughtException", (error) => {
|
|
817
|
+
logger.logger.debug("[START] Uncaught exception:", error);
|
|
818
|
+
if (currentSession?.isAborting()) {
|
|
819
|
+
logger.logger.debug("[START] Uncaught exception during abort teardown \u2014 swallowing, not archiving");
|
|
820
|
+
return;
|
|
821
|
+
}
|
|
822
|
+
void cleanup({ archive: true });
|
|
823
|
+
});
|
|
824
|
+
process.on("unhandledRejection", (reason) => {
|
|
825
|
+
logger.logger.debug("[START] Unhandled rejection:", reason);
|
|
826
|
+
if (currentSession?.isAborting()) {
|
|
827
|
+
logger.logger.debug("[START] Unhandled rejection during abort teardown \u2014 swallowing, not archiving");
|
|
828
|
+
return;
|
|
829
|
+
}
|
|
830
|
+
void cleanup({ archive: true });
|
|
831
|
+
});
|
|
832
|
+
registerKillSessionHandler(session.rpcHandlerManager, () => cleanup({ archive: true }), session);
|
|
833
|
+
const exitCode = await loop({
|
|
834
|
+
path: workingDirectory,
|
|
835
|
+
model: options.model,
|
|
836
|
+
permissionMode: initialPermissionMode,
|
|
837
|
+
startingMode: options.startingMode,
|
|
838
|
+
messageQueue,
|
|
839
|
+
api: api$1,
|
|
840
|
+
allowedTools: happyServer.toolNames.map((toolName) => `mcp__happy__${toolName}`),
|
|
841
|
+
onModeChange: (newMode) => {
|
|
842
|
+
currentRunMode = newMode;
|
|
843
|
+
session.sendSessionEvent({ type: "switch", mode: newMode });
|
|
844
|
+
session.updateAgentState((currentState) => ({
|
|
845
|
+
...currentState,
|
|
846
|
+
controlledByUser: newMode === "local"
|
|
847
|
+
}));
|
|
848
|
+
},
|
|
849
|
+
onSessionReady: (sessionInstance) => {
|
|
850
|
+
currentSession = sessionInstance;
|
|
851
|
+
sessionInstance.addSessionFoundCallback?.(announceSessionToScanner);
|
|
852
|
+
},
|
|
853
|
+
// B-515: lease for a warm Claude process, taken once by the first
|
|
854
|
+
// remote launch (null when not eligible).
|
|
855
|
+
claudePrewarm: createPrewarm,
|
|
856
|
+
// B-355: remember exactly what the SDK is about to write to the Claude
|
|
857
|
+
// transcript, so the JSONL scanner below recognises it as ours.
|
|
858
|
+
onPromptFinalized: recordAppPrompt,
|
|
859
|
+
onAbort: resetCurrentModeDefaults,
|
|
860
|
+
onPermissionModeChange: (mode) => {
|
|
861
|
+
lastExplicitModeSwitchAt = Date.now();
|
|
862
|
+
publishPermissionMode(mode);
|
|
863
|
+
logger.logger.debug(`[loop] Permission mode updated from session RPC / approval to: ${mode}`);
|
|
864
|
+
},
|
|
865
|
+
onEffectivePermissionMode: (mode) => publishEffectivePermissionMode(mode),
|
|
866
|
+
mcpServers: {
|
|
867
|
+
"happy": {
|
|
868
|
+
type: "http",
|
|
869
|
+
url: happyServer.url
|
|
870
|
+
}
|
|
871
|
+
},
|
|
872
|
+
session,
|
|
873
|
+
claudeEnvVars: options.claudeEnvVars,
|
|
874
|
+
claudeArgs: options.claudeArgs,
|
|
875
|
+
sandboxConfig,
|
|
876
|
+
hookSettingsPath,
|
|
877
|
+
jsRuntime: options.jsRuntime
|
|
878
|
+
});
|
|
879
|
+
currentSession?.cleanup();
|
|
880
|
+
cleanupPrewarm();
|
|
881
|
+
session.sendSessionDeath();
|
|
882
|
+
logger.logger.debug("Waiting for socket to flush...");
|
|
883
|
+
await session.flush();
|
|
884
|
+
logger.logger.debug("Closing session...");
|
|
885
|
+
await session.close();
|
|
886
|
+
happyServer.stop();
|
|
887
|
+
logger.logger.debug("Stopped Happy MCP server");
|
|
888
|
+
hookServer.stop();
|
|
889
|
+
cleanupHookSettingsFile(hookSettingsPath);
|
|
890
|
+
logger.logger.debug("Stopped Hook server and cleaned up settings file");
|
|
891
|
+
process.exit(exitCode);
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
exports.runClaude = runClaude;
|