@dmsdc-ai/aigentry-telepty 0.1.13 → 0.1.15

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 +159 -29
  2. package/package.json +1 -1
package/cli.js CHANGED
@@ -15,6 +15,89 @@ const { getRuntimeInfo } = require('./runtime-info');
15
15
  const { formatHostLabel, groupSessionsByHost, pickSessionTarget } = require('./session-routing');
16
16
  const { runInteractiveSkillInstaller } = require('./skill-installer');
17
17
  const args = process.argv.slice(2);
18
+ let pendingTerminalInputError = null;
19
+ let simulatedPromptErrorInjected = false;
20
+
21
+ function isRecoverableTerminalInputError(error) {
22
+ return Boolean(error && (error.code === 'EIO' || error.syscall === 'read'));
23
+ }
24
+
25
+ function rememberTerminalInputError(error) {
26
+ pendingTerminalInputError = error;
27
+ }
28
+
29
+ function consumeTerminalInputError() {
30
+ if (!pendingTerminalInputError) {
31
+ return null;
32
+ }
33
+
34
+ const error = pendingTerminalInputError;
35
+ pendingTerminalInputError = null;
36
+ return error;
37
+ }
38
+
39
+ function resetInteractiveInput(stream = process.stdin) {
40
+ if (!stream) {
41
+ return;
42
+ }
43
+
44
+ if (stream.isTTY && typeof stream.setRawMode === 'function') {
45
+ try {
46
+ stream.setRawMode(false);
47
+ } catch {
48
+ // Ignore raw-mode reset failures when the TTY is already gone.
49
+ }
50
+ }
51
+
52
+ if (typeof stream.pause === 'function') {
53
+ stream.pause();
54
+ }
55
+
56
+ if (typeof stream.resume === 'function') {
57
+ stream.resume();
58
+ }
59
+ }
60
+
61
+ function handleTerminalInputError(error, options = {}) {
62
+ if (!isRecoverableTerminalInputError(error)) {
63
+ return false;
64
+ }
65
+
66
+ rememberTerminalInputError(error);
67
+ resetInteractiveInput(options.stream);
68
+
69
+ if (!options.silent) {
70
+ process.stderr.write('\n\x1b[33m⚠️ Terminal input was interrupted. Returning to the telepty menu...\x1b[0m\n');
71
+ }
72
+
73
+ return true;
74
+ }
75
+
76
+ const originalCreateInterface = readline.createInterface.bind(readline);
77
+ readline.createInterface = function patchedCreateInterface(...interfaceArgs) {
78
+ const rl = originalCreateInterface(...interfaceArgs);
79
+ rl.on('error', (error) => {
80
+ if (handleTerminalInputError(error, { stream: rl.input, silent: true })) {
81
+ try {
82
+ rl.close();
83
+ } catch {
84
+ // Ignore close failures after a TTY read error.
85
+ }
86
+ return;
87
+ }
88
+
89
+ process.stderr.write(`\n❌ Telepty terminal input error: ${error.message}\n`);
90
+ });
91
+ return rl;
92
+ };
93
+
94
+ process.stdin.on('error', (error) => {
95
+ if (handleTerminalInputError(error, { stream: process.stdin, silent: true })) {
96
+ return;
97
+ }
98
+
99
+ process.stderr.write(`\n❌ Telepty stdin error: ${error.message}\n`);
100
+ });
18
101
 
19
102
  // Check for updates unless explicitly disabled for tests/CI.
20
103
  if (!process.env.NO_UPDATE_NOTIFIER && !process.env.TELEPTY_DISABLE_UPDATE_NOTIFIER) {
@@ -57,23 +140,45 @@ function startDetachedDaemon() {
57
140
  cp.unref();
58
141
  }
59
142
 
60
- function relaunchInteractiveManager() {
61
- return new Promise((resolve, reject) => {
62
- const child = spawn(process.argv[0], [process.argv[1]], {
63
- stdio: 'inherit',
64
- env: {
65
- ...process.env,
66
- NO_UPDATE_NOTIFIER: '1',
67
- TELEPTY_DISABLE_UPDATE_NOTIFIER: '1'
68
- }
69
- });
143
+ function renderInteractiveHeader() {
144
+ const runtimeInfo = getRuntimeInfo(__dirname);
145
+ console.clear();
146
+ console.log('\x1b[36m\x1b[1m⚡ Telepty Agent Manager\x1b[0m\n');
147
+ console.log(`\x1b[90mVersion ${runtimeInfo.version} Updated ${runtimeInfo.updatedAtLabel}\x1b[0m\n`);
148
+ }
70
149
 
71
- child.once('error', reject);
72
- child.once('spawn', () => resolve(child));
73
- });
150
+ async function promptWithRecovery(promptConfig) {
151
+ if (process.env.TELEPTY_TEST_TRIGGER_PROMPT_EIO_ONCE === '1' && !simulatedPromptErrorInjected) {
152
+ simulatedPromptErrorInjected = true;
153
+ rememberTerminalInputError(Object.assign(new Error('simulated terminal EIO'), { code: 'EIO', syscall: 'read' }));
154
+ console.log('\n\x1b[33m⚠️ Terminal input was interrupted. Returning to the telepty menu...\x1b[0m\n');
155
+ return { __teleptyRetry: true };
156
+ }
157
+
158
+ const response = await prompts(promptConfig);
159
+ const terminalError = consumeTerminalInputError();
160
+ if (terminalError) {
161
+ console.log('\n\x1b[33m⚠️ Terminal input was interrupted. Returning to the telepty menu...\x1b[0m\n');
162
+ return { __teleptyRetry: true };
163
+ }
164
+
165
+ return response;
166
+ }
167
+
168
+ function runUpdateInstall() {
169
+ if (process.env.TELEPTY_SKIP_PACKAGE_UPDATE === '1') {
170
+ return;
171
+ }
172
+
173
+ const updateCommand = process.env.TELEPTY_UPDATE_COMMAND || 'npm install -g @dmsdc-ai/aigentry-telepty@latest';
174
+ execSync(updateCommand, { stdio: 'inherit' });
74
175
  }
75
176
 
76
177
  async function repairLocalDaemon(options = {}) {
178
+ if (process.env.TELEPTY_SKIP_DAEMON_REPAIR === '1') {
179
+ return { stopped: 0, failed: 0, meta: null, skipped: true };
180
+ }
181
+
77
182
  const restart = options.restart !== false;
78
183
  const results = cleanupDaemonProcesses();
79
184
 
@@ -243,13 +348,10 @@ async function manageInteractiveAttach(sessionId, targetHost) {
243
348
  }
244
349
 
245
350
  async function manageInteractive() {
246
- const runtimeInfo = getRuntimeInfo(__dirname);
247
- console.clear();
248
- console.log('\x1b[36m\x1b[1m⚡ Telepty Agent Manager\x1b[0m\n');
249
- console.log(`\x1b[90mVersion ${runtimeInfo.version} Updated ${runtimeInfo.updatedAtLabel}\x1b[0m\n`);
351
+ renderInteractiveHeader();
250
352
 
251
353
  while (true) {
252
- const response = await prompts({
354
+ const response = await promptWithRecovery({
253
355
  type: 'select',
254
356
  name: 'action',
255
357
  message: 'What would you like to do?',
@@ -266,15 +368,23 @@ async function manageInteractive() {
266
368
  ]
267
369
  });
268
370
 
371
+ if (response.__teleptyRetry) {
372
+ renderInteractiveHeader();
373
+ continue;
374
+ }
375
+
269
376
  if (response.action === 'update') {
270
377
  console.log('\n\x1b[36m🔄 Updating telepty to the latest version...\x1b[0m');
271
378
  try {
272
- execSync('npm install -g @dmsdc-ai/aigentry-telepty@latest', { stdio: 'inherit' });
379
+ runUpdateInstall();
273
380
  console.log('\n\x1b[32m✅ Update complete! Restarting daemon...\x1b[0m');
274
- await repairLocalDaemon({ restart: true });
275
- console.log('\x1b[36m↻ Reopening telepty...\x1b[0m\n');
276
- await relaunchInteractiveManager();
277
- process.exit(0);
381
+ const repairResult = await repairLocalDaemon({ restart: true });
382
+ if (repairResult.skipped) {
383
+ console.log('\x1b[36m↻ Refreshing telepty without daemon restart...\x1b[0m\n');
384
+ } else {
385
+ console.log('\x1b[36m↻ Returning to telepty...\x1b[0m\n');
386
+ }
387
+ renderInteractiveHeader();
278
388
  } catch (e) {
279
389
  console.error(`\n❌ Update failed: ${e.message}\n`);
280
390
  }
@@ -331,10 +441,15 @@ async function manageInteractive() {
331
441
  }
332
442
 
333
443
  if (response.action === 'spawn') {
334
- const { id, command } = await prompts([
444
+ const spawnResponse = await promptWithRecovery([
335
445
  { type: 'text', name: 'id', message: 'Enter new session ID (e.g. agent-1):', validate: v => v ? true : 'Required' },
336
446
  { type: 'text', name: 'command', message: 'Enter command to run (e.g. bash, zsh, python):', initial: 'bash' }
337
447
  ]);
448
+ if (spawnResponse.__teleptyRetry) {
449
+ renderInteractiveHeader();
450
+ continue;
451
+ }
452
+ const { id, command } = spawnResponse;
338
453
  if (!id || !command) continue;
339
454
 
340
455
  await ensureDaemonRunning();
@@ -361,10 +476,15 @@ async function manageInteractive() {
361
476
  }
362
477
 
363
478
  if (response.action === 'allow') {
364
- const { id, command } = await prompts([
479
+ const allowResponse = await promptWithRecovery([
365
480
  { type: 'text', name: 'id', message: 'Enter session ID (e.g. my-claude):', validate: v => v ? true : 'Required' },
366
481
  { type: 'text', name: 'command', message: 'Enter command to run (e.g. claude, codex, gemini, bash):', initial: 'bash' }
367
482
  ]);
483
+ if (allowResponse.__teleptyRetry) {
484
+ renderInteractiveHeader();
485
+ continue;
486
+ }
487
+ const { id, command } = allowResponse;
368
488
  if (!id || !command) continue;
369
489
 
370
490
  // Delegate to the allow command handler by setting up args and calling main flow
@@ -380,7 +500,7 @@ async function manageInteractive() {
380
500
  console.log('\n❌ No active sessions found to ' + response.action + '.\n');
381
501
  continue;
382
502
  }
383
- const { target } = await prompts({
503
+ const attachOrInjectResponse = await promptWithRecovery({
384
504
  type: 'select',
385
505
  name: 'target',
386
506
  message: `Select a session to ${response.action}:`,
@@ -389,6 +509,11 @@ async function manageInteractive() {
389
509
  value: s
390
510
  }))
391
511
  });
512
+ if (attachOrInjectResponse.__teleptyRetry) {
513
+ renderInteractiveHeader();
514
+ continue;
515
+ }
516
+ const { target } = attachOrInjectResponse;
392
517
 
393
518
  if (!target) continue;
394
519
 
@@ -398,12 +523,17 @@ async function manageInteractive() {
398
523
  }
399
524
 
400
525
  if (response.action === 'inject') {
401
- const { promptText } = await prompts({
526
+ const injectPromptResponse = await promptWithRecovery({
402
527
  type: 'text',
403
528
  name: 'promptText',
404
529
  message: 'Enter text to inject:',
405
530
  validate: v => v ? true : 'Required'
406
531
  });
532
+ if (injectPromptResponse.__teleptyRetry) {
533
+ renderInteractiveHeader();
534
+ continue;
535
+ }
536
+ const { promptText } = injectPromptResponse;
407
537
  if (!promptText) continue;
408
538
  try {
409
539
  const res = await fetchWithAuth(`http://${target.host}:${PORT}/api/sessions/${encodeURIComponent(target.id)}/inject`, {
@@ -429,9 +559,9 @@ async function main() {
429
559
  if (cmd === 'update') {
430
560
  console.log('\x1b[36m🔄 Updating telepty to the latest version...\x1b[0m');
431
561
  try {
432
- execSync('npm install -g @dmsdc-ai/aigentry-telepty@latest', { stdio: 'inherit' });
562
+ runUpdateInstall();
433
563
  console.log('\n\x1b[32m✅ Update complete! Restarting daemon...\x1b[0m');
434
- cleanupDaemonProcesses();
564
+ await repairLocalDaemon({ restart: true });
435
565
  console.log('🎉 You are now using the latest version.');
436
566
  } catch (e) {
437
567
  console.error('\n❌ Update failed. Please try running: npm install -g @dmsdc-ai/aigentry-telepty@latest');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dmsdc-ai/aigentry-telepty",
3
- "version": "0.1.13",
3
+ "version": "0.1.15",
4
4
  "main": "daemon.js",
5
5
  "bin": {
6
6
  "aigentry-telepty": "install.js",