@dmsdc-ai/aigentry-telepty 0.1.41 → 0.1.43

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.
Files changed (2) hide show
  1. package/daemon.js +66 -17
  2. package/package.json +1 -1
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 list = Object.entries(sessions).map(([id, session]) => ({
256
- id,
257
- type: session.type || 'spawned',
258
- command: session.command,
259
- cwd: session.cwd,
260
- createdAt: session.createdAt,
261
- active_clients: session.clients.size
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;
@@ -644,25 +660,32 @@ app.post('/api/sessions/:id/inject', (req, res) => {
644
660
  }
645
661
 
646
662
  if (!no_enter) {
647
- // Universal split_cr: text first, \r after delay
648
- // Allow bridge 0.1.40+ has 3s queue flush timeout, so \r always gets delivered
663
+ // Split_cr: text first, \r after delay + kitty send-key Return as backup
649
664
  setTimeout(() => {
650
665
  const ok = writeToSession('\r');
651
666
  console.log(`[INJECT+SUBMIT] Split \\r for ${id}: ${ok ? 'success' : 'failed'}`);
652
- // Restore kitty tab title (optional, no-op if not kitty)
667
+ // Kitty send-key Return as backup (handles old allow bridges without queue flush)
653
668
  try {
654
669
  const sock = findKittySocket();
655
670
  if (sock) {
656
671
  if (!session.kittyWindowId) session.kittyWindowId = findKittyWindowId(sock, id);
657
672
  if (session.kittyWindowId) {
658
- require('child_process').execSync(`kitty @ --to unix:${sock} set-tab-title --match id:${session.kittyWindowId} '⚡ telepty :: ${id}'`, {
659
- timeout: 2000, stdio: ['pipe', 'pipe', 'pipe']
660
- });
673
+ const { execSync } = require('child_process');
674
+ setTimeout(() => {
675
+ try {
676
+ execSync(`kitty @ --to unix:${sock} send-key --match id:${session.kittyWindowId} Return`, {
677
+ timeout: 3000, stdio: ['pipe', 'pipe', 'pipe']
678
+ });
679
+ execSync(`kitty @ --to unix:${sock} set-tab-title --match id:${session.kittyWindowId} '⚡ telepty :: ${id}'`, {
680
+ timeout: 2000, stdio: ['pipe', 'pipe', 'pipe']
681
+ });
682
+ } catch {}
683
+ }, 500);
661
684
  }
662
685
  }
663
686
  } catch {}
664
687
  }, 300);
665
- submitResult = { deferred: true, strategy: 'split_cr' };
688
+ submitResult = { deferred: true, strategy: 'split_cr_kitty_backup' };
666
689
  } else {
667
690
  if (!writeToSession(finalPrompt)) {
668
691
  return res.status(503).json({ error: 'Wrap process is not connected' });
@@ -1037,8 +1060,11 @@ const server = app.listen(PORT, HOST, () => {
1037
1060
  console.log(`🚀 aigentry-telepty daemon listening on http://${HOST}:${PORT}`);
1038
1061
  });
1039
1062
 
1063
+ const IDLE_THRESHOLD_SECONDS = 60;
1040
1064
  setInterval(() => {
1065
+ const now = Date.now();
1041
1066
  for (const [id, session] of Object.entries(sessions)) {
1067
+ const idleSeconds = session.lastActivityAt ? Math.floor((now - new Date(session.lastActivityAt).getTime()) / 1000) : null;
1042
1068
  const healthMsg = JSON.stringify({
1043
1069
  type: 'session_health',
1044
1070
  session_id: id,
@@ -1046,13 +1072,34 @@ setInterval(() => {
1046
1072
  alive: session.type === 'wrapped' ? (session.ownerWs && session.ownerWs.readyState === 1) : (session.ptyProcess && !session.ptyProcess.killed),
1047
1073
  pid: session.ptyProcess?.pid || null,
1048
1074
  type: session.type,
1049
- clients: session.clients ? session.clients.size : 0
1075
+ clients: session.clients ? session.clients.size : 0,
1076
+ idleSeconds
1050
1077
  },
1051
1078
  timestamp: new Date().toISOString()
1052
1079
  });
1053
1080
  busClients.forEach(client => {
1054
1081
  if (client.readyState === 1) client.send(healthMsg);
1055
1082
  });
1083
+
1084
+ // Emit session.idle when idle exceeds threshold
1085
+ if (idleSeconds !== null && idleSeconds >= IDLE_THRESHOLD_SECONDS && !session._idleEmitted) {
1086
+ session._idleEmitted = true;
1087
+ const idleMsg = JSON.stringify({
1088
+ type: 'session.idle',
1089
+ session_id: id,
1090
+ idleSeconds,
1091
+ lastActivityAt: session.lastActivityAt,
1092
+ timestamp: new Date().toISOString()
1093
+ });
1094
+ busClients.forEach(client => {
1095
+ if (client.readyState === 1) client.send(idleMsg);
1096
+ });
1097
+ console.log(`[IDLE] Session ${id} idle for ${idleSeconds}s`);
1098
+ }
1099
+ // Reset idle flag when activity resumes
1100
+ if (idleSeconds !== null && idleSeconds < IDLE_THRESHOLD_SECONDS) {
1101
+ session._idleEmitted = false;
1102
+ }
1056
1103
  }
1057
1104
  }, 10000);
1058
1105
 
@@ -1085,6 +1132,7 @@ wss.on('connection', (ws, req) => {
1085
1132
  command: 'wrapped',
1086
1133
  cwd: process.cwd(),
1087
1134
  createdAt: new Date().toISOString(),
1135
+ lastActivityAt: new Date().toISOString(),
1088
1136
  clients: new Set([ws]),
1089
1137
  isClosing: false
1090
1138
  };
@@ -1123,8 +1171,9 @@ wss.on('connection', (ws, req) => {
1123
1171
 
1124
1172
  if (activeSession.type === 'wrapped') {
1125
1173
  if (ws === activeSession.ownerWs) {
1126
- // Owner sending output -> broadcast to other clients
1174
+ // Owner sending output -> broadcast to other clients + update activity
1127
1175
  if (type === 'output') {
1176
+ activeSession.lastActivityAt = new Date().toISOString();
1128
1177
  activeSession.clients.forEach(client => {
1129
1178
  if (client !== ws && client.readyState === 1) {
1130
1179
  client.send(JSON.stringify({ type: 'output', data }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dmsdc-ai/aigentry-telepty",
3
- "version": "0.1.41",
3
+ "version": "0.1.43",
4
4
  "main": "daemon.js",
5
5
  "bin": {
6
6
  "aigentry-telepty": "install.js",