@songsid/agend 2.1.4-beta.12 → 2.1.4-beta.14
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/backend/codex.js +29 -3
- package/dist/backend/codex.js.map +1 -1
- package/dist/backend/kiro.d.ts +1 -0
- package/dist/backend/kiro.js +12 -0
- package/dist/backend/kiro.js.map +1 -1
- package/dist/channel/mcp-tools.js +3 -1
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/cli.js +23 -12
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +2 -0
- package/dist/config.js +5 -1
- package/dist/config.js.map +1 -1
- package/dist/daemon.d.ts +33 -3
- package/dist/daemon.js +99 -6
- package/dist/daemon.js.map +1 -1
- package/dist/fleet-manager.d.ts +52 -1
- package/dist/fleet-manager.js +174 -7
- package/dist/fleet-manager.js.map +1 -1
- package/dist/fleet-yaml-slim.d.ts +10 -0
- package/dist/fleet-yaml-slim.js +59 -0
- package/dist/fleet-yaml-slim.js.map +1 -0
- package/dist/general-knowledge/skills/model-discovery/SKILL.md +63 -16
- package/dist/outbound-handlers.d.ts +4 -0
- package/dist/outbound-handlers.js +36 -1
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +4 -0
- package/dist/outbound-schemas.js +6 -0
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/service-installer.js +1 -0
- package/dist/service-installer.js.map +1 -1
- package/package.json +1 -1
package/dist/daemon.d.ts
CHANGED
|
@@ -11,6 +11,8 @@ export declare function buildInstructionReloadNotice(binaryName: string, instanc
|
|
|
11
11
|
export declare const DEFAULT_STUCK_TIMEOUT_MS: number;
|
|
12
12
|
export declare const DEFAULT_STATE_IDLE_DEBOUNCE_MS = 2000;
|
|
13
13
|
export declare const DEFAULT_STATE_SAFETY_SWEEP_MS = 60000;
|
|
14
|
+
/** A foreground server/port-forward should hand control back or be acknowledged. */
|
|
15
|
+
export declare const DEFAULT_BLOCKING_PROCESS_GRACE_MS: number;
|
|
14
16
|
/**
|
|
15
17
|
* Whether two working directories belong to the same project, so a fleet-scoped
|
|
16
18
|
* decision recorded in one reaches the other. Covers the worktree/checkout
|
|
@@ -142,10 +144,13 @@ export declare class PaneStateMachine {
|
|
|
142
144
|
* working" and a finished turn would not be seen as idle until the next 60s
|
|
143
145
|
* safety sweep. Output timestamps are the honest liveness signal here, and
|
|
144
146
|
* the caller has them.
|
|
147
|
+
* @param opts.forceBusy the backend has positively identified a still-running
|
|
148
|
+
* foreground tool even though its TUI keeps an old ready footer visible.
|
|
145
149
|
*/
|
|
146
150
|
observe(pane: string, now?: number, opts?: {
|
|
147
151
|
settled?: boolean;
|
|
148
152
|
changeAt?: number;
|
|
153
|
+
forceBusy?: boolean;
|
|
149
154
|
}): InstanceStateSnapshot;
|
|
150
155
|
/** Record pane motion from tmux control mode without capturing pane content. */
|
|
151
156
|
recordOutput(now?: number): InstanceStateSnapshot;
|
|
@@ -258,6 +263,30 @@ export declare class InteractivePromptDetector {
|
|
|
258
263
|
observe(pane: string, now?: number, outputAt?: number): InteractivePromptDetection | null;
|
|
259
264
|
reset(): void;
|
|
260
265
|
}
|
|
266
|
+
export interface BlockingProcessDetection {
|
|
267
|
+
activity: string;
|
|
268
|
+
evidence: string;
|
|
269
|
+
blockedForMs: number;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Detect a foreground shell tool which has become a long-lived server.
|
|
273
|
+
*
|
|
274
|
+
* Unlike the generic pane-stuck clock, this clock deliberately survives new
|
|
275
|
+
* output: access logs and `Handling connection` lines are exactly why a
|
|
276
|
+
* foreground server can remain blocked forever without ever looking silent.
|
|
277
|
+
* Once a server marker is seen, the still-running tool identity is the stable
|
|
278
|
+
* signal; the marker may scroll out of the 32-row pane.
|
|
279
|
+
*/
|
|
280
|
+
export declare class BlockingProcessDetector {
|
|
281
|
+
private readonly graceMs;
|
|
282
|
+
private activity;
|
|
283
|
+
private evidence;
|
|
284
|
+
private detectedAt;
|
|
285
|
+
private notified;
|
|
286
|
+
constructor(graceMs?: number);
|
|
287
|
+
observe(pane: string, activity: string | null, now?: number): BlockingProcessDetection | null;
|
|
288
|
+
reset(): void;
|
|
289
|
+
}
|
|
261
290
|
export declare class Daemon extends EventEmitter {
|
|
262
291
|
private name;
|
|
263
292
|
private config;
|
|
@@ -376,6 +405,7 @@ export declare class Daemon extends EventEmitter {
|
|
|
376
405
|
private lastProgressBroadcastAt;
|
|
377
406
|
private errorMonitorTimer;
|
|
378
407
|
private readonly interactivePromptDetector;
|
|
408
|
+
private readonly blockingProcessDetector;
|
|
379
409
|
/** Same 5-min gate the error monitor uses, so a dead MCP server alerts once. */
|
|
380
410
|
private static readonly MCP_DEATH_COOLDOWN_MS;
|
|
381
411
|
private lastMcpDeathNotifiedAt;
|
|
@@ -554,9 +584,9 @@ export declare class Daemon extends EventEmitter {
|
|
|
554
584
|
* Tell the fleet manager what this instance is doing right now, for the live
|
|
555
585
|
* progress line on the cancel button.
|
|
556
586
|
*
|
|
557
|
-
*
|
|
558
|
-
*
|
|
559
|
-
*
|
|
587
|
+
* Primarily cosmetic. The foreground-process detector also uses a current
|
|
588
|
+
* shell activity as one of several positive signals, but never makes a state
|
|
589
|
+
* decision from an arbitrary activity label alone.
|
|
560
590
|
*
|
|
561
591
|
* Repeats are dropped: the ticker only edits the channel message when the text
|
|
562
592
|
* changes, and a stream of identical broadcasts would defeat that.
|
package/dist/daemon.js
CHANGED
|
@@ -47,6 +47,8 @@ export function buildInstructionReloadNotice(binaryName, instanceName, instanceD
|
|
|
47
47
|
export const DEFAULT_STUCK_TIMEOUT_MS = 10 * 60_000;
|
|
48
48
|
export const DEFAULT_STATE_IDLE_DEBOUNCE_MS = 2_000;
|
|
49
49
|
export const DEFAULT_STATE_SAFETY_SWEEP_MS = 60_000;
|
|
50
|
+
/** A foreground server/port-forward should hand control back or be acknowledged. */
|
|
51
|
+
export const DEFAULT_BLOCKING_PROCESS_GRACE_MS = 2 * 60_000;
|
|
50
52
|
const LAST_INBOUND_FILE = "last-inbound-at";
|
|
51
53
|
/** Minimum gap between "health check is failing" notifications for one instance. */
|
|
52
54
|
const HEALTH_ERROR_NOTIFY_INTERVAL_MS = 10 * 60_000;
|
|
@@ -281,9 +283,11 @@ export class PaneStateMachine {
|
|
|
281
283
|
* working" and a finished turn would not be seen as idle until the next 60s
|
|
282
284
|
* safety sweep. Output timestamps are the honest liveness signal here, and
|
|
283
285
|
* the caller has them.
|
|
286
|
+
* @param opts.forceBusy the backend has positively identified a still-running
|
|
287
|
+
* foreground tool even though its TUI keeps an old ready footer visible.
|
|
284
288
|
*/
|
|
285
289
|
observe(pane, now = Date.now(), opts = {}) {
|
|
286
|
-
const { settled = false, changeAt = now } = opts;
|
|
290
|
+
const { settled = false, changeAt = now, forceBusy = false } = opts;
|
|
287
291
|
const paneHash = createHash("sha256").update(pane).digest("hex");
|
|
288
292
|
const firstObservation = this.lastPaneHash === null;
|
|
289
293
|
const paneChanged = this.lastPaneHash !== paneHash;
|
|
@@ -292,7 +296,10 @@ export class PaneStateMachine {
|
|
|
292
296
|
this.lastPaneChangeAt = changeAt;
|
|
293
297
|
}
|
|
294
298
|
this.lastObservedAt = now;
|
|
295
|
-
|
|
299
|
+
// Some TUIs keep their ready footer visible while a foreground tool owns
|
|
300
|
+
// stdin. Backends can positively identify that tool from the pane; in that
|
|
301
|
+
// case the old prompt must not clear pending work or retire Cancel.
|
|
302
|
+
const ready = !forceBusy && this.isReady(pane);
|
|
296
303
|
const nextState = firstObservation
|
|
297
304
|
? ready ? "idle" : "working"
|
|
298
305
|
: paneChanged && !settled
|
|
@@ -593,6 +600,69 @@ export class InteractivePromptDetector {
|
|
|
593
600
|
this.notifiedSignature = null;
|
|
594
601
|
}
|
|
595
602
|
}
|
|
603
|
+
const BLOCKING_PROCESS_PATTERNS = [
|
|
604
|
+
// kubectl port-forward (including a backgrounded child whose inherited
|
|
605
|
+
// stdout keeps the parent shell tool open).
|
|
606
|
+
/^\s*(?:Forwarding from\s+(?:127\.0\.0\.1|localhost|\[::1\]):\d+\s+->\s+\d+|Handling connection for\s+\d+)\s*$/im,
|
|
607
|
+
// Common development servers. Keep these line-shaped and require an address
|
|
608
|
+
// or port so prose such as "the server is running" does not arm the detector.
|
|
609
|
+
/^\s*(?:(?:INFO:\s*)?Uvicorn running on|Serving HTTP on|Listening on|Server (?:is )?(?:listening|running) (?:at|on)|Application startup complete[^\n]*(?:port|https?:\/\/))[^\n]*(?:https?:\/\/|\b(?:localhost|127\.0\.0\.1|0\.0\.0\.0|\[::\]|port)\b)[^\n]*$/im,
|
|
610
|
+
];
|
|
611
|
+
/**
|
|
612
|
+
* Detect a foreground shell tool which has become a long-lived server.
|
|
613
|
+
*
|
|
614
|
+
* Unlike the generic pane-stuck clock, this clock deliberately survives new
|
|
615
|
+
* output: access logs and `Handling connection` lines are exactly why a
|
|
616
|
+
* foreground server can remain blocked forever without ever looking silent.
|
|
617
|
+
* Once a server marker is seen, the still-running tool identity is the stable
|
|
618
|
+
* signal; the marker may scroll out of the 32-row pane.
|
|
619
|
+
*/
|
|
620
|
+
export class BlockingProcessDetector {
|
|
621
|
+
graceMs;
|
|
622
|
+
activity = null;
|
|
623
|
+
evidence = null;
|
|
624
|
+
detectedAt = 0;
|
|
625
|
+
notified = false;
|
|
626
|
+
constructor(graceMs = DEFAULT_BLOCKING_PROCESS_GRACE_MS) {
|
|
627
|
+
this.graceMs = graceMs;
|
|
628
|
+
}
|
|
629
|
+
observe(pane, activity, now = Date.now()) {
|
|
630
|
+
const shellActivity = activity && /^(?:shell|bash|exec_command|terminal|command)(?::|$)/i.test(activity);
|
|
631
|
+
if (!shellActivity) {
|
|
632
|
+
this.reset();
|
|
633
|
+
return null;
|
|
634
|
+
}
|
|
635
|
+
if (activity !== this.activity) {
|
|
636
|
+
this.reset();
|
|
637
|
+
this.activity = activity;
|
|
638
|
+
}
|
|
639
|
+
if (!this.evidence) {
|
|
640
|
+
const tail = sanitizePaneTail(pane, 40).join("\n");
|
|
641
|
+
for (const pattern of BLOCKING_PROCESS_PATTERNS) {
|
|
642
|
+
const match = tail.match(pattern);
|
|
643
|
+
if (!match)
|
|
644
|
+
continue;
|
|
645
|
+
this.evidence = match[0].trim().slice(0, 200);
|
|
646
|
+
this.detectedAt = now;
|
|
647
|
+
break;
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
if (!this.evidence || this.notified || now - this.detectedAt < this.graceMs)
|
|
651
|
+
return null;
|
|
652
|
+
this.notified = true;
|
|
653
|
+
return {
|
|
654
|
+
activity,
|
|
655
|
+
evidence: this.evidence,
|
|
656
|
+
blockedForMs: now - this.detectedAt,
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
reset() {
|
|
660
|
+
this.activity = null;
|
|
661
|
+
this.evidence = null;
|
|
662
|
+
this.detectedAt = 0;
|
|
663
|
+
this.notified = false;
|
|
664
|
+
}
|
|
665
|
+
}
|
|
596
666
|
export class Daemon extends EventEmitter {
|
|
597
667
|
name;
|
|
598
668
|
config;
|
|
@@ -727,6 +797,7 @@ export class Daemon extends EventEmitter {
|
|
|
727
797
|
// PTY error pattern monitoring
|
|
728
798
|
errorMonitorTimer = null;
|
|
729
799
|
interactivePromptDetector = new InteractivePromptDetector();
|
|
800
|
+
blockingProcessDetector = new BlockingProcessDetector();
|
|
730
801
|
/** Same 5-min gate the error monitor uses, so a dead MCP server alerts once. */
|
|
731
802
|
static MCP_DEATH_COOLDOWN_MS = 5 * 60_000;
|
|
732
803
|
lastMcpDeathNotifiedAt = 0;
|
|
@@ -1606,6 +1677,25 @@ export class Daemon extends EventEmitter {
|
|
|
1606
1677
|
// A prompt is not an error and must not enter the PTY recovery gate.
|
|
1607
1678
|
// Continue scanning real errors in this same snapshot.
|
|
1608
1679
|
}
|
|
1680
|
+
// If a backend can derive activity from this exact pane, trust its
|
|
1681
|
+
// explicit null (tool completed) instead of falling back to a possibly
|
|
1682
|
+
// stale transcript activity from the previous scan.
|
|
1683
|
+
const paneActivity = this.backend?.getPaneActivity
|
|
1684
|
+
? this.backend.getPaneActivity(pane)
|
|
1685
|
+
: this.currentActivity;
|
|
1686
|
+
const hasPendingWork = this.pendingWork.hasPendingWork();
|
|
1687
|
+
if (!hasPendingWork)
|
|
1688
|
+
this.blockingProcessDetector.reset();
|
|
1689
|
+
const blockingProcess = hasPendingWork
|
|
1690
|
+
? this.blockingProcessDetector.observe(pane, paneActivity, Date.now())
|
|
1691
|
+
: null;
|
|
1692
|
+
if (blockingProcess) {
|
|
1693
|
+
// Reuse the hang-notification bridge: it offers explicit Restart/Wait
|
|
1694
|
+
// choices and, unlike pty_error, does not retire a still-useful Cancel
|
|
1695
|
+
// button merely because a foreground process owns stdin.
|
|
1696
|
+
this.logger.warn(blockingProcess, "Foreground process is blocking the agent input loop");
|
|
1697
|
+
this.hangDetector?.emit("hang", { unchangedForMs: blockingProcess.blockedForMs });
|
|
1698
|
+
}
|
|
1609
1699
|
// Auto-dismiss runtime dialogs (e.g. Codex rate limit model switch)
|
|
1610
1700
|
for (const dialog of dialogs) {
|
|
1611
1701
|
if (!dialog.pattern.test(pane))
|
|
@@ -2253,6 +2343,7 @@ export class Daemon extends EventEmitter {
|
|
|
2253
2343
|
// mismatched spinner frame was enough to report idle mid-turn.
|
|
2254
2344
|
const settled = this.instanceStateLastOutputAt === 0
|
|
2255
2345
|
|| captureStartedAt - this.instanceStateLastOutputAt >= this.instanceStateIdleDebounceMs;
|
|
2346
|
+
const paneActivity = this.backend?.getPaneActivity?.(pane) ?? null;
|
|
2256
2347
|
// The two times are genuinely different and both matter: the content
|
|
2257
2348
|
// changed when tmux reported output (observedChangeAt), but idle/stuck are
|
|
2258
2349
|
// decisions about *now*. The old double-observe expressed that by calling
|
|
@@ -2260,6 +2351,7 @@ export class Daemon extends EventEmitter {
|
|
|
2260
2351
|
const snapshot = this.instanceStateMachine.observe(pane, Date.now(), {
|
|
2261
2352
|
settled,
|
|
2262
2353
|
changeAt: observedChangeAt,
|
|
2354
|
+
forceBusy: paneActivity !== null,
|
|
2263
2355
|
});
|
|
2264
2356
|
this.applyInstanceStateSnapshot(snapshot, pane);
|
|
2265
2357
|
// Backends without a transcript feed can still say what they are doing, if
|
|
@@ -2267,7 +2359,7 @@ export class Daemon extends EventEmitter {
|
|
|
2267
2359
|
// extra tmux call. Cadence is the capture cadence (idle debounce + the 60s
|
|
2268
2360
|
// safety sweep), which matches the progress ticker's own 60s interval.
|
|
2269
2361
|
if (this.backend?.getPaneActivity) {
|
|
2270
|
-
this.publishActivity(snapshot.state === "idle" ? null :
|
|
2362
|
+
this.publishActivity(snapshot.state === "idle" ? null : paneActivity);
|
|
2271
2363
|
}
|
|
2272
2364
|
if (snapshot.state === "idle") {
|
|
2273
2365
|
this.clearInstanceStateStuckTimer();
|
|
@@ -2397,6 +2489,7 @@ export class Daemon extends EventEmitter {
|
|
|
2397
2489
|
// fire into a stopped daemon).
|
|
2398
2490
|
this.clearMcpRestartRequest();
|
|
2399
2491
|
this.interactivePromptDetector.reset();
|
|
2492
|
+
this.blockingProcessDetector.reset();
|
|
2400
2493
|
this.stopInstanceStateMonitor();
|
|
2401
2494
|
this.transcriptMonitor?.stop();
|
|
2402
2495
|
this.guardian?.stop();
|
|
@@ -2422,9 +2515,9 @@ export class Daemon extends EventEmitter {
|
|
|
2422
2515
|
* Tell the fleet manager what this instance is doing right now, for the live
|
|
2423
2516
|
* progress line on the cancel button.
|
|
2424
2517
|
*
|
|
2425
|
-
*
|
|
2426
|
-
*
|
|
2427
|
-
*
|
|
2518
|
+
* Primarily cosmetic. The foreground-process detector also uses a current
|
|
2519
|
+
* shell activity as one of several positive signals, but never makes a state
|
|
2520
|
+
* decision from an arbitrary activity label alone.
|
|
2428
2521
|
*
|
|
2429
2522
|
* Repeats are dropped: the ticker only edits the channel message when the text
|
|
2430
2523
|
* changes, and a stream of identical broadcasts would defeat that.
|