very-happy-cli 0.2.159 → 0.2.160
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-DUYFlyfN.cjs → AcpBackend-Bm2rdke8.cjs} +78 -76
- package/dist/{AcpBackend-VsfkZiVF.mjs → AcpBackend-mzv8Dhba.mjs} +4 -2
- package/dist/{AcpSessionManager-Bdv8LWXh.cjs → AcpSessionManager-BAY1Pggx.cjs} +9 -9
- package/dist/{AcpSessionManager-rYtVb0kN.mjs → AcpSessionManager-CoMWFVw3.mjs} +1 -1
- package/dist/BasePermissionHandler-CFHAxumV.cjs +157 -0
- package/dist/BasePermissionHandler-m3WVholL.mjs +155 -0
- package/dist/BaseReasoningProcessor-BFa9_rZA.cjs +200 -0
- package/dist/BaseReasoningProcessor-CYrKBXGm.mjs +198 -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-CHh4lmjM.cjs → agentHome-CboCeKWq.cjs} +14 -28
- package/dist/{agentHome-CvsJyeQB.mjs → agentHome-Dj1C43aJ.mjs} +8 -22
- package/dist/api-CLMUr-SX.mjs +3339 -0
- package/dist/api-CgundNNX.cjs +3346 -0
- package/dist/{types-Dh-s4Lqs.mjs → apiMachine-B9dLW_p5.mjs} +9116 -16205
- package/dist/{types-BbvK1Tr_.cjs → apiMachine-Bn8482VM.cjs} +9104 -16247
- package/dist/assistantTools-BTqPnlFl.cjs +380 -0
- package/dist/assistantTools-D6tuc6o4.mjs +376 -0
- package/dist/attachmentContent-DpiTJZb4.cjs +88 -0
- package/dist/attachmentContent-_kxRznNf.mjs +82 -0
- package/dist/attachmentLimits-CzVrqgLR.cjs +354 -0
- package/dist/attachmentLimits-yba1uHFO.mjs +348 -0
- package/dist/auth-BDOVh5h5.cjs +220 -0
- package/dist/auth-Cwutmdsa.mjs +225 -0
- package/dist/auth-PYHZQNPp.mjs +218 -0
- package/dist/auth-ba4WMYrN.cjs +227 -0
- package/dist/authRelay-BdYkC1Po.cjs +156 -0
- package/dist/authRelay-DSAvg1uv.mjs +143 -0
- package/dist/{auto-DKmEls8Z.cjs → auto-BOX_fgmR.cjs} +70 -79
- package/dist/{auto-nb8GhRRy.mjs → auto-BXu14yP_.mjs} +27 -36
- package/dist/backgroundTaskActivity-8PdPxrvy.mjs +149 -0
- package/dist/backgroundTaskActivity-CBvUdj39.cjs +157 -0
- package/dist/bootstrap-B5G1TijC.cjs +116 -0
- package/dist/bootstrap-hxXPCFMx.mjs +112 -0
- package/dist/browser-Y6Bl8L_O.cjs +22 -0
- package/dist/browser-jh2P0UVZ.mjs +20 -0
- package/dist/claudeCliPath-BqkaH2oR.mjs +8 -0
- package/dist/claudeCliPath-U4HHmV4r.cjs +10 -0
- package/dist/claudeLocal-BaY4fZKi.mjs +339 -0
- package/dist/claudeLocal-CRGG4Pea.cjs +343 -0
- package/dist/claudeLocalLauncher-D4T7MTqd.mjs +152 -0
- package/dist/claudeLocalLauncher-DyEbVzUN.cjs +154 -0
- package/dist/cliUpdate-DsKXPFYp.cjs +120 -0
- package/dist/cliUpdate-Y34VGe9Y.mjs +112 -0
- package/dist/client-3Ac-O4pw.cjs +104 -0
- package/dist/client-BPNbmtvO.cjs +119 -0
- package/dist/client-CeGKqqcJ.mjs +100 -0
- package/dist/client-ClR0K8zX.mjs +112 -0
- package/dist/codex/happyMcpStdioBridge.cjs +33 -14
- package/dist/codex/happyMcpStdioBridge.mjs +26 -7
- package/dist/codexAppServerClient-I3teiV-N.mjs +1088 -0
- package/dist/codexAppServerClient-q2FjnRcK.cjs +1090 -0
- package/dist/codexCommand-4zcccIoq.cjs +2088 -0
- package/dist/codexCommand-Cc9gcbdX.mjs +2086 -0
- package/dist/codexThreadFork-BH9IZPqD.cjs +350 -0
- package/dist/codexThreadFork-slzAseLx.mjs +338 -0
- package/dist/{config-ClLtM6F3.mjs → config-CTx_jb-C.mjs} +7 -40
- package/dist/{config-DLx-vW3I.cjs → config-Ci9Kpgwa.cjs} +26 -59
- package/dist/configuration-BSYpcPwE.cjs +181 -0
- package/dist/configuration-RfeNjFmd.mjs +170 -0
- package/dist/connect-BpOdzNbE.mjs +682 -0
- package/dist/connect-C2CiOUFs.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-DsDr6E2t.cjs +69 -0
- package/dist/context-H0Bcb9S_.mjs +65 -0
- package/dist/controlClient-BGbDcgWS.mjs +260 -0
- package/dist/controlClient-Bcwv3D21.cjs +279 -0
- package/dist/createSessionMetadata-BJxEnM-o.cjs +58 -0
- package/dist/createSessionMetadata-CVVkGjxV.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-C5CYTVpm.cjs +507 -0
- package/dist/deterministicJson-CzkFJWw1.mjs +502 -0
- package/dist/doctor-BPkYWSuq.mjs +75 -0
- package/dist/doctor-D2YJ-Yn2.cjs +395 -0
- package/dist/doctor-DOvByjbk.mjs +391 -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-BCwNrMyv.cjs +64 -0
- package/dist/ensureDaemonRunning-Dqa2pOjY.mjs +61 -0
- package/dist/handleResumeCommand-BvXYf5El.mjs +240 -0
- package/dist/handleResumeCommand-C2cKchpX.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-BEOMECtE.cjs → index-B6pAdvJx.cjs} +121 -91
- package/dist/index-Bu5Zi_a9.cjs +182 -0
- package/dist/index-D3kI0hDR.cjs +107 -0
- package/dist/index-DA7yINuk.cjs +994 -0
- package/dist/index-DDeHXrIX.cjs +71 -0
- package/dist/index-DKLU_fD0.mjs +158 -0
- package/dist/{index-2mCaeKyf.mjs → index-DMZqWwsP.mjs} +68 -38
- package/dist/index-DVkMGPyS.mjs +68 -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-BgoC2e_2.cjs +88 -0
- package/dist/install-CFY3tYtJ.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-BeDfxxEX.cjs +305 -0
- package/dist/lidState-CeEt442o.mjs +297 -0
- package/dist/limits-C-a7gr04.cjs +43 -0
- package/dist/limits-DIo7rEBR.mjs +36 -0
- package/dist/localHappyAgentAuth-C-HQyiv6.mjs +82 -0
- package/dist/localHappyAgentAuth-D6rFhaoc.cjs +86 -0
- package/dist/logger-6HYUEXNk.mjs +271 -0
- package/dist/logger-CS0mw3fa.cjs +275 -0
- package/dist/loginShellEnv-BkuYtExZ.cjs +182 -0
- package/dist/loginShellEnv-Dw8M7tkI.mjs +167 -0
- package/dist/machineMetadata-DyDbODYd.cjs +41 -0
- package/dist/machineMetadata-oOzBfDfs.mjs +38 -0
- package/dist/main-BkxJP6JQ.cjs +941 -0
- package/dist/main-BwNIAI1A.mjs +939 -0
- package/dist/manager-BQ4flvQZ.mjs +101 -0
- package/dist/manager-Cly8xVIR.cjs +105 -0
- package/dist/{mcp-Dy3f_K0h.mjs → mcp-B1A4I3TK.mjs} +27 -33
- package/dist/{mcp-CPYbtsYG.cjs → mcp-Hszd7qpS.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-BhaKlnxz.mjs +171 -0
- package/dist/package-BwMrPwqH.cjs +173 -0
- package/dist/path-B7zW-WwE.cjs +18 -0
- package/dist/path-D2wdaNSk.mjs +15 -0
- package/dist/peerTools-BTqw97Gb.mjs +272 -0
- package/dist/peerTools-BvKtHXj8.cjs +283 -0
- package/dist/permissionMode-Cr5JCbB7.mjs +80 -0
- package/dist/permissionMode-CrV-XuMs.cjs +87 -0
- package/dist/persistence-HGW_ZVs9.mjs +372 -0
- package/dist/persistence-yKTH3-5X.cjs +414 -0
- package/dist/piExtension.mjs +2 -1
- package/dist/piRunnerArgs-BNloJCCg.cjs +66 -0
- package/dist/piRunnerArgs-BuX1UFWy.mjs +59 -0
- package/dist/{piRuntime-kLZNpAAl.mjs → piRuntime-CSGkwCKQ.mjs} +2 -2
- package/dist/{piRuntime-NtF0YqjH.cjs → piRuntime-C_H7MJCM.cjs} +4 -4
- package/dist/{piTerminal-B0fEOV_2.mjs → piTerminal-C8a1OrNQ.mjs} +32 -35
- package/dist/{piTerminal-9J0GAKzb.cjs → piTerminal-DNCZ6yTM.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-Ccvsz7Ou.mjs +536 -0
- package/dist/registerCommonHandlers-DskirN-_.cjs +538 -0
- package/dist/registerKillSessionHandler-DZeLXWcB.cjs +361 -0
- package/dist/registerKillSessionHandler-GYvHdipX.mjs +358 -0
- package/dist/remoteSessionClient-B0LraSB9.cjs +634 -0
- package/dist/remoteSessionClient-CYnXRynE.mjs +612 -0
- package/dist/resources-BdFBQi8r.mjs +117 -0
- package/dist/resources-JDPugcDq.cjs +120 -0
- package/dist/run-CJzxoHVh.cjs +5762 -0
- package/dist/run-D3ifupU3.mjs +5739 -0
- package/dist/runClaude-Ba01qT-u.mjs +848 -0
- package/dist/runClaude-Da3RirzE.cjs +850 -0
- package/dist/runClaudeDeps-DOt79DjU.mjs +3624 -0
- package/dist/runClaudeDeps-DOzEBMls.cjs +3649 -0
- package/dist/{runGemini-DV6Ouadt.mjs → runGemini-BGeTz57m.mjs} +58 -31
- package/dist/{runGemini-F-mqx3_K.cjs → runGemini-DSr9LIxL.cjs} +202 -175
- package/dist/{runOpenClaw-Qqo9j63M.cjs → runOpenClaw-B1mTxN35.cjs} +64 -65
- package/dist/{runOpenClaw-D7MlPS5n.mjs → runOpenClaw-CogSedIc.mjs} +31 -32
- package/dist/sandbox-CG7IyDTU.cjs +187 -0
- package/dist/sandbox-DTe6d54R.mjs +180 -0
- package/dist/selfCheck-C9mVrQwf.mjs +84 -0
- package/dist/selfCheck-Dc-mNSLb.cjs +87 -0
- package/dist/{send-4qJTCAYW.cjs → send-B7J_eaG3.cjs} +16 -9
- package/dist/{send-CLhYXNgy.mjs → send-CBXJKc6O.mjs} +12 -5
- package/dist/server-CZTP_iOn.cjs +407 -0
- package/dist/server-DcVBv04l.mjs +404 -0
- package/dist/sessionLock-Bys1PNSo.cjs +205 -0
- package/dist/sessionLock-D-EG2CfG.mjs +197 -0
- package/dist/sessionModeFile-DMl3dw3r.mjs +32 -0
- package/dist/sessionModeFile-RSm0NQkH.cjs +36 -0
- package/dist/sessionOps-Dn8z85h_.cjs +783 -0
- package/dist/sessionOps-SuHW8Dfe.mjs +760 -0
- package/dist/sessionScanner-BOrGjMXL.cjs +209 -0
- package/dist/sessionScanner-CX3onqXh.mjs +206 -0
- package/dist/{sessions-CzIH2rGp.mjs → sessions-BEG_HTSi.mjs} +17 -8
- package/dist/{sessions-Bh1pyddi.cjs → sessions-D74aJbhL.cjs} +37 -28
- package/dist/setupOfflineReconnection-ByElY60m.cjs +74 -0
- package/dist/setupOfflineReconnection-DpvWTt69.mjs +72 -0
- package/dist/smallFastModel-BLNgMrwR.mjs +18 -0
- package/dist/smallFastModel-D_uxGMo3.cjs +21 -0
- package/dist/{spawn-CPIWlPOM.mjs → spawn-B7yro0b2.mjs} +26 -38
- package/dist/{spawn-CmFOSVdW.cjs → spawn-DgR0RKRs.cjs} +52 -64
- package/dist/spawnAgents-BGQiEF-w.cjs +52 -0
- package/dist/spawnAgents-DZzZ9jMI.mjs +45 -0
- package/dist/spawnHappyCLI-B2H5s4B_.mjs +64 -0
- package/dist/spawnHappyCLI-DBhwZT7O.cjs +66 -0
- package/dist/specialCommands-BSqhYg5X.mjs +52 -0
- package/dist/specialCommands-DSsTeRej.cjs +54 -0
- package/dist/startFileWatcher-B4VsLgjD.cjs +71 -0
- package/dist/startFileWatcher-DErVWZE4.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-C8gyQFMc.mjs +69 -0
- package/dist/sync-DccIKZ5_.cjs +71 -0
- package/dist/systemPrompt-BwTPWfMu.cjs +97 -0
- package/dist/systemPrompt-DX521JJz.mjs +94 -0
- package/dist/{teams-CEahj4cm.mjs → teams-C32F1hqd.mjs} +9 -40
- package/dist/{teams-CB1R9-Z9.cjs → teams-Dl8g8kTI.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-D0XyHOid.mjs +38 -0
- package/dist/time-DCuTyook.cjs +42 -0
- package/dist/titleGenerator-CQ8CoGhg.mjs +166 -0
- package/dist/titleGenerator-ChzMJsyg.cjs +172 -0
- package/dist/tmuxSocket-B8Yx20sT.mjs +19 -0
- package/dist/tmuxSocket-D-Xt6Vn-.cjs +24 -0
- package/dist/{todo-DqyRI4wP.cjs → todo-BvUOTa8G.cjs} +15 -12
- package/dist/{todo-Cknp5D4U.mjs → todo-CZ6cFrrn.mjs} +11 -8
- package/dist/tools-BZtHF_qQ.mjs +177 -0
- package/dist/tools-Be3AoAwu.cjs +63 -0
- package/dist/tools-DQj0AeLk.cjs +180 -0
- package/dist/tools-DkluDcG4.mjs +60 -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-BkLzaJ6I.mjs +47 -0
- package/dist/uninstall-x-TUtf5H.cjs +49 -0
- package/package.json +7 -7
- 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,235 @@
|
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
import { spawnSync } from 'node:child_process';
|
|
3
|
+
import { p as piSessionNameFromTitle } from './terminalTitleSuggest-CeKsX_V6.mjs';
|
|
4
|
+
import { s as shellescape, t as tmuxArgs, a as scrubTmuxClientEnv } from './tmuxSocket-B8Yx20sT.mjs';
|
|
5
|
+
|
|
6
|
+
const USER_SESSION_FIELD_SEP = "<~|~>";
|
|
7
|
+
const USER_SESSIONS_FORMAT = [
|
|
8
|
+
"#{session_id}",
|
|
9
|
+
"#{session_name}",
|
|
10
|
+
"#{session_windows}",
|
|
11
|
+
"#{session_attached}",
|
|
12
|
+
"#{session_activity}",
|
|
13
|
+
"#{session_created}",
|
|
14
|
+
"#{pane_current_command}",
|
|
15
|
+
"#{pane_current_path}"
|
|
16
|
+
].join(USER_SESSION_FIELD_SEP);
|
|
17
|
+
const TMUX_SESSION_ID_RE = /^\$\d{1,9}$/;
|
|
18
|
+
const TMUX_SESSION_NAME_MAX = 128;
|
|
19
|
+
function isSafeTmuxSessionName(name) {
|
|
20
|
+
if (typeof name !== "string") return false;
|
|
21
|
+
if (name.length === 0 || name.length > TMUX_SESSION_NAME_MAX) return false;
|
|
22
|
+
return !/[\x00-\x1f\x7f]/.test(name);
|
|
23
|
+
}
|
|
24
|
+
function isVhSessionName(name) {
|
|
25
|
+
return name.startsWith("vh-");
|
|
26
|
+
}
|
|
27
|
+
function parseUserSessionLine(line) {
|
|
28
|
+
if (!line) return void 0;
|
|
29
|
+
const parts = line.split(USER_SESSION_FIELD_SEP);
|
|
30
|
+
if (parts.length < 8) return void 0;
|
|
31
|
+
const [id, name, windows, attached, activity, created, command] = parts;
|
|
32
|
+
if (!TMUX_SESSION_ID_RE.test(id) || !isSafeTmuxSessionName(name)) return void 0;
|
|
33
|
+
const cwd = parts.slice(7).join(USER_SESSION_FIELD_SEP);
|
|
34
|
+
const num = (v) => {
|
|
35
|
+
const n = Number(v);
|
|
36
|
+
return Number.isFinite(n) ? n : void 0;
|
|
37
|
+
};
|
|
38
|
+
return {
|
|
39
|
+
id,
|
|
40
|
+
name,
|
|
41
|
+
windows: Math.max(0, Math.floor(num(windows) ?? 0)),
|
|
42
|
+
attached: (num(attached) ?? 0) > 0,
|
|
43
|
+
activityAt: activity ? (num(activity) ?? 0) * 1e3 || void 0 : void 0,
|
|
44
|
+
createdAt: created ? (num(created) ?? 0) * 1e3 || void 0 : void 0,
|
|
45
|
+
command: command.trim() || void 0,
|
|
46
|
+
cwd: cwd || void 0
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
const USER_SESSIONS_MAX = 50;
|
|
50
|
+
function parseUserSessions(stdout, max = USER_SESSIONS_MAX) {
|
|
51
|
+
const out = [];
|
|
52
|
+
for (const line of stdout.split("\n")) {
|
|
53
|
+
const s = parseUserSessionLine(line);
|
|
54
|
+
if (!s || isVhSessionName(s.name)) continue;
|
|
55
|
+
out.push(s);
|
|
56
|
+
}
|
|
57
|
+
out.sort((a, b) => (b.activityAt ?? 0) - (a.activityAt ?? 0));
|
|
58
|
+
return out.slice(0, max);
|
|
59
|
+
}
|
|
60
|
+
function attachStartupCommand(sessionId, socket) {
|
|
61
|
+
if (!TMUX_SESSION_ID_RE.test(sessionId)) throw new Error(`not a tmux session id: ${sessionId}`);
|
|
62
|
+
const sock = socket ? ` -S ${shellescape(socket)}` : "";
|
|
63
|
+
return ` TMUX= tmux${sock} attach-session -t ${shellescape(sessionId)}`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const SHELL_COMMANDS = /* @__PURE__ */ new Set(["zsh", "bash", "fish", "sh", "dash", "ksh", "tcsh", "csh"]);
|
|
67
|
+
const TITLE_MAX_CHARS = 60;
|
|
68
|
+
const JUNK_TITLES = /* @__PURE__ */ new Set(["tmux", "claude", "node", ...SHELL_COMMANDS]);
|
|
69
|
+
function deriveAutoTitle(paneTitle, hostname, cwd) {
|
|
70
|
+
if (typeof paneTitle !== "string") return void 0;
|
|
71
|
+
const t = paneTitle.replace(/^[^\p{L}\p{N}]+/u, "").replace(/\s+/g, " ").trim();
|
|
72
|
+
if (!t) return void 0;
|
|
73
|
+
const piName = piSessionNameFromTitle(t, cwd);
|
|
74
|
+
if (piName) {
|
|
75
|
+
const chars2 = Array.from(piName);
|
|
76
|
+
return chars2.length > TITLE_MAX_CHARS ? chars2.slice(0, TITLE_MAX_CHARS).join("") : piName;
|
|
77
|
+
}
|
|
78
|
+
const lower = t.toLowerCase();
|
|
79
|
+
const host = (hostname || "").toLowerCase();
|
|
80
|
+
const shortHost = host.split(".")[0];
|
|
81
|
+
if (host && (lower === host || lower === shortHost)) return void 0;
|
|
82
|
+
if (JUNK_TITLES.has(lower)) return void 0;
|
|
83
|
+
const chars = Array.from(t);
|
|
84
|
+
return chars.length > TITLE_MAX_CHARS ? chars.slice(0, TITLE_MAX_CHARS).join("") : t;
|
|
85
|
+
}
|
|
86
|
+
const LIST_FIELD_SEP = "<~|~>";
|
|
87
|
+
const LIST_SESSIONS_FORMAT = [
|
|
88
|
+
"#{session_name}",
|
|
89
|
+
"#{session_created}",
|
|
90
|
+
"#{session_activity}",
|
|
91
|
+
"#{pane_current_path}",
|
|
92
|
+
"#{@vh_title}",
|
|
93
|
+
"#{@vh_title_manual}",
|
|
94
|
+
"#{@vh_tags}",
|
|
95
|
+
// B-273: the user tmux session this terminal was opened to attach (name),
|
|
96
|
+
// carried into close records so a restore can re-attach.
|
|
97
|
+
"#{@vh_attach}",
|
|
98
|
+
// B-287: the pane's REAL geometry, persisted with the live snapshot so a
|
|
99
|
+
// cold restore (daemon boot auto-restore, archive ↻) recreates the session
|
|
100
|
+
// at the size it last had — claude's one-shot welcome banner then never
|
|
101
|
+
// gets reflowed by the first web open (the "half-drawn logo").
|
|
102
|
+
"#{pane_width}",
|
|
103
|
+
"#{pane_height}",
|
|
104
|
+
// B-121: the control-mode client has no `pty.process`, so the agent-state
|
|
105
|
+
// fast path lost its live `#{pane_current_command}` equivalent. Carry it in
|
|
106
|
+
// the ONE list-sessions call the tracker already makes — the value goes
|
|
107
|
+
// from "live" to "≤ LIST_TRACK_INTERVAL_MS old", which classifyPane
|
|
108
|
+
// tolerates (its dialog/working judgments come from the pane TEXT; the
|
|
109
|
+
// command only separates shell/idle). MUST stay before pane_title: that
|
|
110
|
+
// field is deliberately last so a pathological 0x1f inside a title can only
|
|
111
|
+
// garble the title, never shift the fields.
|
|
112
|
+
"#{pane_current_command} #{pane_pid}",
|
|
113
|
+
"#{pane_title}"
|
|
114
|
+
].join(LIST_FIELD_SEP);
|
|
115
|
+
function parseSessionListLine(line) {
|
|
116
|
+
if (!line) return void 0;
|
|
117
|
+
const parts = line.split(LIST_FIELD_SEP);
|
|
118
|
+
if (parts.length < 12) return void 0;
|
|
119
|
+
const [name, created, activity, cwd, vhTitle, manual, vhTags, vhAttach, paneW, paneH, paneCommand] = parts;
|
|
120
|
+
if (!name) return void 0;
|
|
121
|
+
const geom = parsePositiveInt(paneW) !== void 0 && parsePositiveInt(paneH) !== void 0 ? { paneCols: parsePositiveInt(paneW), paneRows: parsePositiveInt(paneH) } : {};
|
|
122
|
+
return {
|
|
123
|
+
name,
|
|
124
|
+
created: created ? Number(created) * 1e3 : void 0,
|
|
125
|
+
activity: activity ? Number(activity) * 1e3 : void 0,
|
|
126
|
+
cwd: cwd || void 0,
|
|
127
|
+
vhTitle: vhTitle.trim() || void 0,
|
|
128
|
+
manual: manual.trim().length > 0,
|
|
129
|
+
tags: parseTerminalTags(vhTags),
|
|
130
|
+
paneCurrentCommand: paneCommand.trim().replace(/ \d+$/, "") || void 0,
|
|
131
|
+
.../ (\d+)$/.test(paneCommand) ? { panePid: Number(paneCommand.match(/ (\d+)$/)[1]) } : {},
|
|
132
|
+
// Verbatim (no trim): tmux allows edge spaces in a session name and the
|
|
133
|
+
// restore lookup / attach echo compare it exactly.
|
|
134
|
+
attachTmux: isSafeTmuxSessionName(vhAttach) ? vhAttach : void 0,
|
|
135
|
+
...geom,
|
|
136
|
+
// pane_title is last, so anything after field 11 is title content that
|
|
137
|
+
// contained the separator — rejoin it rather than dropping it.
|
|
138
|
+
paneTitle: parts.slice(11).join(LIST_FIELD_SEP) || void 0
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
function parsePositiveInt(raw) {
|
|
142
|
+
if (raw === void 0 || !/^[0-9]{1,5}$/.test(raw.trim())) return void 0;
|
|
143
|
+
const n = Number(raw.trim());
|
|
144
|
+
return n > 0 ? n : void 0;
|
|
145
|
+
}
|
|
146
|
+
const TERMINAL_TAG_MAX_COUNT = 64;
|
|
147
|
+
const TERMINAL_TAG_MAX_LENGTH = 24;
|
|
148
|
+
const TERMINAL_TAGS_MAX_BYTES = 4096;
|
|
149
|
+
function validateTerminalTags(value) {
|
|
150
|
+
if (!Array.isArray(value) || value.length > TERMINAL_TAG_MAX_COUNT) return void 0;
|
|
151
|
+
const seen = /* @__PURE__ */ new Set();
|
|
152
|
+
const out = [];
|
|
153
|
+
for (const valueTag of value) {
|
|
154
|
+
if (typeof valueTag !== "string") return void 0;
|
|
155
|
+
const tag = valueTag.trim().replace(/^#+/, "").trim().replace(/\s+/g, "-").slice(0, TERMINAL_TAG_MAX_LENGTH);
|
|
156
|
+
if (!tag || tag !== valueTag) return void 0;
|
|
157
|
+
const key = tag.toLowerCase();
|
|
158
|
+
if (seen.has(key)) return void 0;
|
|
159
|
+
seen.add(key);
|
|
160
|
+
out.push(tag);
|
|
161
|
+
}
|
|
162
|
+
if (Buffer.byteLength(JSON.stringify(out), "utf8") > TERMINAL_TAGS_MAX_BYTES) return void 0;
|
|
163
|
+
return out;
|
|
164
|
+
}
|
|
165
|
+
function parseTerminalTags(raw) {
|
|
166
|
+
if (typeof raw !== "string" || !raw.trim()) return [];
|
|
167
|
+
try {
|
|
168
|
+
return validateTerminalTags(JSON.parse(raw)) ?? [];
|
|
169
|
+
} catch {
|
|
170
|
+
return [];
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const TMUX_TIMEOUT_MS = 3e3;
|
|
175
|
+
const VH_LIST_SESSIONS_FORMAT = LIST_SESSIONS_FORMAT;
|
|
176
|
+
function parseVhTerminals(stdout, hostname = os.hostname()) {
|
|
177
|
+
const out = [];
|
|
178
|
+
for (const line of stdout.split("\n")) {
|
|
179
|
+
const s = parseSessionListLine(line);
|
|
180
|
+
if (!s || !s.name.startsWith("vh-")) continue;
|
|
181
|
+
out.push({
|
|
182
|
+
id: s.name.slice(3),
|
|
183
|
+
title: s.vhTitle ?? deriveAutoTitle(s.paneTitle, hostname, s.cwd),
|
|
184
|
+
cwd: s.cwd,
|
|
185
|
+
createdAt: s.created,
|
|
186
|
+
activityAt: s.activity
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
return out;
|
|
190
|
+
}
|
|
191
|
+
function runTmux(args, input) {
|
|
192
|
+
try {
|
|
193
|
+
const r = spawnSync("tmux", tmuxArgs(args), {
|
|
194
|
+
encoding: "utf8",
|
|
195
|
+
timeout: TMUX_TIMEOUT_MS,
|
|
196
|
+
input,
|
|
197
|
+
env: scrubTmuxClientEnv({ ...process.env })
|
|
198
|
+
});
|
|
199
|
+
if (r.error) return { ok: false, stdout: "", error: r.error.message };
|
|
200
|
+
if (r.status !== 0) return { ok: false, stdout: r.stdout ?? "", error: (r.stderr || `tmux exited with ${r.status}`).trim() };
|
|
201
|
+
return { ok: true, stdout: r.stdout ?? "" };
|
|
202
|
+
} catch (error) {
|
|
203
|
+
return { ok: false, stdout: "", error: error instanceof Error ? error.message : String(error) };
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
function listVhTerminals() {
|
|
207
|
+
const r = runTmux(["list-sessions", "-F", VH_LIST_SESSIONS_FORMAT]);
|
|
208
|
+
if (!r.ok) {
|
|
209
|
+
if ((r.error ?? "").includes("no server running")) return { ok: true, terminals: [] };
|
|
210
|
+
return { ok: false, terminals: [], error: r.error };
|
|
211
|
+
}
|
|
212
|
+
return { ok: true, terminals: parseVhTerminals(r.stdout) };
|
|
213
|
+
}
|
|
214
|
+
function target(terminalId) {
|
|
215
|
+
return `=vh-${terminalId}:`;
|
|
216
|
+
}
|
|
217
|
+
function readVhTerminal(terminalId, lines) {
|
|
218
|
+
const n = Math.max(1, Math.min(2e3, Math.floor(lines)));
|
|
219
|
+
const r = runTmux(["capture-pane", "-p", "-t", target(terminalId), "-S", `-${n}`]);
|
|
220
|
+
return { ok: r.ok, text: r.stdout, error: r.error };
|
|
221
|
+
}
|
|
222
|
+
const PASTE_BUFFER_NAME = "vh-assistant-paste";
|
|
223
|
+
function sendToVhTerminal(terminalId, text, submit) {
|
|
224
|
+
const load = runTmux(["load-buffer", "-b", PASTE_BUFFER_NAME, "-"], text);
|
|
225
|
+
if (!load.ok) return { ok: false, error: `load-buffer failed: ${load.error}` };
|
|
226
|
+
const paste = runTmux(["paste-buffer", "-p", "-d", "-b", PASTE_BUFFER_NAME, "-t", target(terminalId)]);
|
|
227
|
+
if (!paste.ok) return { ok: false, error: `paste-buffer failed: ${paste.error}` };
|
|
228
|
+
if (submit) {
|
|
229
|
+
const enter = runTmux(["send-keys", "-t", target(terminalId), "Enter"]);
|
|
230
|
+
if (!enter.ok) return { ok: false, error: `send-keys Enter failed: ${enter.error}` };
|
|
231
|
+
}
|
|
232
|
+
return { ok: true };
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export { LIST_SESSIONS_FORMAT as L, TMUX_SESSION_ID_RE as T, USER_SESSIONS_FORMAT as U, attachStartupCommand as a, isVhSessionName as b, parseSessionListLine as c, deriveAutoTitle as d, LIST_FIELD_SEP as e, isSafeTmuxSessionName as i, listVhTerminals as l, parseUserSessions as p, readVhTerminal as r, sendToVhTerminal as s, validateTerminalTags as v };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { l as logger } from './logger-6HYUEXNk.mjs';
|
|
2
|
+
|
|
3
|
+
async function delay(ms) {
|
|
4
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
5
|
+
}
|
|
6
|
+
function exponentialBackoffDelay(currentFailureCount, minDelay, maxDelay, maxFailureCount) {
|
|
7
|
+
let maxDelayRet = minDelay + (maxDelay - minDelay) / maxFailureCount * Math.min(currentFailureCount, maxFailureCount);
|
|
8
|
+
return Math.round(Math.random() * maxDelayRet);
|
|
9
|
+
}
|
|
10
|
+
function createBackoff(opts) {
|
|
11
|
+
return async (callback) => {
|
|
12
|
+
let currentFailureCount = 0;
|
|
13
|
+
const minDelay = opts && opts.minDelay !== void 0 ? opts.minDelay : 250;
|
|
14
|
+
const maxDelay = opts && opts.maxDelay !== void 0 ? opts.maxDelay : 1e3;
|
|
15
|
+
const maxFailureCount = opts && opts.maxFailureCount !== void 0 ? opts.maxFailureCount : 50;
|
|
16
|
+
while (true) {
|
|
17
|
+
try {
|
|
18
|
+
return await callback();
|
|
19
|
+
} catch (e) {
|
|
20
|
+
if (currentFailureCount < maxFailureCount) {
|
|
21
|
+
currentFailureCount++;
|
|
22
|
+
}
|
|
23
|
+
if (opts && opts.onError) {
|
|
24
|
+
opts.onError(e, currentFailureCount);
|
|
25
|
+
}
|
|
26
|
+
let waitForRequest = exponentialBackoffDelay(currentFailureCount, minDelay, maxDelay, maxFailureCount);
|
|
27
|
+
await delay(waitForRequest);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
let backoff = createBackoff({
|
|
33
|
+
onError: (e, failuresCount) => {
|
|
34
|
+
logger.debug(`[BACKOFF] retry ${failuresCount}:`, e?.message || e);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
export { backoff as b, delay as d, exponentialBackoffDelay as e };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var logger = require('./logger-CS0mw3fa.cjs');
|
|
4
|
+
|
|
5
|
+
async function delay(ms) {
|
|
6
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
7
|
+
}
|
|
8
|
+
function exponentialBackoffDelay(currentFailureCount, minDelay, maxDelay, maxFailureCount) {
|
|
9
|
+
let maxDelayRet = minDelay + (maxDelay - minDelay) / maxFailureCount * Math.min(currentFailureCount, maxFailureCount);
|
|
10
|
+
return Math.round(Math.random() * maxDelayRet);
|
|
11
|
+
}
|
|
12
|
+
function createBackoff(opts) {
|
|
13
|
+
return async (callback) => {
|
|
14
|
+
let currentFailureCount = 0;
|
|
15
|
+
const minDelay = opts && opts.minDelay !== void 0 ? opts.minDelay : 250;
|
|
16
|
+
const maxDelay = opts && opts.maxDelay !== void 0 ? opts.maxDelay : 1e3;
|
|
17
|
+
const maxFailureCount = opts && opts.maxFailureCount !== void 0 ? opts.maxFailureCount : 50;
|
|
18
|
+
while (true) {
|
|
19
|
+
try {
|
|
20
|
+
return await callback();
|
|
21
|
+
} catch (e) {
|
|
22
|
+
if (currentFailureCount < maxFailureCount) {
|
|
23
|
+
currentFailureCount++;
|
|
24
|
+
}
|
|
25
|
+
if (opts && opts.onError) {
|
|
26
|
+
opts.onError(e, currentFailureCount);
|
|
27
|
+
}
|
|
28
|
+
let waitForRequest = exponentialBackoffDelay(currentFailureCount, minDelay, maxDelay, maxFailureCount);
|
|
29
|
+
await delay(waitForRequest);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
let backoff = createBackoff({
|
|
35
|
+
onError: (e, failuresCount) => {
|
|
36
|
+
logger.logger.debug(`[BACKOFF] retry ${failuresCount}:`, e?.message || e);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
exports.backoff = backoff;
|
|
41
|
+
exports.delay = delay;
|
|
42
|
+
exports.exponentialBackoffDelay = exponentialBackoffDelay;
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import { T as TITLE_PROMPT_PREFIX } from './oneShotPrompts-DT4p6RNd.mjs';
|
|
4
|
+
import { randomUUID } from 'node:crypto';
|
|
5
|
+
import { resolve, join } from 'node:path';
|
|
6
|
+
import { l as logger } from './logger-6HYUEXNk.mjs';
|
|
7
|
+
import { p as projectPath } from './projectPath-BTBIMB57.mjs';
|
|
8
|
+
|
|
9
|
+
const MAX_PROMPT_SAMPLE_CHARS = 500;
|
|
10
|
+
const MAX_TITLE_CHARS = 60;
|
|
11
|
+
const GENERATION_TIMEOUT_MS = 3e4;
|
|
12
|
+
function resolveClaudeBinary() {
|
|
13
|
+
try {
|
|
14
|
+
const require = createRequire(import.meta.url);
|
|
15
|
+
const utilsPath = resolve(join(projectPath(), "scripts", "claude_version_utils.cjs"));
|
|
16
|
+
const { getClaudeCliPath } = require(utilsPath);
|
|
17
|
+
const path = getClaudeCliPath();
|
|
18
|
+
if (typeof path === "string" && path.length > 0) return path;
|
|
19
|
+
} catch (error) {
|
|
20
|
+
logger.debug("[titleGenerator] getClaudeCliPath failed; falling back to PATH", { error: String(error) });
|
|
21
|
+
}
|
|
22
|
+
return process.env.HAPPY_CLAUDE_PATH || "claude";
|
|
23
|
+
}
|
|
24
|
+
function buildPrompt(firstUserMessage) {
|
|
25
|
+
const sample = firstUserMessage.slice(0, MAX_PROMPT_SAMPLE_CHARS);
|
|
26
|
+
return `${TITLE_PROMPT_PREFIX} (match the message's language). Be concrete \u2014 name the actual task/subject. Do NOT output generic placeholders like "\u65B0\u5BF9\u8BDD"/"New chat"/"Conversation"/"Untitled". No quotes, no trailing punctuation. Reply with ONLY the title.
|
|
27
|
+
|
|
28
|
+
Message: "${sample}"`;
|
|
29
|
+
}
|
|
30
|
+
const GENERIC_TITLES = /* @__PURE__ */ new Set([
|
|
31
|
+
"new chat",
|
|
32
|
+
"new conversation",
|
|
33
|
+
"conversation",
|
|
34
|
+
"chat",
|
|
35
|
+
"untitled",
|
|
36
|
+
"new session",
|
|
37
|
+
"\u65B0\u5BF9\u8BDD",
|
|
38
|
+
"\u65B0\u4F1A\u8BDD",
|
|
39
|
+
"\u5BF9\u8BDD",
|
|
40
|
+
"\u4F1A\u8BDD",
|
|
41
|
+
"\u804A\u5929",
|
|
42
|
+
"\u672A\u547D\u540D"
|
|
43
|
+
]);
|
|
44
|
+
function sanitizeTitle(raw) {
|
|
45
|
+
let title = raw.trim();
|
|
46
|
+
if (!title) return null;
|
|
47
|
+
const firstLine = title.split(/\r?\n/).map((l) => l.trim()).find((l) => l.length > 0);
|
|
48
|
+
if (!firstLine) return null;
|
|
49
|
+
title = firstLine;
|
|
50
|
+
title = title.replace(/^["'`]+/, "").replace(/["'`]+$/, "").trim();
|
|
51
|
+
title = title.replace(/[.。!?!?,,::;;]+$/, "").trim();
|
|
52
|
+
if (!title) return null;
|
|
53
|
+
if (title.length > MAX_TITLE_CHARS) {
|
|
54
|
+
title = title.slice(0, MAX_TITLE_CHARS).trim();
|
|
55
|
+
}
|
|
56
|
+
if (!title) return null;
|
|
57
|
+
if (GENERIC_TITLES.has(title.toLowerCase().replace(/\s+/g, " ").trim())) return null;
|
|
58
|
+
return title;
|
|
59
|
+
}
|
|
60
|
+
function runClaudeOneShot(binary, prompt) {
|
|
61
|
+
return new Promise((resolve2) => {
|
|
62
|
+
let settled = false;
|
|
63
|
+
const done = (value) => {
|
|
64
|
+
if (settled) return;
|
|
65
|
+
settled = true;
|
|
66
|
+
resolve2(value);
|
|
67
|
+
};
|
|
68
|
+
let child;
|
|
69
|
+
try {
|
|
70
|
+
child = spawn(binary, ["-p", "--model", "haiku", prompt], {
|
|
71
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
72
|
+
// HAPPY_MANAGED=1: this claude is run BY happy. The user-wide
|
|
73
|
+
// terminal-mirror SessionStart hook exits on it, so a one-shot
|
|
74
|
+
// spawned from inside a vh web terminal (VH_TERMINAL_ID
|
|
75
|
+
// inherited — B-500's pi terminal bridge, or `very-happy` typed
|
|
76
|
+
// in a terminal) never binds a mirror session to itself.
|
|
77
|
+
env: { ...process.env, HAPPY_MANAGED: "1" }
|
|
78
|
+
});
|
|
79
|
+
} catch (error) {
|
|
80
|
+
logger.debug("[titleGenerator] spawn failed", { error: String(error) });
|
|
81
|
+
done(null);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const timer = setTimeout(() => {
|
|
85
|
+
logger.debug("[titleGenerator] generation timed out, killing claude one-shot");
|
|
86
|
+
try {
|
|
87
|
+
child.kill("SIGKILL");
|
|
88
|
+
} catch {
|
|
89
|
+
}
|
|
90
|
+
done(null);
|
|
91
|
+
}, GENERATION_TIMEOUT_MS);
|
|
92
|
+
let stdout = "";
|
|
93
|
+
child.stdout?.on("data", (chunk) => {
|
|
94
|
+
stdout += chunk.toString("utf8");
|
|
95
|
+
});
|
|
96
|
+
child.on("error", (error) => {
|
|
97
|
+
clearTimeout(timer);
|
|
98
|
+
logger.debug("[titleGenerator] claude one-shot error", { error: String(error) });
|
|
99
|
+
done(null);
|
|
100
|
+
});
|
|
101
|
+
child.on("close", (code) => {
|
|
102
|
+
clearTimeout(timer);
|
|
103
|
+
if (code !== 0) {
|
|
104
|
+
logger.debug(`[titleGenerator] claude one-shot exited with code ${code}`);
|
|
105
|
+
done(null);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
done(stdout);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
async function suggestTitleForPrompt(prompt) {
|
|
113
|
+
const binary = resolveClaudeBinary();
|
|
114
|
+
if (!binary) {
|
|
115
|
+
logger.debug("[titleGenerator] no claude binary; skipping auto-title");
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
const stdout = await runClaudeOneShot(binary, buildPrompt(prompt));
|
|
119
|
+
if (stdout == null) return null;
|
|
120
|
+
const title = sanitizeTitle(stdout);
|
|
121
|
+
if (!title) logger.debug("[titleGenerator] empty/unusable title output; skipping");
|
|
122
|
+
return title;
|
|
123
|
+
}
|
|
124
|
+
class TitleGenerator {
|
|
125
|
+
constructor(session) {
|
|
126
|
+
this.session = session;
|
|
127
|
+
}
|
|
128
|
+
triggered = false;
|
|
129
|
+
/**
|
|
130
|
+
* Fire-and-forget. Returns immediately. Safe to call on every user
|
|
131
|
+
* message; only the first call on a session without an existing summary
|
|
132
|
+
* does anything.
|
|
133
|
+
*/
|
|
134
|
+
maybeGenerate(firstUserMessage) {
|
|
135
|
+
if (this.triggered) return;
|
|
136
|
+
if (!firstUserMessage || firstUserMessage.trim().length === 0) return;
|
|
137
|
+
if (this.session.getMetadata()?.summary?.text) {
|
|
138
|
+
this.triggered = true;
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
this.triggered = true;
|
|
142
|
+
void this.generate(firstUserMessage).catch((error) => {
|
|
143
|
+
logger.debug("[titleGenerator] generation rejected", { error: String(error) });
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
async generate(firstUserMessage) {
|
|
147
|
+
if (this.session.getMetadata()?.summary?.text) {
|
|
148
|
+
logger.debug("[titleGenerator] summary appeared before generation; skipping");
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
const title = await suggestTitleForPrompt(firstUserMessage);
|
|
152
|
+
if (!title) return;
|
|
153
|
+
if (this.session.getMetadata()?.summary?.text) {
|
|
154
|
+
logger.debug("[titleGenerator] summary set during generation; not overwriting");
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
logger.debug(`[titleGenerator] setting auto-title: ${title}`);
|
|
158
|
+
this.session.sendClaudeSessionMessage({
|
|
159
|
+
type: "summary",
|
|
160
|
+
summary: title,
|
|
161
|
+
leafUuid: randomUUID()
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export { TitleGenerator as T, runClaudeOneShot as a, resolveClaudeBinary as r, suggestTitleForPrompt as s };
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var node_child_process = require('node:child_process');
|
|
4
|
+
var module$1 = require('node:module');
|
|
5
|
+
var oneShotPrompts = require('./oneShotPrompts-CL0dip9C.cjs');
|
|
6
|
+
var node_crypto = require('node:crypto');
|
|
7
|
+
var path = require('node:path');
|
|
8
|
+
var logger = require('./logger-CS0mw3fa.cjs');
|
|
9
|
+
var projectPath = require('./projectPath-ChLhdOyt.cjs');
|
|
10
|
+
|
|
11
|
+
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
12
|
+
const MAX_PROMPT_SAMPLE_CHARS = 500;
|
|
13
|
+
const MAX_TITLE_CHARS = 60;
|
|
14
|
+
const GENERATION_TIMEOUT_MS = 3e4;
|
|
15
|
+
function resolveClaudeBinary() {
|
|
16
|
+
try {
|
|
17
|
+
const require$1 = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('titleGenerator-ChzMJsyg.cjs', document.baseURI).href)));
|
|
18
|
+
const utilsPath = path.resolve(path.join(projectPath.projectPath(), "scripts", "claude_version_utils.cjs"));
|
|
19
|
+
const { getClaudeCliPath } = require$1(utilsPath);
|
|
20
|
+
const path$1 = getClaudeCliPath();
|
|
21
|
+
if (typeof path$1 === "string" && path$1.length > 0) return path$1;
|
|
22
|
+
} catch (error) {
|
|
23
|
+
logger.logger.debug("[titleGenerator] getClaudeCliPath failed; falling back to PATH", { error: String(error) });
|
|
24
|
+
}
|
|
25
|
+
return process.env.HAPPY_CLAUDE_PATH || "claude";
|
|
26
|
+
}
|
|
27
|
+
function buildPrompt(firstUserMessage) {
|
|
28
|
+
const sample = firstUserMessage.slice(0, MAX_PROMPT_SAMPLE_CHARS);
|
|
29
|
+
return `${oneShotPrompts.TITLE_PROMPT_PREFIX} (match the message's language). Be concrete \u2014 name the actual task/subject. Do NOT output generic placeholders like "\u65B0\u5BF9\u8BDD"/"New chat"/"Conversation"/"Untitled". No quotes, no trailing punctuation. Reply with ONLY the title.
|
|
30
|
+
|
|
31
|
+
Message: "${sample}"`;
|
|
32
|
+
}
|
|
33
|
+
const GENERIC_TITLES = /* @__PURE__ */ new Set([
|
|
34
|
+
"new chat",
|
|
35
|
+
"new conversation",
|
|
36
|
+
"conversation",
|
|
37
|
+
"chat",
|
|
38
|
+
"untitled",
|
|
39
|
+
"new session",
|
|
40
|
+
"\u65B0\u5BF9\u8BDD",
|
|
41
|
+
"\u65B0\u4F1A\u8BDD",
|
|
42
|
+
"\u5BF9\u8BDD",
|
|
43
|
+
"\u4F1A\u8BDD",
|
|
44
|
+
"\u804A\u5929",
|
|
45
|
+
"\u672A\u547D\u540D"
|
|
46
|
+
]);
|
|
47
|
+
function sanitizeTitle(raw) {
|
|
48
|
+
let title = raw.trim();
|
|
49
|
+
if (!title) return null;
|
|
50
|
+
const firstLine = title.split(/\r?\n/).map((l) => l.trim()).find((l) => l.length > 0);
|
|
51
|
+
if (!firstLine) return null;
|
|
52
|
+
title = firstLine;
|
|
53
|
+
title = title.replace(/^["'`]+/, "").replace(/["'`]+$/, "").trim();
|
|
54
|
+
title = title.replace(/[.。!?!?,,::;;]+$/, "").trim();
|
|
55
|
+
if (!title) return null;
|
|
56
|
+
if (title.length > MAX_TITLE_CHARS) {
|
|
57
|
+
title = title.slice(0, MAX_TITLE_CHARS).trim();
|
|
58
|
+
}
|
|
59
|
+
if (!title) return null;
|
|
60
|
+
if (GENERIC_TITLES.has(title.toLowerCase().replace(/\s+/g, " ").trim())) return null;
|
|
61
|
+
return title;
|
|
62
|
+
}
|
|
63
|
+
function runClaudeOneShot(binary, prompt) {
|
|
64
|
+
return new Promise((resolve2) => {
|
|
65
|
+
let settled = false;
|
|
66
|
+
const done = (value) => {
|
|
67
|
+
if (settled) return;
|
|
68
|
+
settled = true;
|
|
69
|
+
resolve2(value);
|
|
70
|
+
};
|
|
71
|
+
let child;
|
|
72
|
+
try {
|
|
73
|
+
child = node_child_process.spawn(binary, ["-p", "--model", "haiku", prompt], {
|
|
74
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
75
|
+
// HAPPY_MANAGED=1: this claude is run BY happy. The user-wide
|
|
76
|
+
// terminal-mirror SessionStart hook exits on it, so a one-shot
|
|
77
|
+
// spawned from inside a vh web terminal (VH_TERMINAL_ID
|
|
78
|
+
// inherited — B-500's pi terminal bridge, or `very-happy` typed
|
|
79
|
+
// in a terminal) never binds a mirror session to itself.
|
|
80
|
+
env: { ...process.env, HAPPY_MANAGED: "1" }
|
|
81
|
+
});
|
|
82
|
+
} catch (error) {
|
|
83
|
+
logger.logger.debug("[titleGenerator] spawn failed", { error: String(error) });
|
|
84
|
+
done(null);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const timer = setTimeout(() => {
|
|
88
|
+
logger.logger.debug("[titleGenerator] generation timed out, killing claude one-shot");
|
|
89
|
+
try {
|
|
90
|
+
child.kill("SIGKILL");
|
|
91
|
+
} catch {
|
|
92
|
+
}
|
|
93
|
+
done(null);
|
|
94
|
+
}, GENERATION_TIMEOUT_MS);
|
|
95
|
+
let stdout = "";
|
|
96
|
+
child.stdout?.on("data", (chunk) => {
|
|
97
|
+
stdout += chunk.toString("utf8");
|
|
98
|
+
});
|
|
99
|
+
child.on("error", (error) => {
|
|
100
|
+
clearTimeout(timer);
|
|
101
|
+
logger.logger.debug("[titleGenerator] claude one-shot error", { error: String(error) });
|
|
102
|
+
done(null);
|
|
103
|
+
});
|
|
104
|
+
child.on("close", (code) => {
|
|
105
|
+
clearTimeout(timer);
|
|
106
|
+
if (code !== 0) {
|
|
107
|
+
logger.logger.debug(`[titleGenerator] claude one-shot exited with code ${code}`);
|
|
108
|
+
done(null);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
done(stdout);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
async function suggestTitleForPrompt(prompt) {
|
|
116
|
+
const binary = resolveClaudeBinary();
|
|
117
|
+
if (!binary) {
|
|
118
|
+
logger.logger.debug("[titleGenerator] no claude binary; skipping auto-title");
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
const stdout = await runClaudeOneShot(binary, buildPrompt(prompt));
|
|
122
|
+
if (stdout == null) return null;
|
|
123
|
+
const title = sanitizeTitle(stdout);
|
|
124
|
+
if (!title) logger.logger.debug("[titleGenerator] empty/unusable title output; skipping");
|
|
125
|
+
return title;
|
|
126
|
+
}
|
|
127
|
+
class TitleGenerator {
|
|
128
|
+
constructor(session) {
|
|
129
|
+
this.session = session;
|
|
130
|
+
}
|
|
131
|
+
triggered = false;
|
|
132
|
+
/**
|
|
133
|
+
* Fire-and-forget. Returns immediately. Safe to call on every user
|
|
134
|
+
* message; only the first call on a session without an existing summary
|
|
135
|
+
* does anything.
|
|
136
|
+
*/
|
|
137
|
+
maybeGenerate(firstUserMessage) {
|
|
138
|
+
if (this.triggered) return;
|
|
139
|
+
if (!firstUserMessage || firstUserMessage.trim().length === 0) return;
|
|
140
|
+
if (this.session.getMetadata()?.summary?.text) {
|
|
141
|
+
this.triggered = true;
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
this.triggered = true;
|
|
145
|
+
void this.generate(firstUserMessage).catch((error) => {
|
|
146
|
+
logger.logger.debug("[titleGenerator] generation rejected", { error: String(error) });
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
async generate(firstUserMessage) {
|
|
150
|
+
if (this.session.getMetadata()?.summary?.text) {
|
|
151
|
+
logger.logger.debug("[titleGenerator] summary appeared before generation; skipping");
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
const title = await suggestTitleForPrompt(firstUserMessage);
|
|
155
|
+
if (!title) return;
|
|
156
|
+
if (this.session.getMetadata()?.summary?.text) {
|
|
157
|
+
logger.logger.debug("[titleGenerator] summary set during generation; not overwriting");
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
logger.logger.debug(`[titleGenerator] setting auto-title: ${title}`);
|
|
161
|
+
this.session.sendClaudeSessionMessage({
|
|
162
|
+
type: "summary",
|
|
163
|
+
summary: title,
|
|
164
|
+
leafUuid: node_crypto.randomUUID()
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
exports.TitleGenerator = TitleGenerator;
|
|
170
|
+
exports.resolveClaudeBinary = resolveClaudeBinary;
|
|
171
|
+
exports.runClaudeOneShot = runClaudeOneShot;
|
|
172
|
+
exports.suggestTitleForPrompt = suggestTitleForPrompt;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
function shellescape(s) {
|
|
2
|
+
return "'" + s.replace(/'/g, "'\\''") + "'";
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
const VH_TMUX_SOCKET_ENV = "VH_TMUX_SOCKET";
|
|
6
|
+
function tmuxSocketFlags(env = process.env) {
|
|
7
|
+
const socket = env[VH_TMUX_SOCKET_ENV];
|
|
8
|
+
return socket ? ["-S", socket] : [];
|
|
9
|
+
}
|
|
10
|
+
function tmuxArgs(args, env = process.env) {
|
|
11
|
+
return [...tmuxSocketFlags(env), ...args];
|
|
12
|
+
}
|
|
13
|
+
function scrubTmuxClientEnv(env) {
|
|
14
|
+
delete env.TMUX;
|
|
15
|
+
delete env.TMUX_PANE;
|
|
16
|
+
return env;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export { VH_TMUX_SOCKET_ENV as V, scrubTmuxClientEnv as a, shellescape as s, tmuxArgs as t };
|