@dmsdc-ai/aigentry-telepty 0.1.71 → 0.1.73
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 +118 -16
- package/daemon.js +20 -1
- 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
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
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
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
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...]
|
|
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/daemon.js
CHANGED
|
@@ -1035,6 +1035,7 @@ app.post('/api/sessions/:id/inject', (req, res) => {
|
|
|
1035
1035
|
|
|
1036
1036
|
console.log(`[INJECT] Wrote to session ${id} (inject_id: ${inject_id})`);
|
|
1037
1037
|
|
|
1038
|
+
const injectTimestamp = new Date().toISOString();
|
|
1038
1039
|
const busMsg = JSON.stringify({
|
|
1039
1040
|
type: 'inject_written',
|
|
1040
1041
|
inject_id,
|
|
@@ -1045,12 +1046,30 @@ app.post('/api/sessions/:id/inject', (req, res) => {
|
|
|
1045
1046
|
reply_to: reply_to || null,
|
|
1046
1047
|
thread_id: thread_id || null,
|
|
1047
1048
|
reply_expected: !!reply_expected,
|
|
1048
|
-
timestamp:
|
|
1049
|
+
timestamp: injectTimestamp
|
|
1049
1050
|
});
|
|
1050
1051
|
busClients.forEach(client => {
|
|
1051
1052
|
if (client.readyState === 1) client.send(busMsg);
|
|
1052
1053
|
});
|
|
1053
1054
|
|
|
1055
|
+
// Notify all attached viewers (telepty attach clients) about the inject
|
|
1056
|
+
// This enables aterm and other viewers to show inject events in real-time
|
|
1057
|
+
if (session.clients && session.clients.size > 0) {
|
|
1058
|
+
const viewerMsg = JSON.stringify({
|
|
1059
|
+
type: 'inject_notification',
|
|
1060
|
+
inject_id,
|
|
1061
|
+
session_id: id,
|
|
1062
|
+
from: from || null,
|
|
1063
|
+
content: prompt,
|
|
1064
|
+
timestamp: injectTimestamp
|
|
1065
|
+
});
|
|
1066
|
+
session.clients.forEach(client => {
|
|
1067
|
+
if (client !== session.ownerWs && client.readyState === 1) {
|
|
1068
|
+
client.send(viewerMsg);
|
|
1069
|
+
}
|
|
1070
|
+
});
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1054
1073
|
if (requestedId !== resolvedId) {
|
|
1055
1074
|
console.log(`[ALIAS] Resolved '${requestedId}' → '${resolvedId}'`);
|
|
1056
1075
|
}
|