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.
- package/dist/index.d.mts +24 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +93 -8
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +93 -8
- package/dist/index.mjs.map +1 -1
- package/dist/pty-worker.js +88 -8
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -37,6 +37,11 @@ interface SpawnConfig {
|
|
|
37
37
|
adapterConfig?: Record<string, unknown>;
|
|
38
38
|
/** Per-session stall timeout in ms. Overrides PTYManagerConfig.stallTimeoutMs. */
|
|
39
39
|
stallTimeoutMs?: number;
|
|
40
|
+
/** Override or disable specific adapter auto-response rules for this session.
|
|
41
|
+
* Keys are regex source strings (from rule.pattern.source).
|
|
42
|
+
* - null value disables that rule entirely
|
|
43
|
+
* - Object value merges fields into the matching adapter rule */
|
|
44
|
+
ruleOverrides?: Record<string, Partial<Omit<AutoResponseRule, 'pattern'>> | null>;
|
|
40
45
|
}
|
|
41
46
|
/**
|
|
42
47
|
* Handle to a running session
|
|
@@ -400,6 +405,8 @@ interface PTYSessionEvents {
|
|
|
400
405
|
exit: (code: number) => void;
|
|
401
406
|
error: (error: Error) => void;
|
|
402
407
|
stall_detected: (recentOutput: string, stallDurationMs: number) => void;
|
|
408
|
+
status_changed: (status: SessionStatus) => void;
|
|
409
|
+
task_complete: () => void;
|
|
403
410
|
}
|
|
404
411
|
/**
|
|
405
412
|
* Special key mappings to escape sequences
|
|
@@ -420,12 +427,16 @@ declare class PTYSession extends EventEmitter {
|
|
|
420
427
|
private sessionRules;
|
|
421
428
|
private _firedOnceRules;
|
|
422
429
|
private _lastBlockingPromptHash;
|
|
430
|
+
private _ruleOverrides;
|
|
431
|
+
private _disabledRulePatterns;
|
|
423
432
|
private _stallTimer;
|
|
424
433
|
private _stallTimeoutMs;
|
|
425
434
|
private _stallDetectionEnabled;
|
|
426
435
|
private _lastStallHash;
|
|
427
436
|
private _stallStartedAt;
|
|
428
437
|
private _lastContentHash;
|
|
438
|
+
private _taskCompleteTimer;
|
|
439
|
+
private static readonly TASK_COMPLETE_DEBOUNCE_MS;
|
|
429
440
|
readonly id: string;
|
|
430
441
|
readonly config: SpawnConfig;
|
|
431
442
|
constructor(adapter: CLIAdapter, config: SpawnConfig, logger?: Logger, stallDetectionEnabled?: boolean, defaultStallTimeoutMs?: number);
|
|
@@ -494,6 +505,17 @@ declare class PTYSession extends EventEmitter {
|
|
|
494
505
|
* Called by the manager after onStallClassify resolves.
|
|
495
506
|
*/
|
|
496
507
|
handleStallClassification(classification: StallClassification | null): void;
|
|
508
|
+
/**
|
|
509
|
+
* Schedule a task_complete transition after a debounce period.
|
|
510
|
+
* If new non-whitespace output arrives before the timer fires,
|
|
511
|
+
* the timer is cancelled (by cancelTaskComplete in onData).
|
|
512
|
+
*/
|
|
513
|
+
private scheduleTaskComplete;
|
|
514
|
+
/**
|
|
515
|
+
* Cancel a pending task_complete timer (new output arrived that
|
|
516
|
+
* doesn't match the idle prompt, so the agent is still working).
|
|
517
|
+
*/
|
|
518
|
+
private cancelTaskComplete;
|
|
497
519
|
/**
|
|
498
520
|
* Start the PTY session
|
|
499
521
|
*/
|
|
@@ -605,6 +627,8 @@ interface PTYManagerEvents {
|
|
|
605
627
|
message: (message: SessionMessage) => void;
|
|
606
628
|
question: (session: SessionHandle, question: string) => void;
|
|
607
629
|
stall_detected: (session: SessionHandle, recentOutput: string, stallDurationMs: number) => void;
|
|
630
|
+
session_status_changed: (session: SessionHandle) => void;
|
|
631
|
+
task_complete: (session: SessionHandle) => void;
|
|
608
632
|
}
|
|
609
633
|
declare class PTYManager extends EventEmitter {
|
|
610
634
|
private sessions;
|
package/dist/index.d.ts
CHANGED
|
@@ -37,6 +37,11 @@ interface SpawnConfig {
|
|
|
37
37
|
adapterConfig?: Record<string, unknown>;
|
|
38
38
|
/** Per-session stall timeout in ms. Overrides PTYManagerConfig.stallTimeoutMs. */
|
|
39
39
|
stallTimeoutMs?: number;
|
|
40
|
+
/** Override or disable specific adapter auto-response rules for this session.
|
|
41
|
+
* Keys are regex source strings (from rule.pattern.source).
|
|
42
|
+
* - null value disables that rule entirely
|
|
43
|
+
* - Object value merges fields into the matching adapter rule */
|
|
44
|
+
ruleOverrides?: Record<string, Partial<Omit<AutoResponseRule, 'pattern'>> | null>;
|
|
40
45
|
}
|
|
41
46
|
/**
|
|
42
47
|
* Handle to a running session
|
|
@@ -400,6 +405,8 @@ interface PTYSessionEvents {
|
|
|
400
405
|
exit: (code: number) => void;
|
|
401
406
|
error: (error: Error) => void;
|
|
402
407
|
stall_detected: (recentOutput: string, stallDurationMs: number) => void;
|
|
408
|
+
status_changed: (status: SessionStatus) => void;
|
|
409
|
+
task_complete: () => void;
|
|
403
410
|
}
|
|
404
411
|
/**
|
|
405
412
|
* Special key mappings to escape sequences
|
|
@@ -420,12 +427,16 @@ declare class PTYSession extends EventEmitter {
|
|
|
420
427
|
private sessionRules;
|
|
421
428
|
private _firedOnceRules;
|
|
422
429
|
private _lastBlockingPromptHash;
|
|
430
|
+
private _ruleOverrides;
|
|
431
|
+
private _disabledRulePatterns;
|
|
423
432
|
private _stallTimer;
|
|
424
433
|
private _stallTimeoutMs;
|
|
425
434
|
private _stallDetectionEnabled;
|
|
426
435
|
private _lastStallHash;
|
|
427
436
|
private _stallStartedAt;
|
|
428
437
|
private _lastContentHash;
|
|
438
|
+
private _taskCompleteTimer;
|
|
439
|
+
private static readonly TASK_COMPLETE_DEBOUNCE_MS;
|
|
429
440
|
readonly id: string;
|
|
430
441
|
readonly config: SpawnConfig;
|
|
431
442
|
constructor(adapter: CLIAdapter, config: SpawnConfig, logger?: Logger, stallDetectionEnabled?: boolean, defaultStallTimeoutMs?: number);
|
|
@@ -494,6 +505,17 @@ declare class PTYSession extends EventEmitter {
|
|
|
494
505
|
* Called by the manager after onStallClassify resolves.
|
|
495
506
|
*/
|
|
496
507
|
handleStallClassification(classification: StallClassification | null): void;
|
|
508
|
+
/**
|
|
509
|
+
* Schedule a task_complete transition after a debounce period.
|
|
510
|
+
* If new non-whitespace output arrives before the timer fires,
|
|
511
|
+
* the timer is cancelled (by cancelTaskComplete in onData).
|
|
512
|
+
*/
|
|
513
|
+
private scheduleTaskComplete;
|
|
514
|
+
/**
|
|
515
|
+
* Cancel a pending task_complete timer (new output arrived that
|
|
516
|
+
* doesn't match the idle prompt, so the agent is still working).
|
|
517
|
+
*/
|
|
518
|
+
private cancelTaskComplete;
|
|
497
519
|
/**
|
|
498
520
|
* Start the PTY session
|
|
499
521
|
*/
|
|
@@ -605,6 +627,8 @@ interface PTYManagerEvents {
|
|
|
605
627
|
message: (message: SessionMessage) => void;
|
|
606
628
|
question: (session: SessionHandle, question: string) => void;
|
|
607
629
|
stall_detected: (session: SessionHandle, recentOutput: string, stallDurationMs: number) => void;
|
|
630
|
+
session_status_changed: (session: SessionHandle) => void;
|
|
631
|
+
task_complete: (session: SessionHandle) => void;
|
|
608
632
|
}
|
|
609
633
|
declare class PTYManager extends EventEmitter {
|
|
610
634
|
private sessions;
|
package/dist/index.js
CHANGED
|
@@ -306,7 +306,7 @@ var SPECIAL_KEYS = {
|
|
|
306
306
|
};
|
|
307
307
|
var BRACKETED_PASTE_START = "\x1B[200~";
|
|
308
308
|
var BRACKETED_PASTE_END = "\x1B[201~";
|
|
309
|
-
var PTYSession = class extends import_events.EventEmitter {
|
|
309
|
+
var PTYSession = class _PTYSession extends import_events.EventEmitter {
|
|
310
310
|
constructor(adapter, config, logger, stallDetectionEnabled, defaultStallTimeoutMs) {
|
|
311
311
|
super();
|
|
312
312
|
this.adapter = adapter;
|
|
@@ -315,6 +315,15 @@ var PTYSession = class extends import_events.EventEmitter {
|
|
|
315
315
|
this.logger = logger || consoleLogger;
|
|
316
316
|
this._stallDetectionEnabled = stallDetectionEnabled ?? false;
|
|
317
317
|
this._stallTimeoutMs = config.stallTimeoutMs ?? defaultStallTimeoutMs ?? 8e3;
|
|
318
|
+
if (config.ruleOverrides) {
|
|
319
|
+
for (const [key, value] of Object.entries(config.ruleOverrides)) {
|
|
320
|
+
if (value === null) {
|
|
321
|
+
this._disabledRulePatterns.add(key);
|
|
322
|
+
} else {
|
|
323
|
+
this._ruleOverrides.set(key, value);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
318
327
|
}
|
|
319
328
|
ptyProcess = null;
|
|
320
329
|
outputBuffer = "";
|
|
@@ -326,6 +335,8 @@ var PTYSession = class extends import_events.EventEmitter {
|
|
|
326
335
|
sessionRules = [];
|
|
327
336
|
_firedOnceRules = /* @__PURE__ */ new Set();
|
|
328
337
|
_lastBlockingPromptHash = null;
|
|
338
|
+
_ruleOverrides = /* @__PURE__ */ new Map();
|
|
339
|
+
_disabledRulePatterns = /* @__PURE__ */ new Set();
|
|
329
340
|
// Stall detection
|
|
330
341
|
_stallTimer = null;
|
|
331
342
|
_stallTimeoutMs;
|
|
@@ -333,6 +344,9 @@ var PTYSession = class extends import_events.EventEmitter {
|
|
|
333
344
|
_lastStallHash = null;
|
|
334
345
|
_stallStartedAt = null;
|
|
335
346
|
_lastContentHash = null;
|
|
347
|
+
// Task completion detection (idle detection when busy)
|
|
348
|
+
_taskCompleteTimer = null;
|
|
349
|
+
static TASK_COMPLETE_DEBOUNCE_MS = 1500;
|
|
336
350
|
id;
|
|
337
351
|
config;
|
|
338
352
|
get status() {
|
|
@@ -508,10 +522,12 @@ var PTYSession = class extends import_events.EventEmitter {
|
|
|
508
522
|
* word boundaries — e.g. "Do\x1b[5Cyou" becomes "Do you", not "Doyou".
|
|
509
523
|
*/
|
|
510
524
|
stripAnsiForStall(str) {
|
|
511
|
-
let result = str.replace(/\x1b\[\d*[
|
|
525
|
+
let result = str.replace(/\x1b\[\d*[CDABGdEF]/g, " ");
|
|
512
526
|
result = result.replace(/\x1b\[\d*(?:;\d+)?[Hf]/g, " ");
|
|
527
|
+
result = result.replace(/\x1b\[\d*[JK]/g, " ");
|
|
513
528
|
result = result.replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, "");
|
|
514
|
-
result = result.replace(/[
|
|
529
|
+
result = result.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, "");
|
|
530
|
+
result = result.replace(/[│╭╰╮╯─═╌║╔╗╚╝╠╣╦╩╬┌┐└┘├┤┬┴┼●○❯❮▶◀⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏⣾⣽⣻⢿⡿⣟⣯⣷✻✶✳✢⏺←→↑↓⬆⬇◆◇▪▫■□▲△▼▽◈⟨⟩⌘⏎⏏⌫⌦⇧⇪⌥]/g, " ");
|
|
515
531
|
result = result.replace(/ {2,}/g, " ");
|
|
516
532
|
return result;
|
|
517
533
|
}
|
|
@@ -548,6 +564,7 @@ var PTYSession = class extends import_events.EventEmitter {
|
|
|
548
564
|
this.writeRaw(resp + "\r");
|
|
549
565
|
}
|
|
550
566
|
this.emit("blocking_prompt", promptInfo, true);
|
|
567
|
+
this.outputBuffer = "";
|
|
551
568
|
} else {
|
|
552
569
|
this.emit("blocking_prompt", promptInfo, false);
|
|
553
570
|
}
|
|
@@ -568,6 +585,39 @@ var PTYSession = class extends import_events.EventEmitter {
|
|
|
568
585
|
}
|
|
569
586
|
}
|
|
570
587
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
588
|
+
// Task Completion Detection
|
|
589
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
590
|
+
/**
|
|
591
|
+
* Schedule a task_complete transition after a debounce period.
|
|
592
|
+
* If new non-whitespace output arrives before the timer fires,
|
|
593
|
+
* the timer is cancelled (by cancelTaskComplete in onData).
|
|
594
|
+
*/
|
|
595
|
+
scheduleTaskComplete() {
|
|
596
|
+
if (this._taskCompleteTimer) return;
|
|
597
|
+
this._taskCompleteTimer = setTimeout(() => {
|
|
598
|
+
this._taskCompleteTimer = null;
|
|
599
|
+
if (this._status !== "busy") return;
|
|
600
|
+
if (!this.adapter.detectReady(this.outputBuffer)) return;
|
|
601
|
+
this._status = "ready";
|
|
602
|
+
this._lastBlockingPromptHash = null;
|
|
603
|
+
this.outputBuffer = "";
|
|
604
|
+
this.clearStallTimer();
|
|
605
|
+
this.emit("status_changed", "ready");
|
|
606
|
+
this.emit("task_complete");
|
|
607
|
+
this.logger.info({ sessionId: this.id }, "Task complete \u2014 agent returned to idle prompt");
|
|
608
|
+
}, _PTYSession.TASK_COMPLETE_DEBOUNCE_MS);
|
|
609
|
+
}
|
|
610
|
+
/**
|
|
611
|
+
* Cancel a pending task_complete timer (new output arrived that
|
|
612
|
+
* doesn't match the idle prompt, so the agent is still working).
|
|
613
|
+
*/
|
|
614
|
+
cancelTaskComplete() {
|
|
615
|
+
if (this._taskCompleteTimer) {
|
|
616
|
+
clearTimeout(this._taskCompleteTimer);
|
|
617
|
+
this._taskCompleteTimer = null;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
571
621
|
// Lifecycle
|
|
572
622
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
573
623
|
/**
|
|
@@ -629,10 +679,6 @@ var PTYSession = class extends import_events.EventEmitter {
|
|
|
629
679
|
this.resetStallTimer();
|
|
630
680
|
}
|
|
631
681
|
this.emit("output", data);
|
|
632
|
-
const blockingPrompt = this.detectAndHandleBlockingPrompt();
|
|
633
|
-
if (blockingPrompt) {
|
|
634
|
-
return;
|
|
635
|
-
}
|
|
636
682
|
if ((this._status === "starting" || this._status === "authenticating") && this.adapter.detectReady(this.outputBuffer)) {
|
|
637
683
|
this._status = "ready";
|
|
638
684
|
this._lastBlockingPromptHash = null;
|
|
@@ -642,6 +688,15 @@ var PTYSession = class extends import_events.EventEmitter {
|
|
|
642
688
|
this.logger.info({ sessionId: this.id }, "Session ready");
|
|
643
689
|
return;
|
|
644
690
|
}
|
|
691
|
+
if (this._status === "busy" && this.adapter.detectReady(this.outputBuffer)) {
|
|
692
|
+
this.scheduleTaskComplete();
|
|
693
|
+
} else {
|
|
694
|
+
this.cancelTaskComplete();
|
|
695
|
+
}
|
|
696
|
+
const blockingPrompt = this.detectAndHandleBlockingPrompt();
|
|
697
|
+
if (blockingPrompt) {
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
645
700
|
if (this._status !== "ready" && this._status !== "busy") {
|
|
646
701
|
const loginDetection = this.adapter.detectLogin(this.outputBuffer);
|
|
647
702
|
if (loginDetection.required && this._status !== "authenticating") {
|
|
@@ -717,6 +772,7 @@ var PTYSession = class extends import_events.EventEmitter {
|
|
|
717
772
|
this.writeRaw(resp + "\r");
|
|
718
773
|
}
|
|
719
774
|
this._lastBlockingPromptHash = null;
|
|
775
|
+
this.outputBuffer = "";
|
|
720
776
|
this.emit("blocking_prompt", promptInfo, true);
|
|
721
777
|
return true;
|
|
722
778
|
}
|
|
@@ -744,7 +800,10 @@ var PTYSession = class extends import_events.EventEmitter {
|
|
|
744
800
|
* Session rules are checked first, then adapter rules.
|
|
745
801
|
*/
|
|
746
802
|
tryAutoResponse() {
|
|
747
|
-
const adapterRules = this.adapter.autoResponseRules || []
|
|
803
|
+
const adapterRules = (this.adapter.autoResponseRules || []).filter((r) => !this._disabledRulePatterns.has(r.pattern.source)).map((r) => {
|
|
804
|
+
const override = this._ruleOverrides.get(r.pattern.source);
|
|
805
|
+
return override ? { ...r, ...override } : r;
|
|
806
|
+
});
|
|
748
807
|
const allRules = [...this.sessionRules, ...adapterRules];
|
|
749
808
|
if (allRules.length === 0) {
|
|
750
809
|
return false;
|
|
@@ -860,6 +919,7 @@ var PTYSession = class extends import_events.EventEmitter {
|
|
|
860
919
|
*/
|
|
861
920
|
send(message) {
|
|
862
921
|
this._status = "busy";
|
|
922
|
+
this.emit("status_changed", "busy");
|
|
863
923
|
this.resetStallTimer();
|
|
864
924
|
const msg = {
|
|
865
925
|
id: `${this.id}-msg-${++this.messageCounter}`,
|
|
@@ -970,6 +1030,7 @@ var PTYSession = class extends import_events.EventEmitter {
|
|
|
970
1030
|
if (this.ptyProcess) {
|
|
971
1031
|
this._status = "stopping";
|
|
972
1032
|
this.clearStallTimer();
|
|
1033
|
+
this.cancelTaskComplete();
|
|
973
1034
|
this.ptyProcess.kill(signal);
|
|
974
1035
|
this.logger.info({ sessionId: this.id, signal }, "Killing PTY session");
|
|
975
1036
|
}
|
|
@@ -1123,6 +1184,12 @@ var PTYManager = class extends import_events2.EventEmitter {
|
|
|
1123
1184
|
session.on("error", (error) => {
|
|
1124
1185
|
this.emit("session_error", session.toHandle(), error.message);
|
|
1125
1186
|
});
|
|
1187
|
+
session.on("status_changed", () => {
|
|
1188
|
+
this.emit("session_status_changed", session.toHandle());
|
|
1189
|
+
});
|
|
1190
|
+
session.on("task_complete", () => {
|
|
1191
|
+
this.emit("task_complete", session.toHandle());
|
|
1192
|
+
});
|
|
1126
1193
|
session.on("stall_detected", (recentOutput, stallDurationMs) => {
|
|
1127
1194
|
const handle = session.toHandle();
|
|
1128
1195
|
this.emit("stall_detected", handle, recentOutput, stallDurationMs);
|
|
@@ -1976,6 +2043,24 @@ var BunCompatiblePTYManager = class extends import_events3.EventEmitter {
|
|
|
1976
2043
|
}
|
|
1977
2044
|
break;
|
|
1978
2045
|
}
|
|
2046
|
+
case "status_changed": {
|
|
2047
|
+
const session = this.sessions.get(id);
|
|
2048
|
+
if (session) {
|
|
2049
|
+
session.status = event.status;
|
|
2050
|
+
session.lastActivityAt = /* @__PURE__ */ new Date();
|
|
2051
|
+
this.emit("session_status_changed", session);
|
|
2052
|
+
}
|
|
2053
|
+
break;
|
|
2054
|
+
}
|
|
2055
|
+
case "task_complete": {
|
|
2056
|
+
const session = this.sessions.get(id);
|
|
2057
|
+
if (session) {
|
|
2058
|
+
session.status = "ready";
|
|
2059
|
+
session.lastActivityAt = /* @__PURE__ */ new Date();
|
|
2060
|
+
this.emit("task_complete", session);
|
|
2061
|
+
}
|
|
2062
|
+
break;
|
|
2063
|
+
}
|
|
1979
2064
|
case "stall_detected": {
|
|
1980
2065
|
const session = this.sessions.get(id);
|
|
1981
2066
|
if (session) {
|