pty-manager 1.2.16 → 1.2.18

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.
@@ -307,6 +307,7 @@ var PTYSession = class extends import_events.EventEmitter {
307
307
  messageCounter = 0;
308
308
  logger;
309
309
  sessionRules = [];
310
+ _firedOnceRules = /* @__PURE__ */ new Set();
310
311
  _lastBlockingPromptHash = null;
311
312
  // Stall detection
312
313
  _stallTimer = null;
@@ -314,6 +315,7 @@ var PTYSession = class extends import_events.EventEmitter {
314
315
  _stallDetectionEnabled;
315
316
  _lastStallHash = null;
316
317
  _stallStartedAt = null;
318
+ _lastContentHash = null;
317
319
  id;
318
320
  config;
319
321
  get status() {
@@ -400,12 +402,28 @@ var PTYSession = class extends import_events.EventEmitter {
400
402
  /**
401
403
  * Start or reset the stall detection timer.
402
404
  * Active when status is "busy" or "authenticating" and stall detection is enabled.
405
+ *
406
+ * Content-based: hashes the ANSI-stripped buffer tail and only resets the
407
+ * timer when visible content actually changes. This prevents TUI spinners
408
+ * (which produce new ANSI sequences but no new visible text) from endlessly
409
+ * resetting the timer.
403
410
  */
404
411
  resetStallTimer() {
405
- this.clearStallTimer();
406
412
  if (!this._stallDetectionEnabled || this._status !== "busy" && this._status !== "authenticating") {
413
+ this.clearStallTimer();
407
414
  return;
408
415
  }
416
+ const tail = this.outputBuffer.slice(-500);
417
+ const stripped = this.stripAnsiForStall(tail).trim();
418
+ const hash = this.simpleHash(stripped);
419
+ if (hash === this._lastContentHash) {
420
+ return;
421
+ }
422
+ this._lastContentHash = hash;
423
+ if (this._stallTimer) {
424
+ clearTimeout(this._stallTimer);
425
+ this._stallTimer = null;
426
+ }
409
427
  this._stallStartedAt = Date.now();
410
428
  this._lastStallHash = null;
411
429
  this._stallTimer = setTimeout(() => {
@@ -421,6 +439,7 @@ var PTYSession = class extends import_events.EventEmitter {
421
439
  this._stallTimer = null;
422
440
  }
423
441
  this._stallStartedAt = null;
442
+ this._lastContentHash = null;
424
443
  }
425
444
  /**
426
445
  * Called when the stall timer fires (no output for stallTimeoutMs).
@@ -465,12 +484,19 @@ var PTYSession = class extends import_events.EventEmitter {
465
484
  return hash.toString(36);
466
485
  }
467
486
  /**
468
- * Strip ANSI codes for stall detection output.
469
- * Replaces cursor-forward sequences with spaces first.
487
+ * Strip ANSI codes, cursor movement, box-drawing, and spinner characters.
488
+ * Used for stall detection hashing and auto-response pattern matching.
489
+ *
490
+ * Cursor movement codes are replaced with spaces (not removed) to preserve
491
+ * word boundaries — e.g. "Do\x1b[5Cyou" becomes "Do you", not "Doyou".
470
492
  */
471
493
  stripAnsiForStall(str) {
472
- const withSpaces = str.replace(/\x1b\[\d*C/g, " ");
473
- return withSpaces.replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, "");
494
+ let result = str.replace(/\x1b\[\d*[CDABG]/g, " ");
495
+ result = result.replace(/\x1b\[\d*(?:;\d+)?[Hf]/g, " ");
496
+ result = result.replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, "");
497
+ result = result.replace(/[│╭╰╮╯─═╌║╔╗╚╝╠╣╦╩╬┌┐└┘├┤┬┴┼●○❯❮▶◀⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏⣾⣽⣻⢿⡿⣟⣯⣷✻✶✳✢⏺←→↑↓]/g, " ");
498
+ result = result.replace(/ {2,}/g, " ");
499
+ return result;
474
500
  }
475
501
  /**
476
502
  * Handle external stall classification result.
@@ -481,6 +507,7 @@ var PTYSession = class extends import_events.EventEmitter {
481
507
  return;
482
508
  }
483
509
  if (!classification || classification.state === "still_working") {
510
+ this._lastContentHash = null;
484
511
  this.resetStallTimer();
485
512
  return;
486
513
  }
@@ -705,10 +732,14 @@ var PTYSession = class extends import_events.EventEmitter {
705
732
  if (allRules.length === 0) {
706
733
  return false;
707
734
  }
708
- let stripped = this.stripAnsiForStall(this.outputBuffer);
709
- stripped = stripped.replace(/[│╭╰╮╯─═╌║╔╗╚╝╠╣╦╩╬┌┐└┘├┤┬┴┼●○❯❮▶◀⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏⏺←→↑↓]/g, " ");
710
- stripped = stripped.replace(/ {2,}/g, " ");
735
+ const stripped = this.stripAnsiForStall(this.outputBuffer);
711
736
  for (const rule of allRules) {
737
+ if (rule.once) {
738
+ const ruleKey = `${rule.pattern.source}:${rule.pattern.flags}`;
739
+ if (this._firedOnceRules.has(ruleKey)) {
740
+ continue;
741
+ }
742
+ }
712
743
  if (rule.pattern.test(stripped)) {
713
744
  const safe = rule.safe !== false;
714
745
  const isSessionRule = this.sessionRules.includes(rule);
@@ -732,6 +763,10 @@ var PTYSession = class extends import_events.EventEmitter {
732
763
  } else {
733
764
  this.writeRaw(rule.response + "\r");
734
765
  }
766
+ if (rule.once) {
767
+ const ruleKey = `${rule.pattern.source}:${rule.pattern.flags}`;
768
+ this._firedOnceRules.add(ruleKey);
769
+ }
735
770
  this.outputBuffer = this.outputBuffer.replace(rule.pattern, "");
736
771
  const promptInfo = {
737
772
  type: rule.type,
@@ -1601,7 +1636,8 @@ function deserializeRule(serialized) {
1601
1636
  responseType: serialized.responseType,
1602
1637
  keys: serialized.keys,
1603
1638
  description: serialized.description,
1604
- safe: serialized.safe
1639
+ safe: serialized.safe,
1640
+ once: serialized.once
1605
1641
  };
1606
1642
  }
1607
1643
  function serializeRule(rule) {
@@ -1613,7 +1649,8 @@ function serializeRule(rule) {
1613
1649
  responseType: rule.responseType,
1614
1650
  keys: rule.keys,
1615
1651
  description: rule.description,
1616
- safe: rule.safe
1652
+ safe: rule.safe,
1653
+ once: rule.once
1617
1654
  };
1618
1655
  }
1619
1656
  function handleAddRule(id, serializedRule) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pty-manager",
3
- "version": "1.2.16",
3
+ "version": "1.2.18",
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",