pty-manager 1.3.1 → 1.3.2

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/dist/index.mjs CHANGED
@@ -309,6 +309,12 @@ var PTYSession = class _PTYSession extends EventEmitter {
309
309
  // Task completion detection (idle detection when busy)
310
310
  _taskCompleteTimer = null;
311
311
  static TASK_COMPLETE_DEBOUNCE_MS = 1500;
312
+ // Deferred output processing — prevents node-pty's synchronous data
313
+ // delivery from starving the event loop (timers, I/O callbacks, etc.)
314
+ _processScheduled = false;
315
+ // Output buffer cap — prevents unbounded growth during long tasks
316
+ static MAX_OUTPUT_BUFFER = 1e5;
317
+ // 100 KB
312
318
  id;
313
319
  config;
314
320
  get status() {
@@ -651,49 +657,16 @@ var PTYSession = class _PTYSession extends EventEmitter {
651
657
  this.ptyProcess.onData((data) => {
652
658
  this._lastActivityAt = /* @__PURE__ */ new Date();
653
659
  this.outputBuffer += data;
654
- if (this._status === "busy" || this._status === "authenticating") {
655
- this.resetStallTimer();
660
+ if (this.outputBuffer.length > _PTYSession.MAX_OUTPUT_BUFFER) {
661
+ this.outputBuffer = this.outputBuffer.slice(-_PTYSession.MAX_OUTPUT_BUFFER);
656
662
  }
657
663
  this.emit("output", data);
658
- if ((this._status === "starting" || this._status === "authenticating") && this.adapter.detectReady(this.outputBuffer)) {
659
- this._status = "ready";
660
- this._lastBlockingPromptHash = null;
661
- this.outputBuffer = "";
662
- this.clearStallTimer();
663
- this.emit("ready");
664
- this.logger.info({ sessionId: this.id }, "Session ready");
665
- return;
666
- }
667
- if (this._status === "busy" && this.adapter.detectReady(this.outputBuffer)) {
668
- this.scheduleTaskComplete();
669
- } else {
670
- this.cancelTaskComplete();
671
- }
672
- const blockingPrompt = this.detectAndHandleBlockingPrompt();
673
- if (blockingPrompt) {
674
- return;
675
- }
676
- if (this._status !== "ready" && this._status !== "busy") {
677
- const loginDetection = this.adapter.detectLogin(this.outputBuffer);
678
- if (loginDetection.required && this._status !== "authenticating") {
679
- this._status = "authenticating";
680
- this.clearStallTimer();
681
- this.emit("login_required", loginDetection.instructions, loginDetection.url);
682
- this.logger.warn(
683
- { sessionId: this.id, loginType: loginDetection.type },
684
- "Login required"
685
- );
686
- return;
687
- }
688
- }
689
- const exitDetection = this.adapter.detectExit(this.outputBuffer);
690
- if (exitDetection.exited) {
691
- this._status = "stopped";
692
- this.clearStallTimer();
693
- this.emit("exit", exitDetection.code || 0);
694
- }
695
- if (this._status !== "starting" && this._status !== "authenticating") {
696
- this.tryParseOutput();
664
+ if (!this._processScheduled) {
665
+ this._processScheduled = true;
666
+ setImmediate(() => {
667
+ this._processScheduled = false;
668
+ this.processOutputBuffer();
669
+ });
697
670
  }
698
671
  });
699
672
  this.ptyProcess.onExit(({ exitCode, signal }) => {
@@ -706,6 +679,56 @@ var PTYSession = class _PTYSession extends EventEmitter {
706
679
  this.emit("exit", exitCode);
707
680
  });
708
681
  }
682
+ /**
683
+ * Process the accumulated output buffer.
684
+ * Called via setImmediate() from the onData handler so that heavy regex
685
+ * work runs in its own event-loop tick, not inside node-pty's native callback.
686
+ */
687
+ processOutputBuffer() {
688
+ if (this._status === "busy" || this._status === "authenticating") {
689
+ this.resetStallTimer();
690
+ }
691
+ if ((this._status === "starting" || this._status === "authenticating") && this.adapter.detectReady(this.outputBuffer)) {
692
+ this._status = "ready";
693
+ this._lastBlockingPromptHash = null;
694
+ this.outputBuffer = "";
695
+ this.clearStallTimer();
696
+ this.emit("ready");
697
+ this.logger.info({ sessionId: this.id }, "Session ready");
698
+ return;
699
+ }
700
+ if (this._status === "busy" && this.adapter.detectReady(this.outputBuffer)) {
701
+ this.scheduleTaskComplete();
702
+ } else {
703
+ this.cancelTaskComplete();
704
+ }
705
+ const blockingPrompt = this.detectAndHandleBlockingPrompt();
706
+ if (blockingPrompt) {
707
+ return;
708
+ }
709
+ if (this._status !== "ready" && this._status !== "busy") {
710
+ const loginDetection = this.adapter.detectLogin(this.outputBuffer);
711
+ if (loginDetection.required && this._status !== "authenticating") {
712
+ this._status = "authenticating";
713
+ this.clearStallTimer();
714
+ this.emit("login_required", loginDetection.instructions, loginDetection.url);
715
+ this.logger.warn(
716
+ { sessionId: this.id, loginType: loginDetection.type },
717
+ "Login required"
718
+ );
719
+ return;
720
+ }
721
+ }
722
+ const exitDetection = this.adapter.detectExit(this.outputBuffer);
723
+ if (exitDetection.exited) {
724
+ this._status = "stopped";
725
+ this.clearStallTimer();
726
+ this.emit("exit", exitDetection.code || 0);
727
+ }
728
+ if (this._status !== "starting" && this._status !== "authenticating") {
729
+ this.tryParseOutput();
730
+ }
731
+ }
709
732
  /**
710
733
  * Detect blocking prompts and handle them with auto-responses or user notification.
711
734
  * Deduplicates emissions - won't re-emit the same blocking prompt repeatedly.