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.
@@ -330,6 +330,12 @@ var PTYSession = class _PTYSession extends import_events.EventEmitter {
330
330
  // Task completion detection (idle detection when busy)
331
331
  _taskCompleteTimer = null;
332
332
  static TASK_COMPLETE_DEBOUNCE_MS = 1500;
333
+ // Deferred output processing — prevents node-pty's synchronous data
334
+ // delivery from starving the event loop (timers, I/O callbacks, etc.)
335
+ _processScheduled = false;
336
+ // Output buffer cap — prevents unbounded growth during long tasks
337
+ static MAX_OUTPUT_BUFFER = 1e5;
338
+ // 100 KB
333
339
  id;
334
340
  config;
335
341
  get status() {
@@ -672,49 +678,16 @@ var PTYSession = class _PTYSession extends import_events.EventEmitter {
672
678
  this.ptyProcess.onData((data) => {
673
679
  this._lastActivityAt = /* @__PURE__ */ new Date();
674
680
  this.outputBuffer += data;
675
- if (this._status === "busy" || this._status === "authenticating") {
676
- this.resetStallTimer();
681
+ if (this.outputBuffer.length > _PTYSession.MAX_OUTPUT_BUFFER) {
682
+ this.outputBuffer = this.outputBuffer.slice(-_PTYSession.MAX_OUTPUT_BUFFER);
677
683
  }
678
684
  this.emit("output", data);
679
- if ((this._status === "starting" || this._status === "authenticating") && this.adapter.detectReady(this.outputBuffer)) {
680
- this._status = "ready";
681
- this._lastBlockingPromptHash = null;
682
- this.outputBuffer = "";
683
- this.clearStallTimer();
684
- this.emit("ready");
685
- this.logger.info({ sessionId: this.id }, "Session ready");
686
- return;
687
- }
688
- if (this._status === "busy" && this.adapter.detectReady(this.outputBuffer)) {
689
- this.scheduleTaskComplete();
690
- } else {
691
- this.cancelTaskComplete();
692
- }
693
- const blockingPrompt = this.detectAndHandleBlockingPrompt();
694
- if (blockingPrompt) {
695
- return;
696
- }
697
- if (this._status !== "ready" && this._status !== "busy") {
698
- const loginDetection = this.adapter.detectLogin(this.outputBuffer);
699
- if (loginDetection.required && this._status !== "authenticating") {
700
- this._status = "authenticating";
701
- this.clearStallTimer();
702
- this.emit("login_required", loginDetection.instructions, loginDetection.url);
703
- this.logger.warn(
704
- { sessionId: this.id, loginType: loginDetection.type },
705
- "Login required"
706
- );
707
- return;
708
- }
709
- }
710
- const exitDetection = this.adapter.detectExit(this.outputBuffer);
711
- if (exitDetection.exited) {
712
- this._status = "stopped";
713
- this.clearStallTimer();
714
- this.emit("exit", exitDetection.code || 0);
715
- }
716
- if (this._status !== "starting" && this._status !== "authenticating") {
717
- this.tryParseOutput();
685
+ if (!this._processScheduled) {
686
+ this._processScheduled = true;
687
+ setImmediate(() => {
688
+ this._processScheduled = false;
689
+ this.processOutputBuffer();
690
+ });
718
691
  }
719
692
  });
720
693
  this.ptyProcess.onExit(({ exitCode, signal }) => {
@@ -727,6 +700,56 @@ var PTYSession = class _PTYSession extends import_events.EventEmitter {
727
700
  this.emit("exit", exitCode);
728
701
  });
729
702
  }
703
+ /**
704
+ * Process the accumulated output buffer.
705
+ * Called via setImmediate() from the onData handler so that heavy regex
706
+ * work runs in its own event-loop tick, not inside node-pty's native callback.
707
+ */
708
+ processOutputBuffer() {
709
+ if (this._status === "busy" || this._status === "authenticating") {
710
+ this.resetStallTimer();
711
+ }
712
+ if ((this._status === "starting" || this._status === "authenticating") && this.adapter.detectReady(this.outputBuffer)) {
713
+ this._status = "ready";
714
+ this._lastBlockingPromptHash = null;
715
+ this.outputBuffer = "";
716
+ this.clearStallTimer();
717
+ this.emit("ready");
718
+ this.logger.info({ sessionId: this.id }, "Session ready");
719
+ return;
720
+ }
721
+ if (this._status === "busy" && this.adapter.detectReady(this.outputBuffer)) {
722
+ this.scheduleTaskComplete();
723
+ } else {
724
+ this.cancelTaskComplete();
725
+ }
726
+ const blockingPrompt = this.detectAndHandleBlockingPrompt();
727
+ if (blockingPrompt) {
728
+ return;
729
+ }
730
+ if (this._status !== "ready" && this._status !== "busy") {
731
+ const loginDetection = this.adapter.detectLogin(this.outputBuffer);
732
+ if (loginDetection.required && this._status !== "authenticating") {
733
+ this._status = "authenticating";
734
+ this.clearStallTimer();
735
+ this.emit("login_required", loginDetection.instructions, loginDetection.url);
736
+ this.logger.warn(
737
+ { sessionId: this.id, loginType: loginDetection.type },
738
+ "Login required"
739
+ );
740
+ return;
741
+ }
742
+ }
743
+ const exitDetection = this.adapter.detectExit(this.outputBuffer);
744
+ if (exitDetection.exited) {
745
+ this._status = "stopped";
746
+ this.clearStallTimer();
747
+ this.emit("exit", exitDetection.code || 0);
748
+ }
749
+ if (this._status !== "starting" && this._status !== "authenticating") {
750
+ this.tryParseOutput();
751
+ }
752
+ }
730
753
  /**
731
754
  * Detect blocking prompts and handle them with auto-responses or user notification.
732
755
  * Deduplicates emissions - won't re-emit the same blocking prompt repeatedly.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pty-manager",
3
- "version": "1.3.1",
3
+ "version": "1.3.2",
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",