@dmsdc-ai/aigentry-telepty 0.1.40 → 0.1.42
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 +12 -0
- package/daemon.js +72 -43
- package/package.json +1 -1
package/cli.js
CHANGED
|
@@ -705,7 +705,9 @@ async function main() {
|
|
|
705
705
|
let promptReady = true; // assume ready initially for first inject
|
|
706
706
|
const injectQueue = [];
|
|
707
707
|
|
|
708
|
+
let queueFlushTimer = null;
|
|
708
709
|
function flushInjectQueue() {
|
|
710
|
+
if (queueFlushTimer) { clearTimeout(queueFlushTimer); queueFlushTimer = null; }
|
|
709
711
|
if (injectQueue.length === 0) return;
|
|
710
712
|
const batch = injectQueue.splice(0);
|
|
711
713
|
let delay = 0;
|
|
@@ -715,6 +717,15 @@ async function main() {
|
|
|
715
717
|
}
|
|
716
718
|
promptReady = false;
|
|
717
719
|
}
|
|
720
|
+
function scheduleQueueFlush() {
|
|
721
|
+
if (queueFlushTimer) return;
|
|
722
|
+
queueFlushTimer = setTimeout(() => {
|
|
723
|
+
queueFlushTimer = null;
|
|
724
|
+
if (injectQueue.length > 0) {
|
|
725
|
+
flushInjectQueue();
|
|
726
|
+
}
|
|
727
|
+
}, 3000);
|
|
728
|
+
}
|
|
718
729
|
|
|
719
730
|
// Connect to daemon WebSocket with auto-reconnect
|
|
720
731
|
const wsUrl = `ws://${REMOTE_HOST}:${PORT}/api/sessions/${encodeURIComponent(sessionId)}?token=${encodeURIComponent(TOKEN)}`;
|
|
@@ -767,6 +778,7 @@ async function main() {
|
|
|
767
778
|
}
|
|
768
779
|
} else {
|
|
769
780
|
injectQueue.push(msg.data);
|
|
781
|
+
scheduleQueueFlush();
|
|
770
782
|
}
|
|
771
783
|
} else if (msg.type === 'resize') {
|
|
772
784
|
child.resize(msg.cols, msg.rows);
|
package/daemon.js
CHANGED
|
@@ -143,6 +143,7 @@ app.post('/api/sessions/spawn', (req, res) => {
|
|
|
143
143
|
command,
|
|
144
144
|
cwd,
|
|
145
145
|
createdAt: new Date().toISOString(),
|
|
146
|
+
lastActivityAt: new Date().toISOString(),
|
|
146
147
|
clients: new Set(),
|
|
147
148
|
isClosing: false
|
|
148
149
|
};
|
|
@@ -210,6 +211,7 @@ app.post('/api/sessions/register', (req, res) => {
|
|
|
210
211
|
command: command || 'wrapped',
|
|
211
212
|
cwd,
|
|
212
213
|
createdAt: new Date().toISOString(),
|
|
214
|
+
lastActivityAt: new Date().toISOString(),
|
|
213
215
|
clients: new Set(),
|
|
214
216
|
isClosing: false
|
|
215
217
|
};
|
|
@@ -252,14 +254,24 @@ app.post('/api/sessions/register', (req, res) => {
|
|
|
252
254
|
});
|
|
253
255
|
|
|
254
256
|
app.get('/api/sessions', (req, res) => {
|
|
255
|
-
const
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
257
|
+
const idleGt = req.query.idle_gt ? Number(req.query.idle_gt) : null;
|
|
258
|
+
const now = Date.now();
|
|
259
|
+
let list = Object.entries(sessions).map(([id, session]) => {
|
|
260
|
+
const idleSeconds = session.lastActivityAt ? Math.floor((now - new Date(session.lastActivityAt).getTime()) / 1000) : null;
|
|
261
|
+
return {
|
|
262
|
+
id,
|
|
263
|
+
type: session.type || 'spawned',
|
|
264
|
+
command: session.command,
|
|
265
|
+
cwd: session.cwd,
|
|
266
|
+
createdAt: session.createdAt,
|
|
267
|
+
lastActivityAt: session.lastActivityAt || null,
|
|
268
|
+
idleSeconds,
|
|
269
|
+
active_clients: session.clients.size
|
|
270
|
+
};
|
|
271
|
+
});
|
|
272
|
+
if (idleGt !== null) {
|
|
273
|
+
list = list.filter(s => s.idleSeconds !== null && s.idleSeconds > idleGt);
|
|
274
|
+
}
|
|
263
275
|
res.json(list);
|
|
264
276
|
});
|
|
265
277
|
|
|
@@ -268,6 +280,7 @@ app.get('/api/sessions/:id', (req, res) => {
|
|
|
268
280
|
const resolvedId = resolveSessionAlias(requestedId);
|
|
269
281
|
if (!resolvedId) return res.status(404).json({ error: 'Session not found' });
|
|
270
282
|
const session = sessions[resolvedId];
|
|
283
|
+
const idleSeconds = session.lastActivityAt ? Math.floor((Date.now() - new Date(session.lastActivityAt).getTime()) / 1000) : null;
|
|
271
284
|
res.json({
|
|
272
285
|
id: resolvedId,
|
|
273
286
|
alias: requestedId !== resolvedId ? requestedId : null,
|
|
@@ -275,6 +288,8 @@ app.get('/api/sessions/:id', (req, res) => {
|
|
|
275
288
|
command: session.command,
|
|
276
289
|
cwd: session.cwd,
|
|
277
290
|
createdAt: session.createdAt,
|
|
291
|
+
lastActivityAt: session.lastActivityAt || null,
|
|
292
|
+
idleSeconds,
|
|
278
293
|
active_clients: session.clients ? session.clients.size : 0,
|
|
279
294
|
lastInjectFrom: session.lastInjectFrom || null,
|
|
280
295
|
lastInjectReplyTo: session.lastInjectReplyTo || null
|
|
@@ -611,6 +626,7 @@ app.post('/api/sessions/:id/inject', (req, res) => {
|
|
|
611
626
|
if (from) session.lastInjectFrom = from;
|
|
612
627
|
if (reply_to) session.lastInjectReplyTo = reply_to;
|
|
613
628
|
if (thread_id) session.lastThreadId = thread_id;
|
|
629
|
+
session.lastActivityAt = new Date().toISOString();
|
|
614
630
|
|
|
615
631
|
// Auto-prepend [from:] [reply-to:] header if from is set and not already in prompt
|
|
616
632
|
let finalPrompt = prompt;
|
|
@@ -639,43 +655,30 @@ app.post('/api/sessions/:id/inject', (req, res) => {
|
|
|
639
655
|
}
|
|
640
656
|
|
|
641
657
|
let submitResult = null;
|
|
642
|
-
if (
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
658
|
+
if (!writeToSession(finalPrompt)) {
|
|
659
|
+
return res.status(503).json({ error: 'Wrap process is not connected' });
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
if (!no_enter) {
|
|
663
|
+
// Universal split_cr: text first, \r after delay
|
|
664
|
+
// Allow bridge 0.1.40+ has 3s queue flush timeout, so \r always gets delivered
|
|
647
665
|
setTimeout(() => {
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
if
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
// Restore tab title after inject (Claude Code overwrites it)
|
|
658
|
-
try {
|
|
659
|
-
execSync(`kitty @ --to unix:${findKittySocket()} set-tab-title --match id:${windowId} '⚡ telepty :: ${id}'`, {
|
|
666
|
+
const ok = writeToSession('\r');
|
|
667
|
+
console.log(`[INJECT+SUBMIT] Split \\r for ${id}: ${ok ? 'success' : 'failed'}`);
|
|
668
|
+
// Restore kitty tab title (optional, no-op if not kitty)
|
|
669
|
+
try {
|
|
670
|
+
const sock = findKittySocket();
|
|
671
|
+
if (sock) {
|
|
672
|
+
if (!session.kittyWindowId) session.kittyWindowId = findKittyWindowId(sock, id);
|
|
673
|
+
if (session.kittyWindowId) {
|
|
674
|
+
require('child_process').execSync(`kitty @ --to unix:${sock} set-tab-title --match id:${session.kittyWindowId} '⚡ telepty :: ${id}'`, {
|
|
660
675
|
timeout: 2000, stdio: ['pipe', 'pipe', 'pipe']
|
|
661
676
|
});
|
|
662
|
-
}
|
|
663
|
-
} catch {
|
|
664
|
-
writeToSession('\r');
|
|
665
|
-
console.log(`[INJECT+SUBMIT] WS text + WS \\r fallback for ${id}`);
|
|
677
|
+
}
|
|
666
678
|
}
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
}
|
|
671
|
-
}, 500);
|
|
672
|
-
submitResult = { deferred: true, strategy: 'ws_text_kitty_return' };
|
|
673
|
-
} else if (session.type === 'wrapped') {
|
|
674
|
-
// no_enter=true for wrapped
|
|
675
|
-
if (!writeToSession(finalPrompt)) {
|
|
676
|
-
return res.status(503).json({ error: 'Wrap process is not connected' });
|
|
677
|
-
}
|
|
678
|
-
submitResult = { strategy: 'ws_no_enter' };
|
|
679
|
+
} catch {}
|
|
680
|
+
}, 300);
|
|
681
|
+
submitResult = { deferred: true, strategy: 'split_cr' };
|
|
679
682
|
} else {
|
|
680
683
|
if (!writeToSession(finalPrompt)) {
|
|
681
684
|
return res.status(503).json({ error: 'Wrap process is not connected' });
|
|
@@ -1050,8 +1053,11 @@ const server = app.listen(PORT, HOST, () => {
|
|
|
1050
1053
|
console.log(`🚀 aigentry-telepty daemon listening on http://${HOST}:${PORT}`);
|
|
1051
1054
|
});
|
|
1052
1055
|
|
|
1056
|
+
const IDLE_THRESHOLD_SECONDS = 60;
|
|
1053
1057
|
setInterval(() => {
|
|
1058
|
+
const now = Date.now();
|
|
1054
1059
|
for (const [id, session] of Object.entries(sessions)) {
|
|
1060
|
+
const idleSeconds = session.lastActivityAt ? Math.floor((now - new Date(session.lastActivityAt).getTime()) / 1000) : null;
|
|
1055
1061
|
const healthMsg = JSON.stringify({
|
|
1056
1062
|
type: 'session_health',
|
|
1057
1063
|
session_id: id,
|
|
@@ -1059,13 +1065,34 @@ setInterval(() => {
|
|
|
1059
1065
|
alive: session.type === 'wrapped' ? (session.ownerWs && session.ownerWs.readyState === 1) : (session.ptyProcess && !session.ptyProcess.killed),
|
|
1060
1066
|
pid: session.ptyProcess?.pid || null,
|
|
1061
1067
|
type: session.type,
|
|
1062
|
-
clients: session.clients ? session.clients.size : 0
|
|
1068
|
+
clients: session.clients ? session.clients.size : 0,
|
|
1069
|
+
idleSeconds
|
|
1063
1070
|
},
|
|
1064
1071
|
timestamp: new Date().toISOString()
|
|
1065
1072
|
});
|
|
1066
1073
|
busClients.forEach(client => {
|
|
1067
1074
|
if (client.readyState === 1) client.send(healthMsg);
|
|
1068
1075
|
});
|
|
1076
|
+
|
|
1077
|
+
// Emit session.idle when idle exceeds threshold
|
|
1078
|
+
if (idleSeconds !== null && idleSeconds >= IDLE_THRESHOLD_SECONDS && !session._idleEmitted) {
|
|
1079
|
+
session._idleEmitted = true;
|
|
1080
|
+
const idleMsg = JSON.stringify({
|
|
1081
|
+
type: 'session.idle',
|
|
1082
|
+
session_id: id,
|
|
1083
|
+
idleSeconds,
|
|
1084
|
+
lastActivityAt: session.lastActivityAt,
|
|
1085
|
+
timestamp: new Date().toISOString()
|
|
1086
|
+
});
|
|
1087
|
+
busClients.forEach(client => {
|
|
1088
|
+
if (client.readyState === 1) client.send(idleMsg);
|
|
1089
|
+
});
|
|
1090
|
+
console.log(`[IDLE] Session ${id} idle for ${idleSeconds}s`);
|
|
1091
|
+
}
|
|
1092
|
+
// Reset idle flag when activity resumes
|
|
1093
|
+
if (idleSeconds !== null && idleSeconds < IDLE_THRESHOLD_SECONDS) {
|
|
1094
|
+
session._idleEmitted = false;
|
|
1095
|
+
}
|
|
1069
1096
|
}
|
|
1070
1097
|
}, 10000);
|
|
1071
1098
|
|
|
@@ -1098,6 +1125,7 @@ wss.on('connection', (ws, req) => {
|
|
|
1098
1125
|
command: 'wrapped',
|
|
1099
1126
|
cwd: process.cwd(),
|
|
1100
1127
|
createdAt: new Date().toISOString(),
|
|
1128
|
+
lastActivityAt: new Date().toISOString(),
|
|
1101
1129
|
clients: new Set([ws]),
|
|
1102
1130
|
isClosing: false
|
|
1103
1131
|
};
|
|
@@ -1136,8 +1164,9 @@ wss.on('connection', (ws, req) => {
|
|
|
1136
1164
|
|
|
1137
1165
|
if (activeSession.type === 'wrapped') {
|
|
1138
1166
|
if (ws === activeSession.ownerWs) {
|
|
1139
|
-
// Owner sending output -> broadcast to other clients
|
|
1167
|
+
// Owner sending output -> broadcast to other clients + update activity
|
|
1140
1168
|
if (type === 'output') {
|
|
1169
|
+
activeSession.lastActivityAt = new Date().toISOString();
|
|
1141
1170
|
activeSession.clients.forEach(client => {
|
|
1142
1171
|
if (client !== ws && client.readyState === 1) {
|
|
1143
1172
|
client.send(JSON.stringify({ type: 'output', data }));
|