pty-manager 1.2.20 → 1.2.22

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.
@@ -289,7 +289,7 @@ var SPECIAL_KEYS = {
289
289
  };
290
290
  var BRACKETED_PASTE_START = "\x1B[200~";
291
291
  var BRACKETED_PASTE_END = "\x1B[201~";
292
- var PTYSession = class extends import_events.EventEmitter {
292
+ var PTYSession = class _PTYSession extends import_events.EventEmitter {
293
293
  constructor(adapter, config, logger, stallDetectionEnabled, defaultStallTimeoutMs) {
294
294
  super();
295
295
  this.adapter = adapter;
@@ -298,6 +298,15 @@ var PTYSession = class extends import_events.EventEmitter {
298
298
  this.logger = logger || consoleLogger;
299
299
  this._stallDetectionEnabled = stallDetectionEnabled ?? false;
300
300
  this._stallTimeoutMs = config.stallTimeoutMs ?? defaultStallTimeoutMs ?? 8e3;
301
+ if (config.ruleOverrides) {
302
+ for (const [key, value] of Object.entries(config.ruleOverrides)) {
303
+ if (value === null) {
304
+ this._disabledRulePatterns.add(key);
305
+ } else {
306
+ this._ruleOverrides.set(key, value);
307
+ }
308
+ }
309
+ }
301
310
  }
302
311
  ptyProcess = null;
303
312
  outputBuffer = "";
@@ -309,6 +318,8 @@ var PTYSession = class extends import_events.EventEmitter {
309
318
  sessionRules = [];
310
319
  _firedOnceRules = /* @__PURE__ */ new Set();
311
320
  _lastBlockingPromptHash = null;
321
+ _ruleOverrides = /* @__PURE__ */ new Map();
322
+ _disabledRulePatterns = /* @__PURE__ */ new Set();
312
323
  // Stall detection
313
324
  _stallTimer = null;
314
325
  _stallTimeoutMs;
@@ -316,6 +327,9 @@ var PTYSession = class extends import_events.EventEmitter {
316
327
  _lastStallHash = null;
317
328
  _stallStartedAt = null;
318
329
  _lastContentHash = null;
330
+ // Task completion detection (idle detection when busy)
331
+ _taskCompleteTimer = null;
332
+ static TASK_COMPLETE_DEBOUNCE_MS = 1500;
319
333
  id;
320
334
  config;
321
335
  get status() {
@@ -491,10 +505,12 @@ var PTYSession = class extends import_events.EventEmitter {
491
505
  * word boundaries — e.g. "Do\x1b[5Cyou" becomes "Do you", not "Doyou".
492
506
  */
493
507
  stripAnsiForStall(str) {
494
- let result = str.replace(/\x1b\[\d*[CDABG]/g, " ");
508
+ let result = str.replace(/\x1b\[\d*[CDABGdEF]/g, " ");
495
509
  result = result.replace(/\x1b\[\d*(?:;\d+)?[Hf]/g, " ");
510
+ result = result.replace(/\x1b\[\d*[JK]/g, " ");
496
511
  result = result.replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, "");
497
- result = result.replace(/[│╭╰╮╯─═╌║╔╗╚╝╠╣╦╩╬┌┐└┘├┤┬┴┼●○❯❮▶◀⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏⣾⣽⣻⢿⡿⣟⣯⣷✻✶✳✢⏺←→↑↓]/g, " ");
512
+ result = result.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, "");
513
+ result = result.replace(/[│╭╰╮╯─═╌║╔╗╚╝╠╣╦╩╬┌┐└┘├┤┬┴┼●○❯❮▶◀⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏⣾⣽⣻⢿⡿⣟⣯⣷✻✶✳✢⏺←→↑↓⬆⬇◆◇▪▫■□▲△▼▽◈⟨⟩⌘⏎⏏⌫⌦⇧⇪⌥]/g, " ");
498
514
  result = result.replace(/ {2,}/g, " ");
499
515
  return result;
500
516
  }
@@ -531,6 +547,7 @@ var PTYSession = class extends import_events.EventEmitter {
531
547
  this.writeRaw(resp + "\r");
532
548
  }
533
549
  this.emit("blocking_prompt", promptInfo, true);
550
+ this.outputBuffer = "";
534
551
  } else {
535
552
  this.emit("blocking_prompt", promptInfo, false);
536
553
  }
@@ -551,6 +568,39 @@ var PTYSession = class extends import_events.EventEmitter {
551
568
  }
552
569
  }
553
570
  // ─────────────────────────────────────────────────────────────────────────────
571
+ // Task Completion Detection
572
+ // ─────────────────────────────────────────────────────────────────────────────
573
+ /**
574
+ * Schedule a task_complete transition after a debounce period.
575
+ * If new non-whitespace output arrives before the timer fires,
576
+ * the timer is cancelled (by cancelTaskComplete in onData).
577
+ */
578
+ scheduleTaskComplete() {
579
+ if (this._taskCompleteTimer) return;
580
+ this._taskCompleteTimer = setTimeout(() => {
581
+ this._taskCompleteTimer = null;
582
+ if (this._status !== "busy") return;
583
+ if (!this.adapter.detectReady(this.outputBuffer)) return;
584
+ this._status = "ready";
585
+ this._lastBlockingPromptHash = null;
586
+ this.outputBuffer = "";
587
+ this.clearStallTimer();
588
+ this.emit("status_changed", "ready");
589
+ this.emit("task_complete");
590
+ this.logger.info({ sessionId: this.id }, "Task complete \u2014 agent returned to idle prompt");
591
+ }, _PTYSession.TASK_COMPLETE_DEBOUNCE_MS);
592
+ }
593
+ /**
594
+ * Cancel a pending task_complete timer (new output arrived that
595
+ * doesn't match the idle prompt, so the agent is still working).
596
+ */
597
+ cancelTaskComplete() {
598
+ if (this._taskCompleteTimer) {
599
+ clearTimeout(this._taskCompleteTimer);
600
+ this._taskCompleteTimer = null;
601
+ }
602
+ }
603
+ // ─────────────────────────────────────────────────────────────────────────────
554
604
  // Lifecycle
555
605
  // ─────────────────────────────────────────────────────────────────────────────
556
606
  /**
@@ -612,10 +662,6 @@ var PTYSession = class extends import_events.EventEmitter {
612
662
  this.resetStallTimer();
613
663
  }
614
664
  this.emit("output", data);
615
- const blockingPrompt = this.detectAndHandleBlockingPrompt();
616
- if (blockingPrompt) {
617
- return;
618
- }
619
665
  if ((this._status === "starting" || this._status === "authenticating") && this.adapter.detectReady(this.outputBuffer)) {
620
666
  this._status = "ready";
621
667
  this._lastBlockingPromptHash = null;
@@ -625,6 +671,15 @@ var PTYSession = class extends import_events.EventEmitter {
625
671
  this.logger.info({ sessionId: this.id }, "Session ready");
626
672
  return;
627
673
  }
674
+ if (this._status === "busy" && this.adapter.detectReady(this.outputBuffer)) {
675
+ this.scheduleTaskComplete();
676
+ } else {
677
+ this.cancelTaskComplete();
678
+ }
679
+ const blockingPrompt = this.detectAndHandleBlockingPrompt();
680
+ if (blockingPrompt) {
681
+ return;
682
+ }
628
683
  if (this._status !== "ready" && this._status !== "busy") {
629
684
  const loginDetection = this.adapter.detectLogin(this.outputBuffer);
630
685
  if (loginDetection.required && this._status !== "authenticating") {
@@ -700,6 +755,7 @@ var PTYSession = class extends import_events.EventEmitter {
700
755
  this.writeRaw(resp + "\r");
701
756
  }
702
757
  this._lastBlockingPromptHash = null;
758
+ this.outputBuffer = "";
703
759
  this.emit("blocking_prompt", promptInfo, true);
704
760
  return true;
705
761
  }
@@ -727,7 +783,10 @@ var PTYSession = class extends import_events.EventEmitter {
727
783
  * Session rules are checked first, then adapter rules.
728
784
  */
729
785
  tryAutoResponse() {
730
- const adapterRules = this.adapter.autoResponseRules || [];
786
+ const adapterRules = (this.adapter.autoResponseRules || []).filter((r) => !this._disabledRulePatterns.has(r.pattern.source)).map((r) => {
787
+ const override = this._ruleOverrides.get(r.pattern.source);
788
+ return override ? { ...r, ...override } : r;
789
+ });
731
790
  const allRules = [...this.sessionRules, ...adapterRules];
732
791
  if (allRules.length === 0) {
733
792
  return false;
@@ -843,6 +902,7 @@ var PTYSession = class extends import_events.EventEmitter {
843
902
  */
844
903
  send(message) {
845
904
  this._status = "busy";
905
+ this.emit("status_changed", "busy");
846
906
  this.resetStallTimer();
847
907
  const msg = {
848
908
  id: `${this.id}-msg-${++this.messageCounter}`,
@@ -953,6 +1013,7 @@ var PTYSession = class extends import_events.EventEmitter {
953
1013
  if (this.ptyProcess) {
954
1014
  this._status = "stopping";
955
1015
  this.clearStallTimer();
1016
+ this.cancelTaskComplete();
956
1017
  this.ptyProcess.kill(signal);
957
1018
  this.logger.info({ sessionId: this.id, signal }, "Killing PTY session");
958
1019
  }
@@ -1106,6 +1167,12 @@ var PTYManager = class extends import_events2.EventEmitter {
1106
1167
  session.on("error", (error) => {
1107
1168
  this.emit("session_error", session.toHandle(), error.message);
1108
1169
  });
1170
+ session.on("status_changed", () => {
1171
+ this.emit("session_status_changed", session.toHandle());
1172
+ });
1173
+ session.on("task_complete", () => {
1174
+ this.emit("task_complete", session.toHandle());
1175
+ });
1109
1176
  session.on("stall_detected", (recentOutput, stallDurationMs) => {
1110
1177
  const handle = session.toHandle();
1111
1178
  this.emit("stall_detected", handle, recentOutput, stallDurationMs);
@@ -1496,6 +1563,19 @@ manager.on("question", (handle, question) => {
1496
1563
  question
1497
1564
  });
1498
1565
  });
1566
+ manager.on("session_status_changed", (handle) => {
1567
+ emit({
1568
+ event: "status_changed",
1569
+ id: handle.id,
1570
+ status: handle.status
1571
+ });
1572
+ });
1573
+ manager.on("task_complete", (handle) => {
1574
+ emit({
1575
+ event: "task_complete",
1576
+ id: handle.id
1577
+ });
1578
+ });
1499
1579
  manager.on("stall_detected", (handle, recentOutput, stallDurationMs) => {
1500
1580
  emit({
1501
1581
  event: "stall_detected",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pty-manager",
3
- "version": "1.2.20",
3
+ "version": "1.2.22",
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",