pty-manager 1.6.2 → 1.6.4
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 +51 -1
- package/dist/index.d.mts +78 -3
- package/dist/index.d.ts +78 -3
- package/dist/index.js +254 -6
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +252 -6
- package/dist/index.mjs.map +1 -1
- package/dist/pty-worker.js +82 -5
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -310,6 +310,8 @@ var PTYSession = class _PTYSession extends EventEmitter {
|
|
|
310
310
|
_stallBackoffMs = 0;
|
|
311
311
|
// Initialized in constructor from _stallTimeoutMs
|
|
312
312
|
static MAX_STALL_BACKOFF_MS = 3e4;
|
|
313
|
+
_stallEmissionCount = 0;
|
|
314
|
+
static MAX_STALL_EMISSIONS = 5;
|
|
313
315
|
// Task completion detection (idle detection when busy)
|
|
314
316
|
_taskCompleteTimer = null;
|
|
315
317
|
_taskCompletePending = false;
|
|
@@ -427,6 +429,7 @@ var PTYSession = class _PTYSession extends EventEmitter {
|
|
|
427
429
|
return;
|
|
428
430
|
}
|
|
429
431
|
this._lastContentHash = hash;
|
|
432
|
+
this._stallEmissionCount = 0;
|
|
430
433
|
if (this._stallTimer) {
|
|
431
434
|
clearTimeout(this._stallTimer);
|
|
432
435
|
this._stallTimer = null;
|
|
@@ -449,6 +452,7 @@ var PTYSession = class _PTYSession extends EventEmitter {
|
|
|
449
452
|
this._stallStartedAt = null;
|
|
450
453
|
this._lastContentHash = null;
|
|
451
454
|
this._stallBackoffMs = this._stallTimeoutMs;
|
|
455
|
+
this._stallEmissionCount = 0;
|
|
452
456
|
}
|
|
453
457
|
/**
|
|
454
458
|
* Called when the stall timer fires (no output for stallTimeoutMs).
|
|
@@ -485,6 +489,15 @@ var PTYSession = class _PTYSession extends EventEmitter {
|
|
|
485
489
|
);
|
|
486
490
|
return;
|
|
487
491
|
}
|
|
492
|
+
this._stallEmissionCount++;
|
|
493
|
+
if (this._stallEmissionCount > _PTYSession.MAX_STALL_EMISSIONS) {
|
|
494
|
+
this.logger.warn(
|
|
495
|
+
{ sessionId: this.id, count: this._stallEmissionCount },
|
|
496
|
+
"Max stall emissions reached \u2014 suspending stall detection for this task"
|
|
497
|
+
);
|
|
498
|
+
this.clearStallTimer();
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
488
501
|
const recentRaw = this.outputBuffer.slice(-2e3);
|
|
489
502
|
const recentOutput = this.stripAnsiForStall(recentRaw);
|
|
490
503
|
const stallDurationMs = this._stallStartedAt ? Date.now() - this._stallStartedAt : this._stallTimeoutMs;
|
|
@@ -524,6 +537,8 @@ var PTYSession = class _PTYSession extends EventEmitter {
|
|
|
524
537
|
let result = str.replace(/\x1b\[\d*[CDABGdEF]/g, " ");
|
|
525
538
|
result = result.replace(/\x1b\[\d*(?:;\d+)?[Hf]/g, " ");
|
|
526
539
|
result = result.replace(/\x1b\[\d*[JK]/g, " ");
|
|
540
|
+
result = result.replace(/\x1b\](?:[^\x07\x1b]|\x1b[^\\])*(?:\x07|\x1b\\)/g, "");
|
|
541
|
+
result = result.replace(/\x1bP(?:[^\x1b]|\x1b[^\\])*\x1b\\/g, "");
|
|
527
542
|
result = result.replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, "");
|
|
528
543
|
result = result.replace(/[\x00-\x08\x0b-\x1f\x7f]/g, "");
|
|
529
544
|
result = result.replace(/\xa0/g, " ");
|
|
@@ -607,10 +622,15 @@ var PTYSession = class _PTYSession extends EventEmitter {
|
|
|
607
622
|
* being a no-op when already scheduled. This allows TUI agents that
|
|
608
623
|
* continue rendering decorative output (status bar, update notices) after
|
|
609
624
|
* the prompt to eventually settle, rather than having the timer cancelled
|
|
610
|
-
* by every new data chunk. The callback re-verifies
|
|
611
|
-
* transitioning, so stale triggers are safe.
|
|
625
|
+
* by every new data chunk. The callback re-verifies the task-complete
|
|
626
|
+
* signal before transitioning, so stale triggers are safe.
|
|
612
627
|
*/
|
|
613
628
|
scheduleTaskComplete() {
|
|
629
|
+
const wasPending = this._taskCompletePending;
|
|
630
|
+
this.traceTaskCompletion("debounce_schedule", {
|
|
631
|
+
wasPending,
|
|
632
|
+
debounceMs: _PTYSession.TASK_COMPLETE_DEBOUNCE_MS
|
|
633
|
+
});
|
|
614
634
|
if (this._taskCompleteTimer) {
|
|
615
635
|
clearTimeout(this._taskCompleteTimer);
|
|
616
636
|
}
|
|
@@ -618,17 +638,72 @@ var PTYSession = class _PTYSession extends EventEmitter {
|
|
|
618
638
|
this._taskCompleteTimer = setTimeout(() => {
|
|
619
639
|
this._taskCompleteTimer = null;
|
|
620
640
|
this._taskCompletePending = false;
|
|
621
|
-
|
|
622
|
-
|
|
641
|
+
const signal = this.isTaskCompleteSignal(this.outputBuffer);
|
|
642
|
+
this.traceTaskCompletion("debounce_fire", { signal });
|
|
643
|
+
if (this._status !== "busy") {
|
|
644
|
+
this.traceTaskCompletion("debounce_reject_status", { signal });
|
|
645
|
+
return;
|
|
646
|
+
}
|
|
647
|
+
if (!signal) {
|
|
648
|
+
this.traceTaskCompletion("debounce_reject_signal", { signal });
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
623
651
|
this._status = "ready";
|
|
624
652
|
this._lastBlockingPromptHash = null;
|
|
625
653
|
this.outputBuffer = "";
|
|
626
654
|
this.clearStallTimer();
|
|
627
655
|
this.emit("status_changed", "ready");
|
|
628
656
|
this.emit("task_complete");
|
|
657
|
+
this.traceTaskCompletion("transition_ready", { signal: true });
|
|
629
658
|
this.logger.info({ sessionId: this.id }, "Task complete \u2014 agent returned to idle prompt");
|
|
630
659
|
}, _PTYSession.TASK_COMPLETE_DEBOUNCE_MS);
|
|
631
660
|
}
|
|
661
|
+
/**
|
|
662
|
+
* Adapter-level task completion check with compatibility fallback.
|
|
663
|
+
* Prefer detectTaskComplete() because detectReady() may be broad for TUIs.
|
|
664
|
+
*/
|
|
665
|
+
isTaskCompleteSignal(output) {
|
|
666
|
+
if (this.adapter.detectTaskComplete) {
|
|
667
|
+
return this.adapter.detectTaskComplete(output);
|
|
668
|
+
}
|
|
669
|
+
return this.adapter.detectReady(output);
|
|
670
|
+
}
|
|
671
|
+
/**
|
|
672
|
+
* Claude-oriented task completion traces for PTY debugging.
|
|
673
|
+
* Enabled by config.traceTaskCompletion, otherwise defaults to enabled for Claude.
|
|
674
|
+
*/
|
|
675
|
+
traceTaskCompletion(event, ctx = {}) {
|
|
676
|
+
if (!this.shouldTraceTaskCompletion()) return;
|
|
677
|
+
const output = this.outputBuffer;
|
|
678
|
+
const detectTaskComplete = this.adapter.detectTaskComplete ? this.adapter.detectTaskComplete(output) : void 0;
|
|
679
|
+
const detectReady = this.adapter.detectReady(output);
|
|
680
|
+
const detectLoading = this.adapter.detectLoading ? this.adapter.detectLoading(output) : void 0;
|
|
681
|
+
const normalizedTail = this.stripAnsiForStall(output.slice(-280));
|
|
682
|
+
this.logger.debug(
|
|
683
|
+
{
|
|
684
|
+
sessionId: this.id,
|
|
685
|
+
adapterType: this.adapter.adapterType,
|
|
686
|
+
event,
|
|
687
|
+
status: this._status,
|
|
688
|
+
taskCompletePending: this._taskCompletePending,
|
|
689
|
+
signal: ctx.signal,
|
|
690
|
+
wasPending: ctx.wasPending,
|
|
691
|
+
debounceMs: ctx.debounceMs,
|
|
692
|
+
detectTaskComplete,
|
|
693
|
+
detectReady,
|
|
694
|
+
detectLoading,
|
|
695
|
+
tailHash: this.simpleHash(normalizedTail),
|
|
696
|
+
tailSnippet: normalizedTail.slice(-140)
|
|
697
|
+
},
|
|
698
|
+
"Task completion trace"
|
|
699
|
+
);
|
|
700
|
+
}
|
|
701
|
+
shouldTraceTaskCompletion() {
|
|
702
|
+
if (typeof this.config.traceTaskCompletion === "boolean") {
|
|
703
|
+
return this.config.traceTaskCompletion;
|
|
704
|
+
}
|
|
705
|
+
return this.adapter.adapterType === "claude";
|
|
706
|
+
}
|
|
632
707
|
/**
|
|
633
708
|
* Cancel a pending task_complete timer (new output arrived that
|
|
634
709
|
* doesn't match the idle prompt, so the agent is still working).
|
|
@@ -781,7 +856,9 @@ var PTYSession = class _PTYSession extends EventEmitter {
|
|
|
781
856
|
return;
|
|
782
857
|
}
|
|
783
858
|
if (this._status === "busy") {
|
|
784
|
-
|
|
859
|
+
const signal = this.isTaskCompleteSignal(this.outputBuffer);
|
|
860
|
+
if (this._taskCompletePending || signal) {
|
|
861
|
+
this.traceTaskCompletion("busy_signal", { signal });
|
|
785
862
|
this.scheduleTaskComplete();
|
|
786
863
|
}
|
|
787
864
|
}
|
|
@@ -1532,6 +1609,171 @@ var PTYManager = class extends EventEmitter2 {
|
|
|
1532
1609
|
}
|
|
1533
1610
|
};
|
|
1534
1611
|
|
|
1612
|
+
// src/task-completion-trace.ts
|
|
1613
|
+
function extractTaskCompletionTraceRecords(entries) {
|
|
1614
|
+
const out = [];
|
|
1615
|
+
for (const entry of entries) {
|
|
1616
|
+
let obj = null;
|
|
1617
|
+
if (typeof entry === "string") {
|
|
1618
|
+
const line = entry.trim();
|
|
1619
|
+
if (!line.startsWith("{") || !line.endsWith("}")) continue;
|
|
1620
|
+
try {
|
|
1621
|
+
obj = JSON.parse(line);
|
|
1622
|
+
} catch {
|
|
1623
|
+
continue;
|
|
1624
|
+
}
|
|
1625
|
+
} else if (entry && typeof entry === "object") {
|
|
1626
|
+
obj = entry;
|
|
1627
|
+
}
|
|
1628
|
+
if (!obj) continue;
|
|
1629
|
+
if (obj.msg !== "Task completion trace") continue;
|
|
1630
|
+
if (typeof obj.event !== "string") continue;
|
|
1631
|
+
out.push({
|
|
1632
|
+
sessionId: asString(obj.sessionId),
|
|
1633
|
+
adapterType: asString(obj.adapterType),
|
|
1634
|
+
event: obj.event,
|
|
1635
|
+
status: asString(obj.status),
|
|
1636
|
+
taskCompletePending: asBool(obj.taskCompletePending),
|
|
1637
|
+
signal: asBool(obj.signal),
|
|
1638
|
+
wasPending: asBool(obj.wasPending),
|
|
1639
|
+
debounceMs: asNumber(obj.debounceMs),
|
|
1640
|
+
detectTaskComplete: asBool(obj.detectTaskComplete),
|
|
1641
|
+
detectReady: asBool(obj.detectReady),
|
|
1642
|
+
detectLoading: asBool(obj.detectLoading),
|
|
1643
|
+
tailHash: asString(obj.tailHash),
|
|
1644
|
+
tailSnippet: asString(obj.tailSnippet),
|
|
1645
|
+
timestamp: asTimestamp(obj.time) ?? asTimestamp(obj.timestamp)
|
|
1646
|
+
});
|
|
1647
|
+
}
|
|
1648
|
+
return out;
|
|
1649
|
+
}
|
|
1650
|
+
function buildTaskCompletionTimeline(records, options = {}) {
|
|
1651
|
+
const filtered = records.filter((r) => {
|
|
1652
|
+
if (!options.adapterType) return true;
|
|
1653
|
+
return r.adapterType === options.adapterType;
|
|
1654
|
+
});
|
|
1655
|
+
const turns = [];
|
|
1656
|
+
let current = null;
|
|
1657
|
+
let ignored = 0;
|
|
1658
|
+
filtered.forEach((record, index) => {
|
|
1659
|
+
if (record.event === "busy_signal" && current && current.completed) {
|
|
1660
|
+
current = null;
|
|
1661
|
+
}
|
|
1662
|
+
if (!current) {
|
|
1663
|
+
current = {
|
|
1664
|
+
turn: turns.length + 1,
|
|
1665
|
+
startIndex: index,
|
|
1666
|
+
endIndex: index,
|
|
1667
|
+
completed: false,
|
|
1668
|
+
maxConfidence: 0,
|
|
1669
|
+
finalConfidence: 0,
|
|
1670
|
+
events: []
|
|
1671
|
+
};
|
|
1672
|
+
turns.push(current);
|
|
1673
|
+
}
|
|
1674
|
+
const step = toStep(record, index);
|
|
1675
|
+
if (!step) {
|
|
1676
|
+
ignored++;
|
|
1677
|
+
return;
|
|
1678
|
+
}
|
|
1679
|
+
current.events.push(step);
|
|
1680
|
+
current.endIndex = index;
|
|
1681
|
+
current.maxConfidence = Math.max(current.maxConfidence, step.confidence);
|
|
1682
|
+
current.finalConfidence = step.confidence;
|
|
1683
|
+
if (step.status === "completed") {
|
|
1684
|
+
current.completed = true;
|
|
1685
|
+
}
|
|
1686
|
+
});
|
|
1687
|
+
return {
|
|
1688
|
+
turns,
|
|
1689
|
+
totalRecords: filtered.length,
|
|
1690
|
+
ignoredRecords: ignored
|
|
1691
|
+
};
|
|
1692
|
+
}
|
|
1693
|
+
function toStep(record, atIndex) {
|
|
1694
|
+
const event = record.event;
|
|
1695
|
+
const confidence = scoreConfidence(record);
|
|
1696
|
+
if (event === "transition_ready") {
|
|
1697
|
+
return withCommon(record, {
|
|
1698
|
+
event,
|
|
1699
|
+
atIndex,
|
|
1700
|
+
status: "completed",
|
|
1701
|
+
confidence: 100
|
|
1702
|
+
});
|
|
1703
|
+
}
|
|
1704
|
+
if (event === "debounce_reject_signal" || event === "debounce_reject_status") {
|
|
1705
|
+
return withCommon(record, {
|
|
1706
|
+
event,
|
|
1707
|
+
atIndex,
|
|
1708
|
+
status: "rejected",
|
|
1709
|
+
confidence
|
|
1710
|
+
});
|
|
1711
|
+
}
|
|
1712
|
+
if (record.detectLoading) {
|
|
1713
|
+
return withCommon(record, {
|
|
1714
|
+
event,
|
|
1715
|
+
atIndex,
|
|
1716
|
+
status: "active_loading",
|
|
1717
|
+
confidence
|
|
1718
|
+
});
|
|
1719
|
+
}
|
|
1720
|
+
if (event === "debounce_fire" && record.signal) {
|
|
1721
|
+
return withCommon(record, {
|
|
1722
|
+
event,
|
|
1723
|
+
atIndex,
|
|
1724
|
+
status: "likely_complete",
|
|
1725
|
+
confidence
|
|
1726
|
+
});
|
|
1727
|
+
}
|
|
1728
|
+
if (event === "busy_signal" || event === "debounce_schedule" || event === "debounce_fire") {
|
|
1729
|
+
return withCommon(record, {
|
|
1730
|
+
event,
|
|
1731
|
+
atIndex,
|
|
1732
|
+
status: "active",
|
|
1733
|
+
confidence
|
|
1734
|
+
});
|
|
1735
|
+
}
|
|
1736
|
+
return null;
|
|
1737
|
+
}
|
|
1738
|
+
function scoreConfidence(record) {
|
|
1739
|
+
let score = 10;
|
|
1740
|
+
if (record.detectLoading) score -= 40;
|
|
1741
|
+
if (record.detectReady) score += 20;
|
|
1742
|
+
if (record.detectTaskComplete) score += 45;
|
|
1743
|
+
if (record.signal) score += 20;
|
|
1744
|
+
if (record.event === "debounce_reject_signal" || record.event === "debounce_reject_status") {
|
|
1745
|
+
score -= 30;
|
|
1746
|
+
}
|
|
1747
|
+
if (record.event === "transition_ready") score = 100;
|
|
1748
|
+
if (score < 0) return 0;
|
|
1749
|
+
if (score > 100) return 100;
|
|
1750
|
+
return score;
|
|
1751
|
+
}
|
|
1752
|
+
function withCommon(record, step) {
|
|
1753
|
+
return {
|
|
1754
|
+
...step,
|
|
1755
|
+
signal: record.signal,
|
|
1756
|
+
detectTaskComplete: record.detectTaskComplete,
|
|
1757
|
+
detectReady: record.detectReady,
|
|
1758
|
+
detectLoading: record.detectLoading
|
|
1759
|
+
};
|
|
1760
|
+
}
|
|
1761
|
+
function asString(value) {
|
|
1762
|
+
return typeof value === "string" ? value : void 0;
|
|
1763
|
+
}
|
|
1764
|
+
function asBool(value) {
|
|
1765
|
+
return typeof value === "boolean" ? value : void 0;
|
|
1766
|
+
}
|
|
1767
|
+
function asNumber(value) {
|
|
1768
|
+
return typeof value === "number" ? value : void 0;
|
|
1769
|
+
}
|
|
1770
|
+
function asTimestamp(value) {
|
|
1771
|
+
if (typeof value === "string" || typeof value === "number" || value instanceof Date) {
|
|
1772
|
+
return value;
|
|
1773
|
+
}
|
|
1774
|
+
return void 0;
|
|
1775
|
+
}
|
|
1776
|
+
|
|
1535
1777
|
// src/adapters/base-adapter.ts
|
|
1536
1778
|
import { spawn } from "child_process";
|
|
1537
1779
|
var BaseCLIAdapter = class {
|
|
@@ -1761,7 +2003,9 @@ var BaseCLIAdapter = class {
|
|
|
1761
2003
|
*/
|
|
1762
2004
|
stripAnsi(str) {
|
|
1763
2005
|
const withSpaces = str.replace(/\x1b\[\d*C/g, " ");
|
|
1764
|
-
|
|
2006
|
+
const withoutOsc = withSpaces.replace(/\x1b\](?:[^\x07\x1b]|\x1b[^\\])*(?:\x07|\x1b\\)/g, "");
|
|
2007
|
+
const withoutDcs = withoutOsc.replace(/\x1bP(?:[^\x1b]|\x1b[^\\])*\x1b\\/g, "");
|
|
2008
|
+
return withoutDcs.replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, "");
|
|
1765
2009
|
}
|
|
1766
2010
|
};
|
|
1767
2011
|
|
|
@@ -2455,8 +2699,10 @@ export {
|
|
|
2455
2699
|
PTYSession,
|
|
2456
2700
|
SPECIAL_KEYS,
|
|
2457
2701
|
ShellAdapter,
|
|
2702
|
+
buildTaskCompletionTimeline,
|
|
2458
2703
|
createAdapter,
|
|
2459
2704
|
createPTYManager,
|
|
2705
|
+
extractTaskCompletionTraceRecords,
|
|
2460
2706
|
isBun
|
|
2461
2707
|
};
|
|
2462
2708
|
//# sourceMappingURL=index.mjs.map
|