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,380 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var promises = require('node:fs/promises');
|
|
4
|
+
var os = require('node:os');
|
|
5
|
+
var z = require('zod');
|
|
6
|
+
var logger = require('./logger-Bc8-u1w0.cjs');
|
|
7
|
+
var persistence = require('./persistence-CBNMzSa8.cjs');
|
|
8
|
+
var controlClient = require('./controlClient-fEHE85bH.cjs');
|
|
9
|
+
var sessionOps = require('./sessionOps-DJdExmBy.cjs');
|
|
10
|
+
var remoteSessionClient = require('./remoteSessionClient-BNx78NLb.cjs');
|
|
11
|
+
var ids = require('./ids-B6ZC0HGt.cjs');
|
|
12
|
+
var terminals = require('./terminals-B_bfC9c2.cjs');
|
|
13
|
+
var bootstrap = require('./bootstrap-CkgIq8N3.cjs');
|
|
14
|
+
var path = require('node:path');
|
|
15
|
+
|
|
16
|
+
const PERSONAL_MEMORY_SOFT_LIMIT_CHARS = 2e3;
|
|
17
|
+
function headingTitle(line) {
|
|
18
|
+
const m = /^##(?!#)\s*(.*?)\s*$/.exec(line);
|
|
19
|
+
return m ? m[1] : null;
|
|
20
|
+
}
|
|
21
|
+
function applyMemorySectionUpdate(doc, section, content) {
|
|
22
|
+
const wanted = section.trim();
|
|
23
|
+
const normalizedContent = content.replace(/\s+$/, "");
|
|
24
|
+
const sectionBlock = `## ${wanted}
|
|
25
|
+
|
|
26
|
+
${normalizedContent}
|
|
27
|
+
`;
|
|
28
|
+
const lines = doc.split("\n");
|
|
29
|
+
let start = -1;
|
|
30
|
+
for (let i = 0; i < lines.length; i++) {
|
|
31
|
+
if (headingTitle(lines[i]) === wanted) {
|
|
32
|
+
start = i;
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (start === -1) {
|
|
37
|
+
const base = doc.replace(/\s+$/, "");
|
|
38
|
+
const doc22 = base.length > 0 ? `${base}
|
|
39
|
+
|
|
40
|
+
${sectionBlock}` : sectionBlock;
|
|
41
|
+
return { doc: doc22, replaced: false };
|
|
42
|
+
}
|
|
43
|
+
let end = lines.length;
|
|
44
|
+
for (let i = start + 1; i < lines.length; i++) {
|
|
45
|
+
if (headingTitle(lines[i]) !== null) {
|
|
46
|
+
end = i;
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const before = lines.slice(0, start).join("\n");
|
|
51
|
+
const after = lines.slice(end).join("\n");
|
|
52
|
+
let doc2 = before.length > 0 ? `${before.replace(/\s+$/, "")}
|
|
53
|
+
|
|
54
|
+
` : "";
|
|
55
|
+
doc2 += sectionBlock;
|
|
56
|
+
if (after.trim().length > 0) {
|
|
57
|
+
doc2 += `
|
|
58
|
+
${after.replace(/\s+$/, "")}
|
|
59
|
+
`;
|
|
60
|
+
}
|
|
61
|
+
return { doc: doc2, replaced: true };
|
|
62
|
+
}
|
|
63
|
+
function journalPathForDate(home, date) {
|
|
64
|
+
const y = date.getFullYear();
|
|
65
|
+
const m = String(date.getMonth() + 1).padStart(2, "0");
|
|
66
|
+
const d = String(date.getDate()).padStart(2, "0");
|
|
67
|
+
return `${home}/memory/journal/${y}-${m}-${d}.md`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function normalizeSpawnDirectory(input, homeDir) {
|
|
71
|
+
const trimmed = typeof input === "string" ? input.trim() : "";
|
|
72
|
+
if (trimmed.length === 0) {
|
|
73
|
+
return { ok: false, error: "directory is required" };
|
|
74
|
+
}
|
|
75
|
+
let expanded = trimmed;
|
|
76
|
+
if (trimmed === "~") {
|
|
77
|
+
expanded = homeDir;
|
|
78
|
+
} else if (trimmed.startsWith("~/")) {
|
|
79
|
+
expanded = path.join(homeDir, trimmed.slice(2));
|
|
80
|
+
} else if (trimmed.startsWith("~")) {
|
|
81
|
+
return { ok: false, error: `Unsupported "~user" path: ${trimmed}. Use an absolute path.` };
|
|
82
|
+
}
|
|
83
|
+
if (!path.isAbsolute(expanded)) {
|
|
84
|
+
return { ok: false, error: `directory must be an absolute path (got "${trimmed}")` };
|
|
85
|
+
}
|
|
86
|
+
return { ok: true, directory: path.normalize(expanded) };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const ASSISTANT_SESSION_TOOL_NAMES = [
|
|
90
|
+
"sessions_list",
|
|
91
|
+
"session_read",
|
|
92
|
+
"session_send",
|
|
93
|
+
"session_spawn",
|
|
94
|
+
"session_kill",
|
|
95
|
+
"session_archive"
|
|
96
|
+
];
|
|
97
|
+
const ASSISTANT_TOOL_NAMES = [
|
|
98
|
+
...ASSISTANT_SESSION_TOOL_NAMES,
|
|
99
|
+
"terminals_list",
|
|
100
|
+
"terminal_read",
|
|
101
|
+
"terminal_send",
|
|
102
|
+
"memory_update",
|
|
103
|
+
"journal_append"
|
|
104
|
+
];
|
|
105
|
+
const MCP_CLIENT_TAG = "assistant-mcp";
|
|
106
|
+
function ok(text) {
|
|
107
|
+
return { content: [{ type: "text", text }], isError: false };
|
|
108
|
+
}
|
|
109
|
+
function fail(text) {
|
|
110
|
+
return { content: [{ type: "text", text }], isError: true };
|
|
111
|
+
}
|
|
112
|
+
function describeSession(id, persisted, extra) {
|
|
113
|
+
const meta = persisted?.metadata;
|
|
114
|
+
const parts = [id];
|
|
115
|
+
if (extra?.live !== void 0) parts.push(extra.live ? "[running]" : "[not running]");
|
|
116
|
+
if (meta?.summary?.text) parts.push(`title="${meta.summary.text}"`);
|
|
117
|
+
if (meta?.path) parts.push(`cwd=${meta.path}`);
|
|
118
|
+
if (meta?.flavor) parts.push(`agent=${meta.flavor}`);
|
|
119
|
+
if (meta?.tags?.length) parts.push(`tags=${meta.tags.join(",")}`);
|
|
120
|
+
if (meta?.variant === "assistant") parts.push("(assistant)");
|
|
121
|
+
if (extra?.pid) parts.push(`pid=${extra.pid}`);
|
|
122
|
+
parts.push(sessionOps.sessionWebUrl(id));
|
|
123
|
+
return parts.join(" ");
|
|
124
|
+
}
|
|
125
|
+
function describeSummary(summary) {
|
|
126
|
+
const parts = [summary.id, summary.live ? "[running]" : "[not running]"];
|
|
127
|
+
if (summary.title) parts.push(`title="${summary.title}"`);
|
|
128
|
+
if (summary.cwd) parts.push(`cwd=${summary.cwd}`);
|
|
129
|
+
if (summary.flavor) parts.push(`agent=${summary.flavor}`);
|
|
130
|
+
if (summary.tags?.length) parts.push(`tags=${summary.tags.join(",")}`);
|
|
131
|
+
if (summary.variant === "assistant") parts.push("(assistant)");
|
|
132
|
+
if (summary.pid !== void 0) parts.push(`pid=${summary.pid}`);
|
|
133
|
+
parts.push(summary.url);
|
|
134
|
+
return parts.join(" ");
|
|
135
|
+
}
|
|
136
|
+
function registerAssistantTools(mcp) {
|
|
137
|
+
registerAssistantSessionTools(mcp);
|
|
138
|
+
registerAssistantMachineTools(mcp);
|
|
139
|
+
}
|
|
140
|
+
function registerAssistantSessionTools(mcp) {
|
|
141
|
+
mcp.registerTool("sessions_list", {
|
|
142
|
+
description: "List Claude Code sessions on this machine: sessions currently tracked by the local daemon (running) plus recently seen ones. Returns id, title, working directory, agent flavor and web URL for each.",
|
|
143
|
+
title: "List Sessions",
|
|
144
|
+
inputSchema: {}
|
|
145
|
+
}, async () => {
|
|
146
|
+
try {
|
|
147
|
+
const sessions = await sessionOps.listSessions();
|
|
148
|
+
if (sessions.length === 0) return ok("No sessions found on this machine.");
|
|
149
|
+
return ok(`${sessions.length} session(s):
|
|
150
|
+
${sessions.map(describeSummary).join("\n")}`);
|
|
151
|
+
} catch (error) {
|
|
152
|
+
return fail(`Failed to list sessions: ${error instanceof Error ? error.message : String(error)}`);
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
mcp.registerTool("session_read", {
|
|
156
|
+
description: "Read the latest messages of a session as a compact role-tagged transcript (user / assistant / tool summaries; large payloads truncated). Sessions spawned by this machine are read directly; a session spawned by another machine of this account is read through that machine's daemon (it must be online).",
|
|
157
|
+
title: "Read Session Transcript",
|
|
158
|
+
inputSchema: {
|
|
159
|
+
sessionId: z.z.string().describe("The Happy session id to read"),
|
|
160
|
+
limit: z.z.number().optional().describe("How many recent messages to fetch (default 20, max 100)"),
|
|
161
|
+
machineId: z.z.string().optional().describe("Machine id the session runs on, when known (skips the lookup across the account's machines)")
|
|
162
|
+
}
|
|
163
|
+
}, async (args) => {
|
|
164
|
+
if (!ids.isValidSessionId(args.sessionId)) return fail("Invalid sessionId");
|
|
165
|
+
try {
|
|
166
|
+
const local = persistence.readPersistedSessions()[args.sessionId];
|
|
167
|
+
if (local && !args.machineId) {
|
|
168
|
+
const result2 = await sessionOps.readSessionTranscript(args.sessionId, args.limit ?? 20);
|
|
169
|
+
const body2 = result2.transcript.length > 0 ? result2.transcript : "(no readable conversation content in this range)";
|
|
170
|
+
return ok(`${describeSummary(result2.summary)}
|
|
171
|
+
--- last ${result2.messageCount} message(s) ---
|
|
172
|
+
${body2}`);
|
|
173
|
+
}
|
|
174
|
+
const result = await remoteSessionClient.readRemoteTranscript(args.sessionId, { limit: args.limit ?? 20, ...args.machineId ? { machineId: args.machineId } : {} });
|
|
175
|
+
const body = result.transcript.length > 0 ? result.transcript : "(no readable conversation content in this range)";
|
|
176
|
+
return ok(`${describeSummary(result.summary)} machine=${result.host} (${result.machineId})
|
|
177
|
+
--- last ${result.messageCount} message(s)${result.truncated ? ", truncated to fit" : ""} ---
|
|
178
|
+
${body}`);
|
|
179
|
+
} catch (error) {
|
|
180
|
+
return fail(`Failed to read session: ${error instanceof Error ? error.message : String(error)}`);
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
mcp.registerTool("session_send", {
|
|
184
|
+
description: "Send a user message into an existing session. Only delivers when a live wrapper will read it: an archived / offline session is refused (the server would store the message unread) unless resume:true brings it back on its machine first. A session spawned by another machine of this account is delivered through that machine's daemon (it must be online). Returns immediately after delivery \u2014 it does NOT wait for the session to respond; use session_read later to check progress.",
|
|
185
|
+
title: "Send Message to Session",
|
|
186
|
+
inputSchema: {
|
|
187
|
+
sessionId: z.z.string().describe("The Happy session id to message"),
|
|
188
|
+
text: z.z.string().describe("The message text to send"),
|
|
189
|
+
resume: z.z.boolean().optional().describe("If the session is archived or offline, resume it on its machine (like the web Restore button) and wait until it is live before sending"),
|
|
190
|
+
machineId: z.z.string().optional().describe("Machine id the session runs on, when known (skips the lookup across the account's machines)")
|
|
191
|
+
}
|
|
192
|
+
}, async (args) => {
|
|
193
|
+
if (!ids.isValidSessionId(args.sessionId)) return fail("Invalid sessionId");
|
|
194
|
+
if (typeof args.text !== "string" || args.text.trim().length === 0) return fail("text must be non-empty");
|
|
195
|
+
try {
|
|
196
|
+
if (!persistence.readPersistedSessions()[args.sessionId] || args.machineId) {
|
|
197
|
+
const remote = await remoteSessionClient.sendRemoteMessage(args.sessionId, args.text, { resume: args.resume === true, sentFrom: "assistant", ...args.machineId ? { machineId: args.machineId } : {} });
|
|
198
|
+
if (!remote.delivered) {
|
|
199
|
+
const detail = remote.error ?? `session is ${remote.status}`;
|
|
200
|
+
return fail(remote.stored ? `Not delivered (via ${remote.machine.host}): ${detail}` : `Session ${args.sessionId} on ${remote.machine.host} is ${remote.status}: ${detail}`);
|
|
201
|
+
}
|
|
202
|
+
return ok(`Message delivered${remote.resumed ? ` (session resumed on ${remote.machine.host})` : ""} to ${args.sessionId} on machine ${remote.machine.host} (${remote.machine.id}) ${sessionOps.sessionWebUrl(args.sessionId)}`);
|
|
203
|
+
}
|
|
204
|
+
const persisted = await sessionOps.waitForSessionKey(args.sessionId, 0);
|
|
205
|
+
const result = await remoteSessionClient.deliverToSession(args.sessionId, persisted, args.text, MCP_CLIENT_TAG, {
|
|
206
|
+
resume: args.resume === true,
|
|
207
|
+
resumeHint: "Pass resume:true to bring it back on this machine, or spawn a new session."
|
|
208
|
+
});
|
|
209
|
+
if (!result.delivered) {
|
|
210
|
+
const detail = result.error ?? `session is ${result.status}`;
|
|
211
|
+
return fail(result.stored ? `Not delivered: ${detail}` : `Session ${args.sessionId} is ${result.status}: ${detail}`);
|
|
212
|
+
}
|
|
213
|
+
return ok(`Message delivered${result.resumed ? " (session resumed on this machine)" : ""} to ${describeSession(args.sessionId, persisted, { live: true })}`);
|
|
214
|
+
} catch (error) {
|
|
215
|
+
if (error instanceof remoteSessionClient.RemoteSessionOpsError) return fail(`Failed to send (${error.code}): ${error.message}`);
|
|
216
|
+
return fail(`Failed to send: ${error instanceof Error ? error.message : String(error)}`);
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
mcp.registerTool("session_spawn", {
|
|
220
|
+
description: "Spawn a NEW Claude Code session in a directory via the local daemon, optionally sending its first prompt. Returns the session id and web URL immediately after dispatch \u2014 it does NOT wait for the task to run or finish.",
|
|
221
|
+
title: "Spawn Session",
|
|
222
|
+
inputSchema: {
|
|
223
|
+
directory: z.z.string().describe("Absolute working directory for the new session (a leading ~ is expanded)"),
|
|
224
|
+
prompt: z.z.string().optional().describe("Optional first message to send once the session is up")
|
|
225
|
+
}
|
|
226
|
+
}, async (args) => {
|
|
227
|
+
const normalized = normalizeSpawnDirectory(typeof args.directory === "string" ? args.directory : "", os.homedir());
|
|
228
|
+
if (!normalized.ok) return fail(normalized.error);
|
|
229
|
+
const directory = normalized.directory;
|
|
230
|
+
try {
|
|
231
|
+
const result = await controlClient.spawnDaemonSession(directory, void 0, { spawnedBy: "assistant" });
|
|
232
|
+
if (result?.error || !result?.sessionId) {
|
|
233
|
+
return fail(`Failed to spawn session: ${result?.error ?? "daemon returned no session id"}`);
|
|
234
|
+
}
|
|
235
|
+
const sessionId = result.sessionId;
|
|
236
|
+
const url = sessionOps.sessionWebUrl(sessionId);
|
|
237
|
+
if (args.prompt && args.prompt.trim().length > 0) {
|
|
238
|
+
try {
|
|
239
|
+
const persisted = await sessionOps.waitForSessionKey(sessionId, 15e3);
|
|
240
|
+
await sessionOps.sendUserMessage(sessionId, persisted, args.prompt, MCP_CLIENT_TAG);
|
|
241
|
+
return ok(`Spawned session ${sessionId} in ${directory} and sent the first prompt. It is now working in the background \u2014 check on it later with session_read. ${url}`);
|
|
242
|
+
} catch (error) {
|
|
243
|
+
return ok(`Spawned session ${sessionId} in ${directory}, but sending the first prompt failed (${error instanceof Error ? error.message : String(error)}). Use session_send to retry. ${url}`);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return ok(`Spawned idle session ${sessionId} in ${directory}. ${url}`);
|
|
247
|
+
} catch (error) {
|
|
248
|
+
return fail(`Failed to spawn session: ${error instanceof Error ? error.message : String(error)}`);
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
mcp.registerTool("session_kill", {
|
|
252
|
+
description: "Stop a running session process on this machine (SIGTERM via the local daemon). Destructive \u2014 confirm with the user (repeat the session title/cwd) before calling.",
|
|
253
|
+
title: "Kill Session",
|
|
254
|
+
inputSchema: {
|
|
255
|
+
sessionId: z.z.string().describe("The Happy session id to stop")
|
|
256
|
+
}
|
|
257
|
+
}, async (args) => {
|
|
258
|
+
if (!ids.isValidSessionId(args.sessionId)) return fail("Invalid sessionId");
|
|
259
|
+
try {
|
|
260
|
+
const persisted = persistence.readPersistedSessions()[args.sessionId];
|
|
261
|
+
const success = await sessionOps.stopSession(args.sessionId);
|
|
262
|
+
if (!success) return fail(`Session ${args.sessionId} was not found among the daemon's running sessions.`);
|
|
263
|
+
return ok(`Stopped ${describeSession(args.sessionId, persisted)}`);
|
|
264
|
+
} catch (error) {
|
|
265
|
+
return fail(`Failed to stop session: ${error instanceof Error ? error.message : String(error)}`);
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
mcp.registerTool("session_archive", {
|
|
269
|
+
description: "Mark a session inactive on the server (it disappears from the active list but stays resumable). Confirm with the user before calling.",
|
|
270
|
+
title: "Archive Session",
|
|
271
|
+
inputSchema: {
|
|
272
|
+
sessionId: z.z.string().describe("The Happy session id to archive")
|
|
273
|
+
}
|
|
274
|
+
}, async (args) => {
|
|
275
|
+
if (!ids.isValidSessionId(args.sessionId)) return fail("Invalid sessionId");
|
|
276
|
+
try {
|
|
277
|
+
await sessionOps.archiveSession(args.sessionId);
|
|
278
|
+
const persisted = persistence.readPersistedSessions()[args.sessionId];
|
|
279
|
+
return ok(`Archived ${describeSession(args.sessionId, persisted)}`);
|
|
280
|
+
} catch (error) {
|
|
281
|
+
return fail(`Failed to archive session: ${error instanceof Error ? error.message : String(error)}`);
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
function registerAssistantMachineTools(mcp) {
|
|
286
|
+
mcp.registerTool("terminals_list", {
|
|
287
|
+
description: "List the web terminals (tmux sessions) on this machine with their id, title and working directory.",
|
|
288
|
+
title: "List Terminals",
|
|
289
|
+
inputSchema: {}
|
|
290
|
+
}, async () => {
|
|
291
|
+
const r = terminals.listVhTerminals();
|
|
292
|
+
if (!r.ok) return fail(`Failed to list terminals: ${r.error}`);
|
|
293
|
+
if (r.terminals.length === 0) return ok("No web terminals are open on this machine.");
|
|
294
|
+
const lines = r.terminals.map((t) => {
|
|
295
|
+
const parts = [t.id];
|
|
296
|
+
if (t.title) parts.push(`title="${t.title}"`);
|
|
297
|
+
if (t.cwd) parts.push(`cwd=${t.cwd}`);
|
|
298
|
+
return parts.join(" ");
|
|
299
|
+
});
|
|
300
|
+
return ok(`${r.terminals.length} terminal(s):
|
|
301
|
+
${lines.join("\n")}`);
|
|
302
|
+
});
|
|
303
|
+
mcp.registerTool("terminal_read", {
|
|
304
|
+
description: "Read the last N lines of a web terminal's screen/history (tmux capture-pane).",
|
|
305
|
+
title: "Read Terminal",
|
|
306
|
+
inputSchema: {
|
|
307
|
+
terminalId: z.z.string().describe("The terminal id (from terminals_list)"),
|
|
308
|
+
lines: z.z.number().optional().describe("How many history lines to read (default 80, max 2000)")
|
|
309
|
+
}
|
|
310
|
+
}, async (args) => {
|
|
311
|
+
if (!ids.isValidTerminalId(args.terminalId)) return fail("Invalid terminalId");
|
|
312
|
+
const r = terminals.readVhTerminal(args.terminalId, args.lines ?? 80);
|
|
313
|
+
if (!r.ok) return fail(`Failed to read terminal ${args.terminalId}: ${r.error}`);
|
|
314
|
+
return ok(`terminal ${args.terminalId} \u2014 last output:
|
|
315
|
+
${r.text}`);
|
|
316
|
+
});
|
|
317
|
+
mcp.registerTool("terminal_send", {
|
|
318
|
+
description: "Type text into a web terminal via tmux bracketed paste. By default the text is ONLY pasted (nothing executes); set submit=true to also press Enter \u2014 that actually runs it, so confirm with the user first.",
|
|
319
|
+
title: "Send to Terminal",
|
|
320
|
+
inputSchema: {
|
|
321
|
+
terminalId: z.z.string().describe("The terminal id (from terminals_list)"),
|
|
322
|
+
text: z.z.string().describe("The text to paste into the terminal"),
|
|
323
|
+
submit: z.z.boolean().optional().describe("Also press Enter after pasting (default false)")
|
|
324
|
+
}
|
|
325
|
+
}, async (args) => {
|
|
326
|
+
if (!ids.isValidTerminalId(args.terminalId)) return fail("Invalid terminalId");
|
|
327
|
+
if (typeof args.text !== "string" || args.text.length === 0) return fail("text must be non-empty");
|
|
328
|
+
const submit = args.submit === true;
|
|
329
|
+
const r = terminals.sendToVhTerminal(args.terminalId, args.text, submit);
|
|
330
|
+
if (!r.ok) return fail(`Failed to send to terminal ${args.terminalId}: ${r.error}`);
|
|
331
|
+
return ok(submit ? `Pasted and submitted (Enter sent) in terminal ${args.terminalId}.` : `Pasted into terminal ${args.terminalId} WITHOUT pressing Enter \u2014 the user (or a follow-up terminal_send with submit=true) decides whether it runs.`);
|
|
332
|
+
});
|
|
333
|
+
mcp.registerTool("memory_update", {
|
|
334
|
+
description: `Update one "## <section>" block of your personal memory file (memory/personal.md): replaces the section body if the heading exists, otherwise appends a new section. Keep the whole file under ~${PERSONAL_MEMORY_SOFT_LIMIT_CHARS} characters; most conversations need NO memory write.`,
|
|
335
|
+
title: "Update Personal Memory",
|
|
336
|
+
inputSchema: {
|
|
337
|
+
section: z.z.string().describe('The level-2 heading title (without "## ") to replace or create'),
|
|
338
|
+
content: z.z.string().describe("The new body for that section (replaces the old body entirely)")
|
|
339
|
+
}
|
|
340
|
+
}, async (args) => {
|
|
341
|
+
if (typeof args.section !== "string" || args.section.trim().length === 0) return fail("section must be non-empty");
|
|
342
|
+
try {
|
|
343
|
+
await bootstrap.bootstrapAssistantHome();
|
|
344
|
+
const path = bootstrap.assistantPersonalMemoryPath();
|
|
345
|
+
const doc = await promises.readFile(path, "utf8");
|
|
346
|
+
const { doc: updated, replaced } = applyMemorySectionUpdate(doc, args.section, args.content);
|
|
347
|
+
await promises.writeFile(path, updated, "utf8");
|
|
348
|
+
logger.logger.debug(`[assistant] memory_update section="${args.section}" replaced=${replaced} size=${updated.length}`);
|
|
349
|
+
const sizeNote = updated.length > PERSONAL_MEMORY_SOFT_LIMIT_CHARS ? ` WARNING: the file is now ${updated.length} chars (soft limit ~${PERSONAL_MEMORY_SOFT_LIMIT_CHARS}) \u2014 condense existing entries instead of adding more.` : ` File is now ${updated.length} chars.`;
|
|
350
|
+
return ok(`${replaced ? "Replaced" : "Appended"} section "${args.section.trim()}" in ${path}.${sizeNote}`);
|
|
351
|
+
} catch (error) {
|
|
352
|
+
return fail(`Failed to update memory: ${error instanceof Error ? error.message : String(error)}`);
|
|
353
|
+
}
|
|
354
|
+
});
|
|
355
|
+
mcp.registerTool("journal_append", {
|
|
356
|
+
description: "Append a timestamped note to today's work journal (memory/journal/YYYY-MM-DD.md). Use before compaction to preserve important progress; append-only.",
|
|
357
|
+
title: "Append Work Journal",
|
|
358
|
+
inputSchema: {
|
|
359
|
+
content: z.z.string().describe("The note to append (plain text/markdown)")
|
|
360
|
+
}
|
|
361
|
+
}, async (args) => {
|
|
362
|
+
if (typeof args.content !== "string" || args.content.trim().length === 0) return fail("content must be non-empty");
|
|
363
|
+
try {
|
|
364
|
+
const { home } = await bootstrap.bootstrapAssistantHome();
|
|
365
|
+
const path = journalPathForDate(home, /* @__PURE__ */ new Date());
|
|
366
|
+
const stamp = (/* @__PURE__ */ new Date()).toTimeString().slice(0, 5);
|
|
367
|
+
await promises.appendFile(path, `
|
|
368
|
+
- [${stamp}] ${args.content.trim()}
|
|
369
|
+
`, "utf8");
|
|
370
|
+
logger.logger.debug(`[assistant] journal_append -> ${path}`);
|
|
371
|
+
return ok(`Appended to ${path}.`);
|
|
372
|
+
} catch (error) {
|
|
373
|
+
return fail(`Failed to append journal: ${error instanceof Error ? error.message : String(error)}`);
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
exports.ASSISTANT_TOOL_NAMES = ASSISTANT_TOOL_NAMES;
|
|
379
|
+
exports.registerAssistantSessionTools = registerAssistantSessionTools;
|
|
380
|
+
exports.registerAssistantTools = registerAssistantTools;
|