pty-manager 1.2.21 → 1.3.0
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/README.md +36 -2
- package/dist/index.d.mts +32 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.js +97 -7
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +97 -7
- package/dist/index.mjs.map +1 -1
- package/dist/pty-worker.js +84 -7
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -268,7 +268,7 @@ var SPECIAL_KEYS = {
|
|
|
268
268
|
};
|
|
269
269
|
var BRACKETED_PASTE_START = "\x1B[200~";
|
|
270
270
|
var BRACKETED_PASTE_END = "\x1B[201~";
|
|
271
|
-
var PTYSession = class extends EventEmitter {
|
|
271
|
+
var PTYSession = class _PTYSession extends EventEmitter {
|
|
272
272
|
constructor(adapter, config, logger, stallDetectionEnabled, defaultStallTimeoutMs) {
|
|
273
273
|
super();
|
|
274
274
|
this.adapter = adapter;
|
|
@@ -306,6 +306,9 @@ var PTYSession = class extends EventEmitter {
|
|
|
306
306
|
_lastStallHash = null;
|
|
307
307
|
_stallStartedAt = null;
|
|
308
308
|
_lastContentHash = null;
|
|
309
|
+
// Task completion detection (idle detection when busy)
|
|
310
|
+
_taskCompleteTimer = null;
|
|
311
|
+
static TASK_COMPLETE_DEBOUNCE_MS = 1500;
|
|
309
312
|
id;
|
|
310
313
|
config;
|
|
311
314
|
get status() {
|
|
@@ -445,6 +448,19 @@ var PTYSession = class extends EventEmitter {
|
|
|
445
448
|
return;
|
|
446
449
|
}
|
|
447
450
|
this._lastStallHash = hash;
|
|
451
|
+
if (this._status === "busy" && this.adapter.detectTaskComplete?.(this.outputBuffer)) {
|
|
452
|
+
this._status = "ready";
|
|
453
|
+
this._lastBlockingPromptHash = null;
|
|
454
|
+
this.outputBuffer = "";
|
|
455
|
+
this.clearStallTimer();
|
|
456
|
+
this.emit("status_changed", "ready");
|
|
457
|
+
this.emit("task_complete");
|
|
458
|
+
this.logger.info(
|
|
459
|
+
{ sessionId: this.id },
|
|
460
|
+
"Task complete (adapter fast-path) \u2014 agent returned to idle prompt"
|
|
461
|
+
);
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
448
464
|
const recentRaw = this.outputBuffer.slice(-2e3);
|
|
449
465
|
const recentOutput = this.stripAnsiForStall(recentRaw);
|
|
450
466
|
const stallDurationMs = this._stallStartedAt ? Date.now() - this._stallStartedAt : this._stallTimeoutMs;
|
|
@@ -481,10 +497,12 @@ var PTYSession = class extends EventEmitter {
|
|
|
481
497
|
* word boundaries — e.g. "Do\x1b[5Cyou" becomes "Do you", not "Doyou".
|
|
482
498
|
*/
|
|
483
499
|
stripAnsiForStall(str) {
|
|
484
|
-
let result = str.replace(/\x1b\[\d*[
|
|
500
|
+
let result = str.replace(/\x1b\[\d*[CDABGdEF]/g, " ");
|
|
485
501
|
result = result.replace(/\x1b\[\d*(?:;\d+)?[Hf]/g, " ");
|
|
502
|
+
result = result.replace(/\x1b\[\d*[JK]/g, " ");
|
|
486
503
|
result = result.replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, "");
|
|
487
|
-
result = result.replace(/[
|
|
504
|
+
result = result.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, "");
|
|
505
|
+
result = result.replace(/[│╭╰╮╯─═╌║╔╗╚╝╠╣╦╩╬┌┐└┘├┤┬┴┼●○❯❮▶◀⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏⣾⣽⣻⢿⡿⣟⣯⣷✻✶✳✢⏺←→↑↓⬆⬇◆◇▪▫■□▲△▼▽◈⟨⟩⌘⏎⏏⌫⌦⇧⇪⌥]/g, " ");
|
|
488
506
|
result = result.replace(/ {2,}/g, " ");
|
|
489
507
|
return result;
|
|
490
508
|
}
|
|
@@ -542,6 +560,39 @@ var PTYSession = class extends EventEmitter {
|
|
|
542
560
|
}
|
|
543
561
|
}
|
|
544
562
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
563
|
+
// Task Completion Detection
|
|
564
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
565
|
+
/**
|
|
566
|
+
* Schedule a task_complete transition after a debounce period.
|
|
567
|
+
* If new non-whitespace output arrives before the timer fires,
|
|
568
|
+
* the timer is cancelled (by cancelTaskComplete in onData).
|
|
569
|
+
*/
|
|
570
|
+
scheduleTaskComplete() {
|
|
571
|
+
if (this._taskCompleteTimer) return;
|
|
572
|
+
this._taskCompleteTimer = setTimeout(() => {
|
|
573
|
+
this._taskCompleteTimer = null;
|
|
574
|
+
if (this._status !== "busy") return;
|
|
575
|
+
if (!this.adapter.detectReady(this.outputBuffer)) return;
|
|
576
|
+
this._status = "ready";
|
|
577
|
+
this._lastBlockingPromptHash = null;
|
|
578
|
+
this.outputBuffer = "";
|
|
579
|
+
this.clearStallTimer();
|
|
580
|
+
this.emit("status_changed", "ready");
|
|
581
|
+
this.emit("task_complete");
|
|
582
|
+
this.logger.info({ sessionId: this.id }, "Task complete \u2014 agent returned to idle prompt");
|
|
583
|
+
}, _PTYSession.TASK_COMPLETE_DEBOUNCE_MS);
|
|
584
|
+
}
|
|
585
|
+
/**
|
|
586
|
+
* Cancel a pending task_complete timer (new output arrived that
|
|
587
|
+
* doesn't match the idle prompt, so the agent is still working).
|
|
588
|
+
*/
|
|
589
|
+
cancelTaskComplete() {
|
|
590
|
+
if (this._taskCompleteTimer) {
|
|
591
|
+
clearTimeout(this._taskCompleteTimer);
|
|
592
|
+
this._taskCompleteTimer = null;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
545
596
|
// Lifecycle
|
|
546
597
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
547
598
|
/**
|
|
@@ -603,10 +654,6 @@ var PTYSession = class extends EventEmitter {
|
|
|
603
654
|
this.resetStallTimer();
|
|
604
655
|
}
|
|
605
656
|
this.emit("output", data);
|
|
606
|
-
const blockingPrompt = this.detectAndHandleBlockingPrompt();
|
|
607
|
-
if (blockingPrompt) {
|
|
608
|
-
return;
|
|
609
|
-
}
|
|
610
657
|
if ((this._status === "starting" || this._status === "authenticating") && this.adapter.detectReady(this.outputBuffer)) {
|
|
611
658
|
this._status = "ready";
|
|
612
659
|
this._lastBlockingPromptHash = null;
|
|
@@ -616,6 +663,15 @@ var PTYSession = class extends EventEmitter {
|
|
|
616
663
|
this.logger.info({ sessionId: this.id }, "Session ready");
|
|
617
664
|
return;
|
|
618
665
|
}
|
|
666
|
+
if (this._status === "busy" && this.adapter.detectReady(this.outputBuffer)) {
|
|
667
|
+
this.scheduleTaskComplete();
|
|
668
|
+
} else {
|
|
669
|
+
this.cancelTaskComplete();
|
|
670
|
+
}
|
|
671
|
+
const blockingPrompt = this.detectAndHandleBlockingPrompt();
|
|
672
|
+
if (blockingPrompt) {
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
619
675
|
if (this._status !== "ready" && this._status !== "busy") {
|
|
620
676
|
const loginDetection = this.adapter.detectLogin(this.outputBuffer);
|
|
621
677
|
if (loginDetection.required && this._status !== "authenticating") {
|
|
@@ -838,6 +894,7 @@ var PTYSession = class extends EventEmitter {
|
|
|
838
894
|
*/
|
|
839
895
|
send(message) {
|
|
840
896
|
this._status = "busy";
|
|
897
|
+
this.emit("status_changed", "busy");
|
|
841
898
|
this.resetStallTimer();
|
|
842
899
|
const msg = {
|
|
843
900
|
id: `${this.id}-msg-${++this.messageCounter}`,
|
|
@@ -948,6 +1005,7 @@ var PTYSession = class extends EventEmitter {
|
|
|
948
1005
|
if (this.ptyProcess) {
|
|
949
1006
|
this._status = "stopping";
|
|
950
1007
|
this.clearStallTimer();
|
|
1008
|
+
this.cancelTaskComplete();
|
|
951
1009
|
this.ptyProcess.kill(signal);
|
|
952
1010
|
this.logger.info({ sessionId: this.id, signal }, "Killing PTY session");
|
|
953
1011
|
}
|
|
@@ -1101,6 +1159,12 @@ var PTYManager = class extends EventEmitter2 {
|
|
|
1101
1159
|
session.on("error", (error) => {
|
|
1102
1160
|
this.emit("session_error", session.toHandle(), error.message);
|
|
1103
1161
|
});
|
|
1162
|
+
session.on("status_changed", () => {
|
|
1163
|
+
this.emit("session_status_changed", session.toHandle());
|
|
1164
|
+
});
|
|
1165
|
+
session.on("task_complete", () => {
|
|
1166
|
+
this.emit("task_complete", session.toHandle());
|
|
1167
|
+
});
|
|
1104
1168
|
session.on("stall_detected", (recentOutput, stallDurationMs) => {
|
|
1105
1169
|
const handle = session.toHandle();
|
|
1106
1170
|
this.emit("stall_detected", handle, recentOutput, stallDurationMs);
|
|
@@ -1505,6 +1569,14 @@ var BaseCLIAdapter = class {
|
|
|
1505
1569
|
}
|
|
1506
1570
|
return { detected: false };
|
|
1507
1571
|
}
|
|
1572
|
+
/**
|
|
1573
|
+
* Default task completion detection — delegates to detectReady().
|
|
1574
|
+
* Subclasses should override to match high-confidence completion patterns
|
|
1575
|
+
* (e.g. duration summaries) that short-circuit the LLM stall classifier.
|
|
1576
|
+
*/
|
|
1577
|
+
detectTaskComplete(output) {
|
|
1578
|
+
return this.detectReady(output);
|
|
1579
|
+
}
|
|
1508
1580
|
/**
|
|
1509
1581
|
* Default input formatting - just return as-is
|
|
1510
1582
|
*/
|
|
@@ -1954,6 +2026,24 @@ var BunCompatiblePTYManager = class extends EventEmitter3 {
|
|
|
1954
2026
|
}
|
|
1955
2027
|
break;
|
|
1956
2028
|
}
|
|
2029
|
+
case "status_changed": {
|
|
2030
|
+
const session = this.sessions.get(id);
|
|
2031
|
+
if (session) {
|
|
2032
|
+
session.status = event.status;
|
|
2033
|
+
session.lastActivityAt = /* @__PURE__ */ new Date();
|
|
2034
|
+
this.emit("session_status_changed", session);
|
|
2035
|
+
}
|
|
2036
|
+
break;
|
|
2037
|
+
}
|
|
2038
|
+
case "task_complete": {
|
|
2039
|
+
const session = this.sessions.get(id);
|
|
2040
|
+
if (session) {
|
|
2041
|
+
session.status = "ready";
|
|
2042
|
+
session.lastActivityAt = /* @__PURE__ */ new Date();
|
|
2043
|
+
this.emit("task_complete", session);
|
|
2044
|
+
}
|
|
2045
|
+
break;
|
|
2046
|
+
}
|
|
1957
2047
|
case "stall_detected": {
|
|
1958
2048
|
const session = this.sessions.get(id);
|
|
1959
2049
|
if (session) {
|