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/README.md
CHANGED
|
@@ -10,6 +10,7 @@ PTY session manager with lifecycle management, pluggable adapters, and blocking
|
|
|
10
10
|
- **Auto-response rules** - Automatically respond to known prompts with text or key sequences
|
|
11
11
|
- **TUI menu navigation** - Navigate arrow-key menus via `selectMenuOption()` and key-sequence rules
|
|
12
12
|
- **Stall detection** - Content-based stall detection with pluggable external classifiers
|
|
13
|
+
- **Task completion detection** - Adapter-level fast-path that short-circuits the LLM stall classifier when the CLI returns to its idle prompt
|
|
13
14
|
- **Terminal attachment** - Attach to sessions for raw I/O streaming
|
|
14
15
|
- **Special key support** - Send Ctrl, Alt, Shift, and function key combinations via `sendKeys()`
|
|
15
16
|
- **Bracketed paste** - Proper paste handling with bracketed paste mode support
|
|
@@ -130,6 +131,11 @@ class MyCLIAdapter extends BaseCLIAdapter {
|
|
|
130
131
|
return /ready>/.test(output);
|
|
131
132
|
}
|
|
132
133
|
|
|
134
|
+
// Optional: high-confidence task completion detection
|
|
135
|
+
detectTaskComplete(output) {
|
|
136
|
+
return /done in \d+s/.test(output) && /ready>/.test(output);
|
|
137
|
+
}
|
|
138
|
+
|
|
133
139
|
parseOutput(output) {
|
|
134
140
|
return {
|
|
135
141
|
type: 'response',
|
|
@@ -189,6 +195,8 @@ class PTYManager extends EventEmitter {
|
|
|
189
195
|
| `blocking_prompt` | `SessionHandle, promptInfo, autoResponded` | Prompt detected |
|
|
190
196
|
| `message` | `SessionMessage` | Parsed message received |
|
|
191
197
|
| `question` | `SessionHandle, question` | Question detected |
|
|
198
|
+
| `stall_detected` | `SessionHandle, recentOutput, stallDurationMs` | Output stalled, needs classification |
|
|
199
|
+
| `task_complete` | `SessionHandle` | Agent finished task, returned to idle |
|
|
192
200
|
|
|
193
201
|
### SpawnConfig
|
|
194
202
|
|
|
@@ -435,9 +443,11 @@ Adapters can declare `usesTuiMenus: true` to indicate they use arrow-key menus i
|
|
|
435
443
|
await session.selectMenuOption(2); // Sends Down, Down, Enter with 50ms delays
|
|
436
444
|
```
|
|
437
445
|
|
|
438
|
-
## Stall Detection
|
|
446
|
+
## Stall Detection & Task Completion
|
|
439
447
|
|
|
440
|
-
Content-based stall detection monitors sessions for output that stops changing. When a stall is detected, the session
|
|
448
|
+
Content-based stall detection monitors sessions for output that stops changing. When a stall is detected, the session first tries the adapter's `detectTaskComplete()` fast-path. If the adapter recognizes the output as a completed task (e.g. duration summary + idle prompt), it transitions directly to `ready` and emits `task_complete` — skipping the expensive LLM stall classifier entirely.
|
|
449
|
+
|
|
450
|
+
If the adapter doesn't recognize the output, the session falls back to emitting `stall_detected` for external classification.
|
|
441
451
|
|
|
442
452
|
```typescript
|
|
443
453
|
// Enable stall detection with a pluggable classifier
|
|
@@ -460,6 +470,30 @@ const session = await manager.spawn({
|
|
|
460
470
|
});
|
|
461
471
|
```
|
|
462
472
|
|
|
473
|
+
### Adapter-Level Task Completion (Fast Path)
|
|
474
|
+
|
|
475
|
+
Adapters can implement `detectTaskComplete(output)` to recognize high-confidence completion patterns specific to their CLI. This avoids the latency and cost of an LLM classifier call.
|
|
476
|
+
|
|
477
|
+
```typescript
|
|
478
|
+
class MyCLIAdapter extends BaseCLIAdapter {
|
|
479
|
+
// ...
|
|
480
|
+
|
|
481
|
+
detectTaskComplete(output: string): boolean {
|
|
482
|
+
// Match CLI-specific patterns that indicate work is done
|
|
483
|
+
return /completed in \d+s/.test(output) && /my-cli>/.test(output);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
The default `BaseCLIAdapter` implementation delegates to `detectReady()`. Coding agent adapters override this with CLI-specific patterns:
|
|
489
|
+
|
|
490
|
+
| Adapter | Completion Indicators |
|
|
491
|
+
|---------|----------------------|
|
|
492
|
+
| Claude Code | Turn duration (`Cooked for 3m 12s`) + `❯` prompt |
|
|
493
|
+
| Gemini CLI | `◇ Ready` window title, `Type your message` composer |
|
|
494
|
+
| Codex | `Worked for 1m 05s` separator + `›` prompt |
|
|
495
|
+
| Aider | `Aider is waiting for your input`, mode prompts with edit markers |
|
|
496
|
+
|
|
463
497
|
## Blocking Prompt Types
|
|
464
498
|
|
|
465
499
|
The library recognizes these blocking prompt types:
|
package/dist/index.d.mts
CHANGED
|
@@ -334,6 +334,15 @@ interface CLIAdapter {
|
|
|
334
334
|
* Get prompt pattern to detect when CLI is waiting for input
|
|
335
335
|
*/
|
|
336
336
|
getPromptPattern(): RegExp;
|
|
337
|
+
/**
|
|
338
|
+
* Optional: Detect if the CLI has completed a task and returned to its idle prompt.
|
|
339
|
+
* More specific than detectReady — matches high-confidence completion indicators
|
|
340
|
+
* (e.g. duration summaries, explicit "done" messages) alongside the idle prompt.
|
|
341
|
+
*
|
|
342
|
+
* Used as a fast-path in stall detection to avoid expensive LLM classifier calls.
|
|
343
|
+
* If not implemented, the stall classifier is used as the fallback.
|
|
344
|
+
*/
|
|
345
|
+
detectTaskComplete?(output: string): boolean;
|
|
337
346
|
/**
|
|
338
347
|
* Optional: Validate that the CLI is installed and accessible
|
|
339
348
|
*/
|
|
@@ -405,6 +414,8 @@ interface PTYSessionEvents {
|
|
|
405
414
|
exit: (code: number) => void;
|
|
406
415
|
error: (error: Error) => void;
|
|
407
416
|
stall_detected: (recentOutput: string, stallDurationMs: number) => void;
|
|
417
|
+
status_changed: (status: SessionStatus) => void;
|
|
418
|
+
task_complete: () => void;
|
|
408
419
|
}
|
|
409
420
|
/**
|
|
410
421
|
* Special key mappings to escape sequences
|
|
@@ -433,6 +444,8 @@ declare class PTYSession extends EventEmitter {
|
|
|
433
444
|
private _lastStallHash;
|
|
434
445
|
private _stallStartedAt;
|
|
435
446
|
private _lastContentHash;
|
|
447
|
+
private _taskCompleteTimer;
|
|
448
|
+
private static readonly TASK_COMPLETE_DEBOUNCE_MS;
|
|
436
449
|
readonly id: string;
|
|
437
450
|
readonly config: SpawnConfig;
|
|
438
451
|
constructor(adapter: CLIAdapter, config: SpawnConfig, logger?: Logger, stallDetectionEnabled?: boolean, defaultStallTimeoutMs?: number);
|
|
@@ -501,6 +514,17 @@ declare class PTYSession extends EventEmitter {
|
|
|
501
514
|
* Called by the manager after onStallClassify resolves.
|
|
502
515
|
*/
|
|
503
516
|
handleStallClassification(classification: StallClassification | null): void;
|
|
517
|
+
/**
|
|
518
|
+
* Schedule a task_complete transition after a debounce period.
|
|
519
|
+
* If new non-whitespace output arrives before the timer fires,
|
|
520
|
+
* the timer is cancelled (by cancelTaskComplete in onData).
|
|
521
|
+
*/
|
|
522
|
+
private scheduleTaskComplete;
|
|
523
|
+
/**
|
|
524
|
+
* Cancel a pending task_complete timer (new output arrived that
|
|
525
|
+
* doesn't match the idle prompt, so the agent is still working).
|
|
526
|
+
*/
|
|
527
|
+
private cancelTaskComplete;
|
|
504
528
|
/**
|
|
505
529
|
* Start the PTY session
|
|
506
530
|
*/
|
|
@@ -612,6 +636,8 @@ interface PTYManagerEvents {
|
|
|
612
636
|
message: (message: SessionMessage) => void;
|
|
613
637
|
question: (session: SessionHandle, question: string) => void;
|
|
614
638
|
stall_detected: (session: SessionHandle, recentOutput: string, stallDurationMs: number) => void;
|
|
639
|
+
session_status_changed: (session: SessionHandle) => void;
|
|
640
|
+
task_complete: (session: SessionHandle) => void;
|
|
615
641
|
}
|
|
616
642
|
declare class PTYManager extends EventEmitter {
|
|
617
643
|
private sessions;
|
|
@@ -757,6 +783,12 @@ declare abstract class BaseCLIAdapter implements CLIAdapter {
|
|
|
757
783
|
* Subclasses should override for CLI-specific detection.
|
|
758
784
|
*/
|
|
759
785
|
detectBlockingPrompt(output: string): BlockingPromptDetection;
|
|
786
|
+
/**
|
|
787
|
+
* Default task completion detection — delegates to detectReady().
|
|
788
|
+
* Subclasses should override to match high-confidence completion patterns
|
|
789
|
+
* (e.g. duration summaries) that short-circuit the LLM stall classifier.
|
|
790
|
+
*/
|
|
791
|
+
detectTaskComplete(output: string): boolean;
|
|
760
792
|
/**
|
|
761
793
|
* Default input formatting - just return as-is
|
|
762
794
|
*/
|
package/dist/index.d.ts
CHANGED
|
@@ -334,6 +334,15 @@ interface CLIAdapter {
|
|
|
334
334
|
* Get prompt pattern to detect when CLI is waiting for input
|
|
335
335
|
*/
|
|
336
336
|
getPromptPattern(): RegExp;
|
|
337
|
+
/**
|
|
338
|
+
* Optional: Detect if the CLI has completed a task and returned to its idle prompt.
|
|
339
|
+
* More specific than detectReady — matches high-confidence completion indicators
|
|
340
|
+
* (e.g. duration summaries, explicit "done" messages) alongside the idle prompt.
|
|
341
|
+
*
|
|
342
|
+
* Used as a fast-path in stall detection to avoid expensive LLM classifier calls.
|
|
343
|
+
* If not implemented, the stall classifier is used as the fallback.
|
|
344
|
+
*/
|
|
345
|
+
detectTaskComplete?(output: string): boolean;
|
|
337
346
|
/**
|
|
338
347
|
* Optional: Validate that the CLI is installed and accessible
|
|
339
348
|
*/
|
|
@@ -405,6 +414,8 @@ interface PTYSessionEvents {
|
|
|
405
414
|
exit: (code: number) => void;
|
|
406
415
|
error: (error: Error) => void;
|
|
407
416
|
stall_detected: (recentOutput: string, stallDurationMs: number) => void;
|
|
417
|
+
status_changed: (status: SessionStatus) => void;
|
|
418
|
+
task_complete: () => void;
|
|
408
419
|
}
|
|
409
420
|
/**
|
|
410
421
|
* Special key mappings to escape sequences
|
|
@@ -433,6 +444,8 @@ declare class PTYSession extends EventEmitter {
|
|
|
433
444
|
private _lastStallHash;
|
|
434
445
|
private _stallStartedAt;
|
|
435
446
|
private _lastContentHash;
|
|
447
|
+
private _taskCompleteTimer;
|
|
448
|
+
private static readonly TASK_COMPLETE_DEBOUNCE_MS;
|
|
436
449
|
readonly id: string;
|
|
437
450
|
readonly config: SpawnConfig;
|
|
438
451
|
constructor(adapter: CLIAdapter, config: SpawnConfig, logger?: Logger, stallDetectionEnabled?: boolean, defaultStallTimeoutMs?: number);
|
|
@@ -501,6 +514,17 @@ declare class PTYSession extends EventEmitter {
|
|
|
501
514
|
* Called by the manager after onStallClassify resolves.
|
|
502
515
|
*/
|
|
503
516
|
handleStallClassification(classification: StallClassification | null): void;
|
|
517
|
+
/**
|
|
518
|
+
* Schedule a task_complete transition after a debounce period.
|
|
519
|
+
* If new non-whitespace output arrives before the timer fires,
|
|
520
|
+
* the timer is cancelled (by cancelTaskComplete in onData).
|
|
521
|
+
*/
|
|
522
|
+
private scheduleTaskComplete;
|
|
523
|
+
/**
|
|
524
|
+
* Cancel a pending task_complete timer (new output arrived that
|
|
525
|
+
* doesn't match the idle prompt, so the agent is still working).
|
|
526
|
+
*/
|
|
527
|
+
private cancelTaskComplete;
|
|
504
528
|
/**
|
|
505
529
|
* Start the PTY session
|
|
506
530
|
*/
|
|
@@ -612,6 +636,8 @@ interface PTYManagerEvents {
|
|
|
612
636
|
message: (message: SessionMessage) => void;
|
|
613
637
|
question: (session: SessionHandle, question: string) => void;
|
|
614
638
|
stall_detected: (session: SessionHandle, recentOutput: string, stallDurationMs: number) => void;
|
|
639
|
+
session_status_changed: (session: SessionHandle) => void;
|
|
640
|
+
task_complete: (session: SessionHandle) => void;
|
|
615
641
|
}
|
|
616
642
|
declare class PTYManager extends EventEmitter {
|
|
617
643
|
private sessions;
|
|
@@ -757,6 +783,12 @@ declare abstract class BaseCLIAdapter implements CLIAdapter {
|
|
|
757
783
|
* Subclasses should override for CLI-specific detection.
|
|
758
784
|
*/
|
|
759
785
|
detectBlockingPrompt(output: string): BlockingPromptDetection;
|
|
786
|
+
/**
|
|
787
|
+
* Default task completion detection — delegates to detectReady().
|
|
788
|
+
* Subclasses should override to match high-confidence completion patterns
|
|
789
|
+
* (e.g. duration summaries) that short-circuit the LLM stall classifier.
|
|
790
|
+
*/
|
|
791
|
+
detectTaskComplete(output: string): boolean;
|
|
760
792
|
/**
|
|
761
793
|
* Default input formatting - just return as-is
|
|
762
794
|
*/
|
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;
|
|
@@ -344,6 +344,9 @@ var PTYSession = class extends import_events.EventEmitter {
|
|
|
344
344
|
_lastStallHash = null;
|
|
345
345
|
_stallStartedAt = null;
|
|
346
346
|
_lastContentHash = null;
|
|
347
|
+
// Task completion detection (idle detection when busy)
|
|
348
|
+
_taskCompleteTimer = null;
|
|
349
|
+
static TASK_COMPLETE_DEBOUNCE_MS = 1500;
|
|
347
350
|
id;
|
|
348
351
|
config;
|
|
349
352
|
get status() {
|
|
@@ -483,6 +486,19 @@ var PTYSession = class extends import_events.EventEmitter {
|
|
|
483
486
|
return;
|
|
484
487
|
}
|
|
485
488
|
this._lastStallHash = hash;
|
|
489
|
+
if (this._status === "busy" && this.adapter.detectTaskComplete?.(this.outputBuffer)) {
|
|
490
|
+
this._status = "ready";
|
|
491
|
+
this._lastBlockingPromptHash = null;
|
|
492
|
+
this.outputBuffer = "";
|
|
493
|
+
this.clearStallTimer();
|
|
494
|
+
this.emit("status_changed", "ready");
|
|
495
|
+
this.emit("task_complete");
|
|
496
|
+
this.logger.info(
|
|
497
|
+
{ sessionId: this.id },
|
|
498
|
+
"Task complete (adapter fast-path) \u2014 agent returned to idle prompt"
|
|
499
|
+
);
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
486
502
|
const recentRaw = this.outputBuffer.slice(-2e3);
|
|
487
503
|
const recentOutput = this.stripAnsiForStall(recentRaw);
|
|
488
504
|
const stallDurationMs = this._stallStartedAt ? Date.now() - this._stallStartedAt : this._stallTimeoutMs;
|
|
@@ -519,10 +535,12 @@ var PTYSession = class extends import_events.EventEmitter {
|
|
|
519
535
|
* word boundaries — e.g. "Do\x1b[5Cyou" becomes "Do you", not "Doyou".
|
|
520
536
|
*/
|
|
521
537
|
stripAnsiForStall(str) {
|
|
522
|
-
let result = str.replace(/\x1b\[\d*[
|
|
538
|
+
let result = str.replace(/\x1b\[\d*[CDABGdEF]/g, " ");
|
|
523
539
|
result = result.replace(/\x1b\[\d*(?:;\d+)?[Hf]/g, " ");
|
|
540
|
+
result = result.replace(/\x1b\[\d*[JK]/g, " ");
|
|
524
541
|
result = result.replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, "");
|
|
525
|
-
result = result.replace(/[
|
|
542
|
+
result = result.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, "");
|
|
543
|
+
result = result.replace(/[│╭╰╮╯─═╌║╔╗╚╝╠╣╦╩╬┌┐└┘├┤┬┴┼●○❯❮▶◀⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏⣾⣽⣻⢿⡿⣟⣯⣷✻✶✳✢⏺←→↑↓⬆⬇◆◇▪▫■□▲△▼▽◈⟨⟩⌘⏎⏏⌫⌦⇧⇪⌥]/g, " ");
|
|
526
544
|
result = result.replace(/ {2,}/g, " ");
|
|
527
545
|
return result;
|
|
528
546
|
}
|
|
@@ -580,6 +598,39 @@ var PTYSession = class extends import_events.EventEmitter {
|
|
|
580
598
|
}
|
|
581
599
|
}
|
|
582
600
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
601
|
+
// Task Completion Detection
|
|
602
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
603
|
+
/**
|
|
604
|
+
* Schedule a task_complete transition after a debounce period.
|
|
605
|
+
* If new non-whitespace output arrives before the timer fires,
|
|
606
|
+
* the timer is cancelled (by cancelTaskComplete in onData).
|
|
607
|
+
*/
|
|
608
|
+
scheduleTaskComplete() {
|
|
609
|
+
if (this._taskCompleteTimer) return;
|
|
610
|
+
this._taskCompleteTimer = setTimeout(() => {
|
|
611
|
+
this._taskCompleteTimer = null;
|
|
612
|
+
if (this._status !== "busy") return;
|
|
613
|
+
if (!this.adapter.detectReady(this.outputBuffer)) return;
|
|
614
|
+
this._status = "ready";
|
|
615
|
+
this._lastBlockingPromptHash = null;
|
|
616
|
+
this.outputBuffer = "";
|
|
617
|
+
this.clearStallTimer();
|
|
618
|
+
this.emit("status_changed", "ready");
|
|
619
|
+
this.emit("task_complete");
|
|
620
|
+
this.logger.info({ sessionId: this.id }, "Task complete \u2014 agent returned to idle prompt");
|
|
621
|
+
}, _PTYSession.TASK_COMPLETE_DEBOUNCE_MS);
|
|
622
|
+
}
|
|
623
|
+
/**
|
|
624
|
+
* Cancel a pending task_complete timer (new output arrived that
|
|
625
|
+
* doesn't match the idle prompt, so the agent is still working).
|
|
626
|
+
*/
|
|
627
|
+
cancelTaskComplete() {
|
|
628
|
+
if (this._taskCompleteTimer) {
|
|
629
|
+
clearTimeout(this._taskCompleteTimer);
|
|
630
|
+
this._taskCompleteTimer = null;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
583
634
|
// Lifecycle
|
|
584
635
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
585
636
|
/**
|
|
@@ -641,10 +692,6 @@ var PTYSession = class extends import_events.EventEmitter {
|
|
|
641
692
|
this.resetStallTimer();
|
|
642
693
|
}
|
|
643
694
|
this.emit("output", data);
|
|
644
|
-
const blockingPrompt = this.detectAndHandleBlockingPrompt();
|
|
645
|
-
if (blockingPrompt) {
|
|
646
|
-
return;
|
|
647
|
-
}
|
|
648
695
|
if ((this._status === "starting" || this._status === "authenticating") && this.adapter.detectReady(this.outputBuffer)) {
|
|
649
696
|
this._status = "ready";
|
|
650
697
|
this._lastBlockingPromptHash = null;
|
|
@@ -654,6 +701,15 @@ var PTYSession = class extends import_events.EventEmitter {
|
|
|
654
701
|
this.logger.info({ sessionId: this.id }, "Session ready");
|
|
655
702
|
return;
|
|
656
703
|
}
|
|
704
|
+
if (this._status === "busy" && this.adapter.detectReady(this.outputBuffer)) {
|
|
705
|
+
this.scheduleTaskComplete();
|
|
706
|
+
} else {
|
|
707
|
+
this.cancelTaskComplete();
|
|
708
|
+
}
|
|
709
|
+
const blockingPrompt = this.detectAndHandleBlockingPrompt();
|
|
710
|
+
if (blockingPrompt) {
|
|
711
|
+
return;
|
|
712
|
+
}
|
|
657
713
|
if (this._status !== "ready" && this._status !== "busy") {
|
|
658
714
|
const loginDetection = this.adapter.detectLogin(this.outputBuffer);
|
|
659
715
|
if (loginDetection.required && this._status !== "authenticating") {
|
|
@@ -876,6 +932,7 @@ var PTYSession = class extends import_events.EventEmitter {
|
|
|
876
932
|
*/
|
|
877
933
|
send(message) {
|
|
878
934
|
this._status = "busy";
|
|
935
|
+
this.emit("status_changed", "busy");
|
|
879
936
|
this.resetStallTimer();
|
|
880
937
|
const msg = {
|
|
881
938
|
id: `${this.id}-msg-${++this.messageCounter}`,
|
|
@@ -986,6 +1043,7 @@ var PTYSession = class extends import_events.EventEmitter {
|
|
|
986
1043
|
if (this.ptyProcess) {
|
|
987
1044
|
this._status = "stopping";
|
|
988
1045
|
this.clearStallTimer();
|
|
1046
|
+
this.cancelTaskComplete();
|
|
989
1047
|
this.ptyProcess.kill(signal);
|
|
990
1048
|
this.logger.info({ sessionId: this.id, signal }, "Killing PTY session");
|
|
991
1049
|
}
|
|
@@ -1139,6 +1197,12 @@ var PTYManager = class extends import_events2.EventEmitter {
|
|
|
1139
1197
|
session.on("error", (error) => {
|
|
1140
1198
|
this.emit("session_error", session.toHandle(), error.message);
|
|
1141
1199
|
});
|
|
1200
|
+
session.on("status_changed", () => {
|
|
1201
|
+
this.emit("session_status_changed", session.toHandle());
|
|
1202
|
+
});
|
|
1203
|
+
session.on("task_complete", () => {
|
|
1204
|
+
this.emit("task_complete", session.toHandle());
|
|
1205
|
+
});
|
|
1142
1206
|
session.on("stall_detected", (recentOutput, stallDurationMs) => {
|
|
1143
1207
|
const handle = session.toHandle();
|
|
1144
1208
|
this.emit("stall_detected", handle, recentOutput, stallDurationMs);
|
|
@@ -1543,6 +1607,14 @@ var BaseCLIAdapter = class {
|
|
|
1543
1607
|
}
|
|
1544
1608
|
return { detected: false };
|
|
1545
1609
|
}
|
|
1610
|
+
/**
|
|
1611
|
+
* Default task completion detection — delegates to detectReady().
|
|
1612
|
+
* Subclasses should override to match high-confidence completion patterns
|
|
1613
|
+
* (e.g. duration summaries) that short-circuit the LLM stall classifier.
|
|
1614
|
+
*/
|
|
1615
|
+
detectTaskComplete(output) {
|
|
1616
|
+
return this.detectReady(output);
|
|
1617
|
+
}
|
|
1546
1618
|
/**
|
|
1547
1619
|
* Default input formatting - just return as-is
|
|
1548
1620
|
*/
|
|
@@ -1992,6 +2064,24 @@ var BunCompatiblePTYManager = class extends import_events3.EventEmitter {
|
|
|
1992
2064
|
}
|
|
1993
2065
|
break;
|
|
1994
2066
|
}
|
|
2067
|
+
case "status_changed": {
|
|
2068
|
+
const session = this.sessions.get(id);
|
|
2069
|
+
if (session) {
|
|
2070
|
+
session.status = event.status;
|
|
2071
|
+
session.lastActivityAt = /* @__PURE__ */ new Date();
|
|
2072
|
+
this.emit("session_status_changed", session);
|
|
2073
|
+
}
|
|
2074
|
+
break;
|
|
2075
|
+
}
|
|
2076
|
+
case "task_complete": {
|
|
2077
|
+
const session = this.sessions.get(id);
|
|
2078
|
+
if (session) {
|
|
2079
|
+
session.status = "ready";
|
|
2080
|
+
session.lastActivityAt = /* @__PURE__ */ new Date();
|
|
2081
|
+
this.emit("task_complete", session);
|
|
2082
|
+
}
|
|
2083
|
+
break;
|
|
2084
|
+
}
|
|
1995
2085
|
case "stall_detected": {
|
|
1996
2086
|
const session = this.sessions.get(id);
|
|
1997
2087
|
if (session) {
|