@dmsdc-ai/aigentry-telepty 0.1.71 → 0.1.72

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/cli.js +118 -16
  2. package/package.json +1 -1
package/cli.js CHANGED
@@ -643,6 +643,11 @@ async function main() {
643
643
  allowArgs.splice(idIndex, 2);
644
644
  }
645
645
 
646
+ // Extract --auto-restart flag
647
+ const autoRestartIndex = allowArgs.indexOf('--auto-restart');
648
+ const autoRestart = autoRestartIndex !== -1;
649
+ if (autoRestart) allowArgs.splice(autoRestartIndex, 1);
650
+
646
651
  // Strip optional -- separator for backward compat
647
652
  const sepIndex = allowArgs.indexOf('--');
648
653
  if (sepIndex !== -1) allowArgs.splice(sepIndex, 1);
@@ -708,13 +713,60 @@ async function main() {
708
713
 
709
714
  // Spawn local PTY (preserves isTTY, env, shell config)
710
715
  const pty = require('node-pty');
711
- const child = pty.spawn(command, cmdArgs, {
712
- name: 'xterm-256color',
713
- cols: process.stdout.columns || 80,
714
- rows: process.stdout.rows || 30,
715
- cwd: process.cwd(),
716
- env: { ...process.env, TELEPTY_SESSION_ID: sessionId }
717
- });
716
+ const sessionCwd = process.cwd();
717
+ const sessionEnv = { ...process.env, TELEPTY_SESSION_ID: sessionId };
718
+ let child = null;
719
+ let sessionStartTime = Date.now();
720
+ let crashCount = 0;
721
+ const MAX_CRASHES = 3;
722
+ const DEATH_LOG_PATH = path.join(os.homedir(), '.telepty', 'logs', 'session-deaths.log');
723
+
724
+ function logSessionDeath(exitCode, signal, duration) {
725
+ try {
726
+ fs.mkdirSync(path.dirname(DEATH_LOG_PATH), { recursive: true });
727
+ const entry = `[${new Date().toISOString()}] session=${sessionId} command=${command} exit=${exitCode} signal=${signal || 'none'} duration=${Math.round(duration / 1000)}s crashes=${crashCount}\n`;
728
+ fs.appendFileSync(DEATH_LOG_PATH, entry);
729
+ } catch {}
730
+ }
731
+
732
+ function emitDeathEvent(exitCode, signal, willRestart) {
733
+ if (wsReady && daemonWs && daemonWs.readyState === 1) {
734
+ daemonWs.send(JSON.stringify({
735
+ type: 'session_died',
736
+ session_id: sessionId,
737
+ exitCode, signal: signal || null,
738
+ duration: Date.now() - sessionStartTime,
739
+ crashCount,
740
+ willRestart,
741
+ timestamp: new Date().toISOString()
742
+ }));
743
+ }
744
+ }
745
+
746
+ function emitRestartEvent(attempt) {
747
+ if (wsReady && daemonWs && daemonWs.readyState === 1) {
748
+ daemonWs.send(JSON.stringify({
749
+ type: 'session_restarted',
750
+ session_id: sessionId,
751
+ attempt,
752
+ timestamp: new Date().toISOString()
753
+ }));
754
+ }
755
+ }
756
+
757
+ function spawnChild() {
758
+ child = pty.spawn(command, cmdArgs, {
759
+ name: 'xterm-256color',
760
+ cols: process.stdout.columns || 80,
761
+ rows: process.stdout.rows || 30,
762
+ cwd: sessionCwd,
763
+ env: sessionEnv
764
+ });
765
+ sessionStartTime = Date.now();
766
+ return child;
767
+ }
768
+
769
+ spawnChild();
718
770
 
719
771
  // Prompt-ready detection for safe inject delivery
720
772
  const PROMPT_PATTERNS = {
@@ -939,14 +991,64 @@ async function main() {
939
991
  }
940
992
  });
941
993
 
942
- // Handle child exit
943
- child.onExit(({ exitCode }) => {
944
- if (!closeAllowSession()) {
945
- return;
946
- }
947
- console.log(`\n\x1b[33mSession '${sessionId}' exited (code ${exitCode}).\x1b[0m`);
948
- exitAllowSession(exitCode || 0);
949
- });
994
+ // Handle child exit with death tracking + auto-restart
995
+ function attachChildExitHandler() {
996
+ child.onExit(({ exitCode, signal }) => {
997
+ const duration = Date.now() - sessionStartTime;
998
+ const isAbnormal = exitCode !== 0 || signal;
999
+ const durationStr = duration > 60000 ? `${Math.round(duration / 60000)}m ${Math.round((duration % 60000) / 1000)}s` : `${Math.round(duration / 1000)}s`;
1000
+
1001
+ logSessionDeath(exitCode, signal, duration);
1002
+
1003
+ if (isAbnormal && autoRestart && crashCount < MAX_CRASHES) {
1004
+ crashCount++;
1005
+ const backoffMs = Math.min(1000 * Math.pow(2, crashCount - 1), 8000);
1006
+ const willRestart = true;
1007
+ emitDeathEvent(exitCode, signal, willRestart);
1008
+ console.log(`\n\x1b[33m⚠️ Session '${sessionId}' died (code ${exitCode}, signal ${signal || 'none'}, ${durationStr}). Restarting in ${backoffMs}ms (attempt ${crashCount}/${MAX_CRASHES})...\x1b[0m`);
1009
+
1010
+ setTimeout(() => {
1011
+ try {
1012
+ spawnChild();
1013
+ // Re-attach output relay, prompt detection, and exit handler
1014
+ child.onData((data) => {
1015
+ const rewritten = rewriteTitleSequences(data);
1016
+ process.stdout.write(rewritten);
1017
+ if (wsReady && daemonWs.readyState === 1) {
1018
+ daemonWs.send(JSON.stringify({ type: 'output', data }));
1019
+ }
1020
+ if (promptPattern.test(data)) {
1021
+ promptReady = true;
1022
+ flushInjectQueue();
1023
+ if (wsReady && daemonWs.readyState === 1) {
1024
+ daemonWs.send(JSON.stringify({ type: 'ready' }));
1025
+ }
1026
+ }
1027
+ });
1028
+ attachChildExitHandler();
1029
+ emitRestartEvent(crashCount);
1030
+ console.log(`\x1b[32m✅ Session '${sessionId}' restarted (attempt ${crashCount}).\x1b[0m\n`);
1031
+ // Reset crash counter if session survives 30s
1032
+ setTimeout(() => { if (crashCount > 0) crashCount = 0; }, 30000);
1033
+ } catch (err) {
1034
+ console.error(`\x1b[31m❌ Failed to restart session '${sessionId}': ${err.message}\x1b[0m`);
1035
+ emitDeathEvent(exitCode, signal, false);
1036
+ if (!closeAllowSession()) return;
1037
+ exitAllowSession(exitCode || 1);
1038
+ }
1039
+ }, backoffMs);
1040
+ } else {
1041
+ if (isAbnormal && autoRestart && crashCount >= MAX_CRASHES) {
1042
+ console.log(`\n\x1b[31m❌ Session '${sessionId}' crashed ${MAX_CRASHES} times. Giving up.\x1b[0m`);
1043
+ }
1044
+ emitDeathEvent(exitCode, signal, false);
1045
+ if (!closeAllowSession()) return;
1046
+ console.log(`\n\x1b[33mSession '${sessionId}' exited (code ${exitCode}${signal ? ', signal ' + signal : ''}, ${durationStr}).\x1b[0m`);
1047
+ exitAllowSession(exitCode || 0);
1048
+ }
1049
+ });
1050
+ }
1051
+ attachChildExitHandler();
950
1052
 
951
1053
  for (const signalName of ['SIGTERM', 'SIGHUP', 'SIGQUIT']) {
952
1054
  const handler = () => {
@@ -2115,7 +2217,7 @@ Discuss the following topic from your project's perspective. Engage with other s
2115
2217
  Usage:
2116
2218
  telepty daemon Start the background daemon
2117
2219
  telepty spawn --id <id> <command> [args...] Spawn a new background CLI
2118
- telepty allow [--id <id>] <command> [args...] Allow inject on a CLI
2220
+ telepty allow [--id <id>] [--auto-restart] <command> [args...] Allow inject on a CLI (auto-restart on crash)
2119
2221
  telepty list List all active sessions across discovered hosts
2120
2222
  telepty attach [id[@host]] Attach to a session (Interactive picker if no ID)
2121
2223
  telepty inject [--no-enter] [--from <id>] [--reply-to <id>] <id[@host]> "<prompt>" Inject text into a single session
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dmsdc-ai/aigentry-telepty",
3
- "version": "0.1.71",
3
+ "version": "0.1.72",
4
4
  "main": "daemon.js",
5
5
  "bin": {
6
6
  "aigentry-telepty": "install.js",