@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 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 backend takes priority
733
- if (session.backend === 'cmux' && session.cmuxWorkspaceId) {
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 backend takes priority
772
- if (session.backend === 'cmux' && session.cmuxWorkspaceId) {
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 kitty send-text (bypasses allow bridge queue)
839
- // then WS as fallback, then kitty send-key Return for enter
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
- if (wid && sock) {
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: use cmux send-key
873
- if (session.backend === 'cmux' && session.cmuxWorkspaceId) {
874
- submitted = submitViaCmux(id);
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. kitty/default backend: osascript primary
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
- // 3. Fallback: kitty send-text → WS
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 primary, WS fallback)
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
- if (wid && sock && targetSession.type === 'wrapped') {
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dmsdc-ai/aigentry-telepty",
3
- "version": "0.1.68",
3
+ "version": "0.1.69",
4
4
  "main": "daemon.js",
5
5
  "bin": {
6
6
  "aigentry-telepty": "install.js",
@@ -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
+ };