pty-manager 1.2.21 → 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;
@@ -327,6 +327,9 @@ var PTYSession = class extends import_events.EventEmitter {
327
327
  _lastStallHash = null;
328
328
  _stallStartedAt = null;
329
329
  _lastContentHash = null;
330
+ // Task completion detection (idle detection when busy)
331
+ _taskCompleteTimer = null;
332
+ static TASK_COMPLETE_DEBOUNCE_MS = 1500;
330
333
  id;
331
334
  config;
332
335
  get status() {
@@ -502,10 +505,12 @@ var PTYSession = class extends import_events.EventEmitter {
502
505
  * word boundaries — e.g. "Do\x1b[5Cyou" becomes "Do you", not "Doyou".
503
506
  */
504
507
  stripAnsiForStall(str) {
505
- let result = str.replace(/\x1b\[\d*[CDABG]/g, " ");
508
+ let result = str.replace(/\x1b\[\d*[CDABGdEF]/g, " ");
506
509
  result = result.replace(/\x1b\[\d*(?:;\d+)?[Hf]/g, " ");
510
+ result = result.replace(/\x1b\[\d*[JK]/g, " ");
507
511
  result = result.replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, "");
508
- result = result.replace(/[│╭╰╮╯─═╌║╔╗╚╝╠╣╦╩╬┌┐└┘├┤┬┴┼●○❯❮▶◀⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏⣾⣽⣻⢿⡿⣟⣯⣷✻✶✳✢⏺←→↑↓]/g, " ");
512
+ result = result.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, "");
513
+ result = result.replace(/[│╭╰╮╯─═╌║╔╗╚╝╠╣╦╩╬┌┐└┘├┤┬┴┼●○❯❮▶◀⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏⣾⣽⣻⢿⡿⣟⣯⣷✻✶✳✢⏺←→↑↓⬆⬇◆◇▪▫■□▲△▼▽◈⟨⟩⌘⏎⏏⌫⌦⇧⇪⌥]/g, " ");
509
514
  result = result.replace(/ {2,}/g, " ");
510
515
  return result;
511
516
  }
@@ -563,6 +568,39 @@ var PTYSession = class extends import_events.EventEmitter {
563
568
  }
564
569
  }
565
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
+ // ─────────────────────────────────────────────────────────────────────────────
566
604
  // Lifecycle
567
605
  // ─────────────────────────────────────────────────────────────────────────────
568
606
  /**
@@ -624,10 +662,6 @@ var PTYSession = class extends import_events.EventEmitter {
624
662
  this.resetStallTimer();
625
663
  }
626
664
  this.emit("output", data);
627
- const blockingPrompt = this.detectAndHandleBlockingPrompt();
628
- if (blockingPrompt) {
629
- return;
630
- }
631
665
  if ((this._status === "starting" || this._status === "authenticating") && this.adapter.detectReady(this.outputBuffer)) {
632
666
  this._status = "ready";
633
667
  this._lastBlockingPromptHash = null;
@@ -637,6 +671,15 @@ var PTYSession = class extends import_events.EventEmitter {
637
671
  this.logger.info({ sessionId: this.id }, "Session ready");
638
672
  return;
639
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
+ }
640
683
  if (this._status !== "ready" && this._status !== "busy") {
641
684
  const loginDetection = this.adapter.detectLogin(this.outputBuffer);
642
685
  if (loginDetection.required && this._status !== "authenticating") {
@@ -859,6 +902,7 @@ var PTYSession = class extends import_events.EventEmitter {
859
902
  */
860
903
  send(message) {
861
904
  this._status = "busy";
905
+ this.emit("status_changed", "busy");
862
906
  this.resetStallTimer();
863
907
  const msg = {
864
908
  id: `${this.id}-msg-${++this.messageCounter}`,
@@ -969,6 +1013,7 @@ var PTYSession = class extends import_events.EventEmitter {
969
1013
  if (this.ptyProcess) {
970
1014
  this._status = "stopping";
971
1015
  this.clearStallTimer();
1016
+ this.cancelTaskComplete();
972
1017
  this.ptyProcess.kill(signal);
973
1018
  this.logger.info({ sessionId: this.id, signal }, "Killing PTY session");
974
1019
  }
@@ -1122,6 +1167,12 @@ var PTYManager = class extends import_events2.EventEmitter {
1122
1167
  session.on("error", (error) => {
1123
1168
  this.emit("session_error", session.toHandle(), error.message);
1124
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
+ });
1125
1176
  session.on("stall_detected", (recentOutput, stallDurationMs) => {
1126
1177
  const handle = session.toHandle();
1127
1178
  this.emit("stall_detected", handle, recentOutput, stallDurationMs);
@@ -1512,6 +1563,19 @@ manager.on("question", (handle, question) => {
1512
1563
  question
1513
1564
  });
1514
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
+ });
1515
1579
  manager.on("stall_detected", (handle, recentOutput, stallDurationMs) => {
1516
1580
  emit({
1517
1581
  event: "stall_detected",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pty-manager",
3
- "version": "1.2.21",
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",