pty-manager 1.2.14 → 1.2.16

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.
@@ -399,11 +399,11 @@ var PTYSession = class extends import_events.EventEmitter {
399
399
  // ─────────────────────────────────────────────────────────────────────────────
400
400
  /**
401
401
  * Start or reset the stall detection timer.
402
- * Only active when status is "busy" and stall detection is enabled.
402
+ * Active when status is "busy" or "authenticating" and stall detection is enabled.
403
403
  */
404
404
  resetStallTimer() {
405
405
  this.clearStallTimer();
406
- if (!this._stallDetectionEnabled || this._status !== "busy") {
406
+ if (!this._stallDetectionEnabled || this._status !== "busy" && this._status !== "authenticating") {
407
407
  return;
408
408
  }
409
409
  this._stallStartedAt = Date.now();
@@ -426,7 +426,7 @@ var PTYSession = class extends import_events.EventEmitter {
426
426
  * Called when the stall timer fires (no output for stallTimeoutMs).
427
427
  */
428
428
  onStallTimerFired() {
429
- if (this._status !== "busy") {
429
+ if (this._status !== "busy" && this._status !== "authenticating") {
430
430
  return;
431
431
  }
432
432
  const tail = this.outputBuffer.slice(-500);
@@ -446,6 +446,12 @@ var PTYSession = class extends import_events.EventEmitter {
446
446
  this.emit("stall_detected", recentOutput, stallDurationMs);
447
447
  this._stallTimer = setTimeout(() => this.onStallTimerFired(), this._stallTimeoutMs);
448
448
  }
449
+ /**
450
+ * Promise-based delay helper.
451
+ */
452
+ delay(ms) {
453
+ return new Promise((resolve) => setTimeout(resolve, ms));
454
+ }
449
455
  /**
450
456
  * Simple string hash for deduplication.
451
457
  */
@@ -471,7 +477,7 @@ var PTYSession = class extends import_events.EventEmitter {
471
477
  * Called by the manager after onStallClassify resolves.
472
478
  */
473
479
  handleStallClassification(classification) {
474
- if (this._status !== "busy") {
480
+ if (this._status !== "busy" && this._status !== "authenticating") {
475
481
  return;
476
482
  }
477
483
  if (!classification || classification.state === "still_working") {
@@ -490,7 +496,13 @@ var PTYSession = class extends import_events.EventEmitter {
490
496
  { sessionId: this.id, response: classification.suggestedResponse },
491
497
  "Auto-responding to stall-classified prompt"
492
498
  );
493
- this.writeRaw(classification.suggestedResponse + "\r");
499
+ const resp = classification.suggestedResponse;
500
+ if (resp.startsWith("keys:")) {
501
+ const keys = resp.slice(5).split(",").map((k) => k.trim());
502
+ this.sendKeySequence(keys);
503
+ } else {
504
+ this.writeRaw(resp + "\r");
505
+ }
494
506
  this.emit("blocking_prompt", promptInfo, true);
495
507
  } else {
496
508
  this.emit("blocking_prompt", promptInfo, false);
@@ -569,10 +581,14 @@ var PTYSession = class extends import_events.EventEmitter {
569
581
  this.ptyProcess.onData((data) => {
570
582
  this._lastActivityAt = /* @__PURE__ */ new Date();
571
583
  this.outputBuffer += data;
572
- if (this._status === "busy") {
584
+ if (this._status === "busy" || this._status === "authenticating") {
573
585
  this.resetStallTimer();
574
586
  }
575
587
  this.emit("output", data);
588
+ const blockingPrompt = this.detectAndHandleBlockingPrompt();
589
+ if (blockingPrompt) {
590
+ return;
591
+ }
576
592
  if ((this._status === "starting" || this._status === "authenticating") && this.adapter.detectReady(this.outputBuffer)) {
577
593
  this._status = "ready";
578
594
  this._lastBlockingPromptHash = null;
@@ -582,10 +598,6 @@ var PTYSession = class extends import_events.EventEmitter {
582
598
  this.logger.info({ sessionId: this.id }, "Session ready");
583
599
  return;
584
600
  }
585
- const blockingPrompt = this.detectAndHandleBlockingPrompt();
586
- if (blockingPrompt) {
587
- return;
588
- }
589
601
  if (this._status !== "ready" && this._status !== "busy") {
590
602
  const loginDetection = this.adapter.detectLogin(this.outputBuffer);
591
603
  if (loginDetection.required && this._status !== "authenticating") {
@@ -653,7 +665,13 @@ var PTYSession = class extends import_events.EventEmitter {
653
665
  },
654
666
  "Auto-responding to blocking prompt"
655
667
  );
656
- this.writeRaw(detection.suggestedResponse + "\r");
668
+ const resp = detection.suggestedResponse;
669
+ if (resp.startsWith("keys:")) {
670
+ const keys = resp.slice(5).split(",").map((k) => k.trim());
671
+ this.sendKeySequence(keys);
672
+ } else {
673
+ this.writeRaw(resp + "\r");
674
+ }
657
675
  this._lastBlockingPromptHash = null;
658
676
  this.emit("blocking_prompt", promptInfo, true);
659
677
  return true;
@@ -705,7 +723,15 @@ var PTYSession = class extends import_events.EventEmitter {
705
723
  },
706
724
  "Applying auto-response rule"
707
725
  );
708
- this.writeRaw(rule.response + "\r");
726
+ const useKeys = rule.keys && rule.keys.length > 0;
727
+ const isTuiDefault = !rule.responseType && !rule.keys && this.adapter.usesTuiMenus;
728
+ if (useKeys) {
729
+ this.sendKeySequence(rule.keys);
730
+ } else if (isTuiDefault) {
731
+ this.sendKeys("enter");
732
+ } else {
733
+ this.writeRaw(rule.response + "\r");
734
+ }
709
735
  this.outputBuffer = this.outputBuffer.replace(rule.pattern, "");
710
736
  const promptInfo = {
711
737
  type: rule.type,
@@ -835,6 +861,26 @@ var PTYSession = class extends import_events.EventEmitter {
835
861
  }
836
862
  }
837
863
  }
864
+ /**
865
+ * Select a TUI menu option by index (0-based).
866
+ * Sends Down arrow `optionIndex` times, then Enter, with 50ms delays.
867
+ */
868
+ async selectMenuOption(optionIndex) {
869
+ for (let i = 0; i < optionIndex; i++) {
870
+ this.sendKeys("down");
871
+ await this.delay(50);
872
+ }
873
+ this.sendKeys("enter");
874
+ }
875
+ /**
876
+ * Send a sequence of keys with staggered timing.
877
+ * Each key is sent 50ms apart using setTimeout to keep the caller synchronous.
878
+ */
879
+ sendKeySequence(keys) {
880
+ keys.forEach((key, i) => {
881
+ setTimeout(() => this.sendKeys(key), i * 50);
882
+ });
883
+ }
838
884
  /**
839
885
  * Paste text using bracketed paste mode
840
886
  *
@@ -1552,6 +1598,8 @@ function deserializeRule(serialized) {
1552
1598
  pattern: new RegExp(serialized.pattern, serialized.flags || ""),
1553
1599
  type: serialized.type,
1554
1600
  response: serialized.response,
1601
+ responseType: serialized.responseType,
1602
+ keys: serialized.keys,
1555
1603
  description: serialized.description,
1556
1604
  safe: serialized.safe
1557
1605
  };
@@ -1562,6 +1610,8 @@ function serializeRule(rule) {
1562
1610
  flags: rule.pattern.flags || void 0,
1563
1611
  type: rule.type,
1564
1612
  response: rule.response,
1613
+ responseType: rule.responseType,
1614
+ keys: rule.keys,
1565
1615
  description: rule.description,
1566
1616
  safe: rule.safe
1567
1617
  };
@@ -1618,6 +1668,14 @@ function handleConfigureStallDetection(enabled, timeoutMs) {
1618
1668
  ack("configureStallDetection", void 0, false, err instanceof Error ? err.message : String(err));
1619
1669
  }
1620
1670
  }
1671
+ function handleSelectMenuOption(id, optionIndex) {
1672
+ const session = manager.getSession(id);
1673
+ if (!session) {
1674
+ ack("selectMenuOption", id, false, `Session ${id} not found`);
1675
+ return;
1676
+ }
1677
+ session.selectMenuOption(optionIndex).then(() => ack("selectMenuOption", id, true)).catch((err) => ack("selectMenuOption", id, false, err instanceof Error ? err.message : String(err)));
1678
+ }
1621
1679
  function handleClassifyStallResult(id, classification) {
1622
1680
  try {
1623
1681
  const session = manager.getSession(id);
@@ -1730,6 +1788,13 @@ function processCommand(line) {
1730
1788
  }
1731
1789
  handleClearRules(command.id);
1732
1790
  break;
1791
+ case "selectMenuOption":
1792
+ if (!command.id || command.optionIndex === void 0) {
1793
+ ack("selectMenuOption", command.id, false, "Missing id or optionIndex");
1794
+ return;
1795
+ }
1796
+ handleSelectMenuOption(command.id, command.optionIndex);
1797
+ break;
1733
1798
  case "configureStallDetection":
1734
1799
  handleConfigureStallDetection(command.enabled ?? false, command.timeoutMs);
1735
1800
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pty-manager",
3
- "version": "1.2.14",
3
+ "version": "1.2.16",
4
4
  "description": "PTY session manager with lifecycle management, pluggable adapters, and blocking prompt detection",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -24,18 +24,6 @@
24
24
  "README.md",
25
25
  "LICENSE"
26
26
  ],
27
- "scripts": {
28
- "postinstall": "node scripts/postinstall.js",
29
- "build": "tsup",
30
- "dev": "tsup --watch",
31
- "test": "vitest",
32
- "test:run": "vitest run",
33
- "test:coverage": "vitest --coverage",
34
- "test:integration": "tsx test-integration.ts",
35
- "type-check": "tsc --noEmit",
36
- "clean": "rm -rf dist",
37
- "prepublishOnly": "pnpm run build"
38
- },
39
27
  "keywords": [
40
28
  "pty",
41
29
  "terminal",
@@ -73,5 +61,16 @@
73
61
  },
74
62
  "engines": {
75
63
  "node": ">=18"
64
+ },
65
+ "scripts": {
66
+ "postinstall": "node scripts/postinstall.js",
67
+ "build": "tsup",
68
+ "dev": "tsup --watch",
69
+ "test": "vitest",
70
+ "test:run": "vitest run",
71
+ "test:coverage": "vitest --coverage",
72
+ "test:integration": "tsx test-integration.ts",
73
+ "type-check": "tsc --noEmit",
74
+ "clean": "rm -rf dist"
76
75
  }
77
- }
76
+ }