@dmsdc-ai/aigentry-telepty 0.1.68 → 0.1.69
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/cli.js +4 -2
- package/daemon.js +91 -19
- package/package.json +1 -1
- package/terminal-backend.js +137 -0
package/cli.js
CHANGED
|
@@ -697,7 +697,8 @@ async function main() {
|
|
|
697
697
|
command,
|
|
698
698
|
cwd: process.cwd(),
|
|
699
699
|
backend: detectedBackend,
|
|
700
|
-
cmux_workspace_id: process.env.CMUX_WORKSPACE_ID || null
|
|
700
|
+
cmux_workspace_id: process.env.CMUX_WORKSPACE_ID || null,
|
|
701
|
+
cmux_surface_id: process.env.CMUX_SURFACE_ID || null
|
|
701
702
|
})
|
|
702
703
|
});
|
|
703
704
|
const data = await res.json();
|
|
@@ -776,7 +777,8 @@ async function main() {
|
|
|
776
777
|
command,
|
|
777
778
|
cwd: process.cwd(),
|
|
778
779
|
backend: detectedBackend,
|
|
779
|
-
cmux_workspace_id: process.env.CMUX_WORKSPACE_ID || null
|
|
780
|
+
cmux_workspace_id: process.env.CMUX_WORKSPACE_ID || null,
|
|
781
|
+
cmux_surface_id: process.env.CMUX_SURFACE_ID || null
|
|
780
782
|
})
|
|
781
783
|
});
|
|
782
784
|
} catch (e) {
|
package/daemon.js
CHANGED
|
@@ -8,6 +8,7 @@ const { getConfig } = require('./auth');
|
|
|
8
8
|
const pkg = require('./package.json');
|
|
9
9
|
const { claimDaemonState, clearDaemonState } = require('./daemon-control');
|
|
10
10
|
const { checkEntitlement } = require('./entitlement');
|
|
11
|
+
const terminalBackend = require('./terminal-backend');
|
|
11
12
|
|
|
12
13
|
const config = getConfig();
|
|
13
14
|
const EXPECTED_TOKEN = config.authToken;
|
|
@@ -19,7 +20,7 @@ function persistSessions() {
|
|
|
19
20
|
try {
|
|
20
21
|
const data = {};
|
|
21
22
|
for (const [id, s] of Object.entries(sessions)) {
|
|
22
|
-
data[id] = { id, type: s.type, command: s.command, cwd: s.cwd, backend: s.backend || null, cmuxWorkspaceId: s.cmuxWorkspaceId || null, createdAt: s.createdAt, lastActivityAt: s.lastActivityAt || null };
|
|
23
|
+
data[id] = { id, type: s.type, command: s.command, cwd: s.cwd, backend: s.backend || null, cmuxWorkspaceId: s.cmuxWorkspaceId || null, cmuxSurfaceId: s.cmuxSurfaceId || null, createdAt: s.createdAt, lastActivityAt: s.lastActivityAt || null };
|
|
23
24
|
}
|
|
24
25
|
fs.mkdirSync(require('path').dirname(SESSION_PERSIST_PATH), { recursive: true });
|
|
25
26
|
fs.writeFileSync(SESSION_PERSIST_PATH, JSON.stringify(data, null, 2));
|
|
@@ -138,6 +139,10 @@ const sessions = {};
|
|
|
138
139
|
const handoffs = {};
|
|
139
140
|
const threads = {};
|
|
140
141
|
|
|
142
|
+
// Detect terminal environment at daemon startup
|
|
143
|
+
const DETECTED_TERMINAL = terminalBackend.detectTerminal();
|
|
144
|
+
console.log(`[DAEMON] Terminal backend: ${DETECTED_TERMINAL}`);
|
|
145
|
+
|
|
141
146
|
// Restore persisted session metadata (wrapped sessions await reconnect)
|
|
142
147
|
const _persisted = loadPersistedSessions();
|
|
143
148
|
for (const [id, meta] of Object.entries(_persisted)) {
|
|
@@ -304,7 +309,7 @@ app.post('/api/sessions/spawn', (req, res) => {
|
|
|
304
309
|
});
|
|
305
310
|
|
|
306
311
|
app.post('/api/sessions/register', (req, res) => {
|
|
307
|
-
const { session_id, command, cwd = process.cwd(), backend, cmux_workspace_id } = req.body;
|
|
312
|
+
const { session_id, command, cwd = process.cwd(), backend, cmux_workspace_id, cmux_surface_id } = req.body;
|
|
308
313
|
if (!session_id) return res.status(400).json({ error: 'session_id is required' });
|
|
309
314
|
// Entitlement: check session limit for new registrations
|
|
310
315
|
if (!sessions[session_id]) {
|
|
@@ -322,6 +327,7 @@ app.post('/api/sessions/register', (req, res) => {
|
|
|
322
327
|
if (cwd) existing.cwd = cwd;
|
|
323
328
|
if (backend) existing.backend = backend;
|
|
324
329
|
if (cmux_workspace_id) existing.cmuxWorkspaceId = cmux_workspace_id;
|
|
330
|
+
if (cmux_surface_id) existing.cmuxSurfaceId = cmux_surface_id;
|
|
325
331
|
console.log(`[REGISTER] Re-registered session ${session_id} (updated metadata)`);
|
|
326
332
|
return res.status(200).json({ session_id, type: 'wrapped', command: existing.command, cwd: existing.cwd, reregistered: true });
|
|
327
333
|
}
|
|
@@ -335,6 +341,7 @@ app.post('/api/sessions/register', (req, res) => {
|
|
|
335
341
|
cwd,
|
|
336
342
|
backend: backend || 'kitty',
|
|
337
343
|
cmuxWorkspaceId: cmux_workspace_id || null,
|
|
344
|
+
cmuxSurfaceId: cmux_surface_id || null,
|
|
338
345
|
createdAt: new Date().toISOString(),
|
|
339
346
|
lastActivityAt: new Date().toISOString(),
|
|
340
347
|
clients: new Set(),
|
|
@@ -393,6 +400,7 @@ app.get('/api/sessions', (req, res) => {
|
|
|
393
400
|
cwd: session.cwd,
|
|
394
401
|
backend: session.backend || 'kitty',
|
|
395
402
|
cmuxWorkspaceId: session.cmuxWorkspaceId || null,
|
|
403
|
+
cmuxSurfaceId: session.cmuxSurfaceId || null,
|
|
396
404
|
createdAt: session.createdAt,
|
|
397
405
|
lastActivityAt: session.lastActivityAt || null,
|
|
398
406
|
idleSeconds,
|
|
@@ -436,6 +444,7 @@ app.get('/api/meta', (req, res) => {
|
|
|
436
444
|
host: HOST,
|
|
437
445
|
port: Number(PORT),
|
|
438
446
|
machine_id: MACHINE_ID,
|
|
447
|
+
terminal: DETECTED_TERMINAL,
|
|
439
448
|
capabilities: ['sessions', 'wrapped-sessions', 'skill-installer', 'singleton-daemon', 'handoff-inbox', 'deliberation-threads', 'cross-machine']
|
|
440
449
|
});
|
|
441
450
|
});
|
|
@@ -464,6 +473,27 @@ app.post('/api/sessions/multicast/inject', (req, res) => {
|
|
|
464
473
|
const session = sessions[id];
|
|
465
474
|
if (session) {
|
|
466
475
|
try {
|
|
476
|
+
// cmux auto-detect at daemon level (text + enter)
|
|
477
|
+
if (DETECTED_TERMINAL === 'cmux') {
|
|
478
|
+
const ok = terminalBackend.cmuxSendText(id, prompt);
|
|
479
|
+
if (ok) {
|
|
480
|
+
setTimeout(() => terminalBackend.cmuxSendEnter(id), 300);
|
|
481
|
+
results.successful.push({ id, strategy: 'cmux_auto' });
|
|
482
|
+
// Broadcast injection to bus
|
|
483
|
+
const busMsg = JSON.stringify({
|
|
484
|
+
type: 'injection',
|
|
485
|
+
sender: 'cli',
|
|
486
|
+
target_agent: id,
|
|
487
|
+
content: prompt,
|
|
488
|
+
timestamp: new Date().toISOString()
|
|
489
|
+
});
|
|
490
|
+
busClients.forEach(client => {
|
|
491
|
+
if (client.readyState === 1) client.send(busMsg);
|
|
492
|
+
});
|
|
493
|
+
return; // skip WS path for this session
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
467
497
|
// Inject text first, then \r separately after delay
|
|
468
498
|
if (session.type === 'wrapped') {
|
|
469
499
|
if (session.ownerWs && session.ownerWs.readyState === 1) {
|
|
@@ -516,6 +546,16 @@ app.post('/api/sessions/broadcast/inject', (req, res) => {
|
|
|
516
546
|
Object.keys(sessions).forEach(id => {
|
|
517
547
|
const session = sessions[id];
|
|
518
548
|
try {
|
|
549
|
+
// cmux auto-detect at daemon level (text + enter)
|
|
550
|
+
if (DETECTED_TERMINAL === 'cmux') {
|
|
551
|
+
const ok = terminalBackend.cmuxSendText(id, prompt);
|
|
552
|
+
if (ok) {
|
|
553
|
+
setTimeout(() => terminalBackend.cmuxSendEnter(id), 300);
|
|
554
|
+
results.successful.push({ id, strategy: 'cmux_auto' });
|
|
555
|
+
return; // skip WS path for this session
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
519
559
|
// Inject text first, then \r separately after delay
|
|
520
560
|
if (session.type === 'wrapped') {
|
|
521
561
|
if (session.ownerWs && session.ownerWs.readyState === 1) {
|
|
@@ -729,8 +769,12 @@ app.post('/api/sessions/:id/submit', (req, res) => {
|
|
|
729
769
|
console.log(`[SUBMIT] Session ${id} (${session.command}) using strategy: ${strategy}`);
|
|
730
770
|
|
|
731
771
|
let success = false;
|
|
732
|
-
// cmux
|
|
733
|
-
if (
|
|
772
|
+
// cmux auto-detect at daemon level
|
|
773
|
+
if (DETECTED_TERMINAL === 'cmux') {
|
|
774
|
+
success = terminalBackend.cmuxSendEnter(id);
|
|
775
|
+
}
|
|
776
|
+
// Session-level cmux backend
|
|
777
|
+
if (!success && session.backend === 'cmux' && session.cmuxWorkspaceId) {
|
|
734
778
|
success = submitViaCmux(id);
|
|
735
779
|
}
|
|
736
780
|
if (!success) {
|
|
@@ -768,8 +812,12 @@ app.post('/api/sessions/submit-all', (req, res) => {
|
|
|
768
812
|
const strategy = getSubmitStrategy(session.command);
|
|
769
813
|
let success = false;
|
|
770
814
|
|
|
771
|
-
// cmux
|
|
772
|
-
if (
|
|
815
|
+
// cmux auto-detect at daemon level
|
|
816
|
+
if (DETECTED_TERMINAL === 'cmux') {
|
|
817
|
+
success = terminalBackend.cmuxSendEnter(id);
|
|
818
|
+
}
|
|
819
|
+
// Session-level cmux backend
|
|
820
|
+
if (!success && session.backend === 'cmux' && session.cmuxWorkspaceId) {
|
|
773
821
|
success = submitViaCmux(id);
|
|
774
822
|
}
|
|
775
823
|
if (!success) {
|
|
@@ -835,14 +883,23 @@ app.post('/api/sessions/:id/inject', (req, res) => {
|
|
|
835
883
|
|
|
836
884
|
let submitResult = null;
|
|
837
885
|
if (session.type === 'wrapped') {
|
|
838
|
-
// For wrapped sessions: try
|
|
839
|
-
// then
|
|
886
|
+
// For wrapped sessions: try cmux send (daemon-level auto-detect),
|
|
887
|
+
// then kitty send-text (bypasses allow bridge queue),
|
|
888
|
+
// then WS as fallback, then submit via cmux/osascript/kitty/WS
|
|
840
889
|
const sock = findKittySocket();
|
|
841
890
|
if (!session.kittyWindowId && sock) session.kittyWindowId = findKittyWindowId(sock, id);
|
|
842
891
|
const wid = session.kittyWindowId;
|
|
843
892
|
|
|
844
893
|
let kittyOk = false;
|
|
845
|
-
|
|
894
|
+
let cmuxOk = false;
|
|
895
|
+
|
|
896
|
+
// cmux backend: send text directly to surface (daemon-level auto-detect)
|
|
897
|
+
if (DETECTED_TERMINAL === 'cmux') {
|
|
898
|
+
cmuxOk = terminalBackend.cmuxSendText(id, finalPrompt);
|
|
899
|
+
if (cmuxOk) console.log(`[INJECT] cmux send for ${id}`);
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
if (!cmuxOk && wid && sock) {
|
|
846
903
|
// Kitty send-text primary (bypasses allow bridge queue)
|
|
847
904
|
try {
|
|
848
905
|
const escaped = finalPrompt.replace(/\\/g, '\\\\').replace(/'/g, "'\\''");
|
|
@@ -856,7 +913,7 @@ app.post('/api/sessions/:id/inject', (req, res) => {
|
|
|
856
913
|
session.kittyWindowId = null;
|
|
857
914
|
}
|
|
858
915
|
}
|
|
859
|
-
if (!kittyOk) {
|
|
916
|
+
if (!cmuxOk && !kittyOk) {
|
|
860
917
|
// Fallback: WS (works with new allow bridges that have queue flush)
|
|
861
918
|
const wsOk = writeToSession(finalPrompt);
|
|
862
919
|
if (!wsOk) {
|
|
@@ -869,20 +926,26 @@ app.post('/api/sessions/:id/inject', (req, res) => {
|
|
|
869
926
|
setTimeout(() => {
|
|
870
927
|
let submitted = false;
|
|
871
928
|
|
|
872
|
-
// 1. cmux backend:
|
|
873
|
-
if (
|
|
874
|
-
submitted =
|
|
929
|
+
// 1. cmux backend: send-key return to surface (daemon-level auto-detect)
|
|
930
|
+
if (DETECTED_TERMINAL === 'cmux') {
|
|
931
|
+
submitted = terminalBackend.cmuxSendEnter(id);
|
|
875
932
|
if (submitted) console.log(`[INJECT] cmux submit for ${id}`);
|
|
876
933
|
}
|
|
877
934
|
|
|
878
|
-
// 2.
|
|
935
|
+
// 2. Session-level cmux (registered backend)
|
|
936
|
+
if (!submitted && session.backend === 'cmux' && session.cmuxWorkspaceId) {
|
|
937
|
+
submitted = submitViaCmux(id);
|
|
938
|
+
if (submitted) console.log(`[INJECT] cmux session-level submit for ${id}`);
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
// 3. kitty/default: osascript primary
|
|
879
942
|
if (!submitted) {
|
|
880
943
|
const submitStrategy = getSubmitStrategy(session.command);
|
|
881
944
|
const keyCombo = submitStrategy === 'osascript_cmd_enter' ? 'cmd_enter' : 'return_key';
|
|
882
945
|
submitted = submitViaOsascript(id, keyCombo);
|
|
883
946
|
}
|
|
884
947
|
|
|
885
|
-
//
|
|
948
|
+
// 4. Fallback: kitty send-text → WS
|
|
886
949
|
if (!submitted) {
|
|
887
950
|
console.log(`[INJECT] submit fallback for ${id}`);
|
|
888
951
|
if (wid && sock) {
|
|
@@ -907,7 +970,7 @@ app.post('/api/sessions/:id/inject', (req, res) => {
|
|
|
907
970
|
} catch {}
|
|
908
971
|
}
|
|
909
972
|
}, 500);
|
|
910
|
-
submitResult = { deferred: true, strategy: session.backend === 'cmux' ? 'cmux_with_fallback' : 'osascript_with_fallback' };
|
|
973
|
+
submitResult = { deferred: true, strategy: DETECTED_TERMINAL === 'cmux' ? 'cmux_auto' : (session.backend === 'cmux' ? 'cmux_with_fallback' : 'osascript_with_fallback') };
|
|
911
974
|
}
|
|
912
975
|
} else {
|
|
913
976
|
// Spawned sessions: direct PTY write
|
|
@@ -1060,13 +1123,22 @@ function busAutoRoute(msg) {
|
|
|
1060
1123
|
const prompt = (msg.payload && msg.payload.prompt) || msg.content || msg.prompt || JSON.stringify(msg);
|
|
1061
1124
|
const inject_id = crypto.randomUUID();
|
|
1062
1125
|
|
|
1063
|
-
// Write to session (kitty
|
|
1126
|
+
// Write to session (cmux auto-detect > kitty > session-level cmux > WS fallback)
|
|
1064
1127
|
const sock = findKittySocket();
|
|
1065
1128
|
if (!targetSession.kittyWindowId && sock) targetSession.kittyWindowId = findKittyWindowId(sock, targetId);
|
|
1066
1129
|
const wid = targetSession.kittyWindowId;
|
|
1067
1130
|
let delivered = false;
|
|
1068
1131
|
|
|
1069
|
-
|
|
1132
|
+
// cmux backend: send text + enter to surface (daemon-level auto-detect)
|
|
1133
|
+
if (!delivered && DETECTED_TERMINAL === 'cmux') {
|
|
1134
|
+
const textOk = terminalBackend.cmuxSendText(targetId, prompt);
|
|
1135
|
+
if (textOk) {
|
|
1136
|
+
setTimeout(() => terminalBackend.cmuxSendEnter(targetId), 500);
|
|
1137
|
+
delivered = true;
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
if (!delivered && wid && sock && targetSession.type === 'wrapped') {
|
|
1070
1142
|
try {
|
|
1071
1143
|
const escaped = prompt.replace(/\\/g, '\\\\').replace(/'/g, "'\\''");
|
|
1072
1144
|
require('child_process').execSync(`kitty @ --to unix:${sock} send-text --match id:${wid} '${escaped}'`, {
|
|
@@ -1082,7 +1154,7 @@ function busAutoRoute(msg) {
|
|
|
1082
1154
|
delivered = true;
|
|
1083
1155
|
} catch {}
|
|
1084
1156
|
}
|
|
1085
|
-
// cmux backend: use WS for text, cmux send-key for enter
|
|
1157
|
+
// Session-level cmux backend: use WS for text, cmux send-key for enter
|
|
1086
1158
|
if (!delivered && targetSession.backend === 'cmux' && targetSession.cmuxWorkspaceId) {
|
|
1087
1159
|
if (targetSession.type === 'wrapped' && targetSession.ownerWs && targetSession.ownerWs.readyState === 1) {
|
|
1088
1160
|
targetSession.ownerWs.send(JSON.stringify({ type: 'inject', data: prompt }));
|
package/package.json
CHANGED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { execSync } = require('child_process');
|
|
4
|
+
|
|
5
|
+
// Detect terminal environment at daemon level
|
|
6
|
+
function detectTerminal() {
|
|
7
|
+
// 1. cmux: check env var or cmux ping
|
|
8
|
+
if (process.env.CMUX_WORKSPACE_ID) {
|
|
9
|
+
try {
|
|
10
|
+
execSync('cmux ping', { timeout: 2000, stdio: ['pipe', 'pipe', 'pipe'] });
|
|
11
|
+
return 'cmux';
|
|
12
|
+
} catch {}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// 2. kitty: check for socket
|
|
16
|
+
try {
|
|
17
|
+
const files = require('fs').readdirSync('/tmp').filter(f => f.startsWith('kitty-sock'));
|
|
18
|
+
if (files.length > 0) return 'kitty';
|
|
19
|
+
} catch {}
|
|
20
|
+
|
|
21
|
+
// 3. headless fallback
|
|
22
|
+
return 'headless';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Cache: sessionId -> surfaceRef
|
|
26
|
+
const surfaceCache = new Map();
|
|
27
|
+
let lastCacheRefresh = 0;
|
|
28
|
+
const CACHE_TTL = 30000; // 30 seconds
|
|
29
|
+
|
|
30
|
+
// Build session -> cmux surface mapping from tab titles
|
|
31
|
+
function refreshSurfaceCache() {
|
|
32
|
+
const now = Date.now();
|
|
33
|
+
if (now - lastCacheRefresh < CACHE_TTL && surfaceCache.size > 0) return;
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
// Find number of workspaces from list-windows
|
|
37
|
+
const windowsOutput = execSync('cmux list-windows', { timeout: 5000, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] });
|
|
38
|
+
const workspacesMatch = windowsOutput.match(/workspaces=(\d+)/);
|
|
39
|
+
const workspaceCount = workspacesMatch ? parseInt(workspacesMatch[1]) : 10;
|
|
40
|
+
|
|
41
|
+
surfaceCache.clear();
|
|
42
|
+
for (let i = 1; i <= workspaceCount; i++) {
|
|
43
|
+
try {
|
|
44
|
+
const output = execSync(`cmux list-pane-surfaces --workspace workspace:${i}`, {
|
|
45
|
+
timeout: 3000, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe']
|
|
46
|
+
});
|
|
47
|
+
// Parse: "* surface:1 ⚡ telepty :: aigentry-orchestrator-claude [selected]"
|
|
48
|
+
const lines = output.split('\n').filter(l => l.trim());
|
|
49
|
+
for (const line of lines) {
|
|
50
|
+
const surfaceMatch = line.match(/surface:(\d+)/);
|
|
51
|
+
const sessionMatch = line.match(/telepty\s*::\s*(\S+)/);
|
|
52
|
+
if (surfaceMatch && sessionMatch) {
|
|
53
|
+
surfaceCache.set(sessionMatch[1], `surface:${surfaceMatch[1]}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
} catch {}
|
|
57
|
+
}
|
|
58
|
+
lastCacheRefresh = now;
|
|
59
|
+
console.log(`[BACKEND] Refreshed cmux surface cache: ${surfaceCache.size} sessions mapped`);
|
|
60
|
+
} catch (err) {
|
|
61
|
+
console.error(`[BACKEND] Failed to refresh surface cache:`, err.message);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Find cmux surface ref for a session
|
|
66
|
+
function findSurface(sessionId) {
|
|
67
|
+
refreshSurfaceCache();
|
|
68
|
+
|
|
69
|
+
// Direct match
|
|
70
|
+
if (surfaceCache.has(sessionId)) return surfaceCache.get(sessionId);
|
|
71
|
+
|
|
72
|
+
// Prefix match (e.g., "aigentry-orchestrator" matches "aigentry-orchestrator-claude")
|
|
73
|
+
for (const [id, ref] of surfaceCache.entries()) {
|
|
74
|
+
if (id.startsWith(sessionId) || sessionId.startsWith(id)) return ref;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Send text to a cmux surface
|
|
81
|
+
function cmuxSendText(sessionId, text) {
|
|
82
|
+
const surface = findSurface(sessionId);
|
|
83
|
+
if (!surface) return false;
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
// Escape single quotes for shell
|
|
87
|
+
const escaped = text.replace(/'/g, "'\\''");
|
|
88
|
+
execSync(`cmux send --surface ${surface} '${escaped}'`, {
|
|
89
|
+
timeout: 5000, stdio: ['pipe', 'pipe', 'pipe']
|
|
90
|
+
});
|
|
91
|
+
console.log(`[BACKEND] cmux send text to ${sessionId} (${surface})`);
|
|
92
|
+
return true;
|
|
93
|
+
} catch (err) {
|
|
94
|
+
console.error(`[BACKEND] cmux send failed for ${sessionId}:`, err.message);
|
|
95
|
+
// Invalidate cache entry
|
|
96
|
+
surfaceCache.delete(sessionId);
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Send enter key to a cmux surface
|
|
102
|
+
function cmuxSendEnter(sessionId) {
|
|
103
|
+
const surface = findSurface(sessionId);
|
|
104
|
+
if (!surface) return false;
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
execSync(`cmux send-key --surface ${surface} return`, {
|
|
108
|
+
timeout: 5000, stdio: ['pipe', 'pipe', 'pipe']
|
|
109
|
+
});
|
|
110
|
+
console.log(`[BACKEND] cmux send-key return to ${sessionId} (${surface})`);
|
|
111
|
+
return true;
|
|
112
|
+
} catch (err) {
|
|
113
|
+
console.error(`[BACKEND] cmux send-key failed for ${sessionId}:`, err.message);
|
|
114
|
+
surfaceCache.delete(sessionId);
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Invalidate cache for a session (e.g., when surface changes)
|
|
120
|
+
function invalidateCache(sessionId) {
|
|
121
|
+
surfaceCache.delete(sessionId);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function clearCache() {
|
|
125
|
+
surfaceCache.clear();
|
|
126
|
+
lastCacheRefresh = 0;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
module.exports = {
|
|
130
|
+
detectTerminal,
|
|
131
|
+
findSurface,
|
|
132
|
+
cmuxSendText,
|
|
133
|
+
cmuxSendEnter,
|
|
134
|
+
refreshSurfaceCache,
|
|
135
|
+
invalidateCache,
|
|
136
|
+
clearCache
|
|
137
|
+
};
|