@aragon-agent/cli 0.6.17 → 0.6.18
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/CHANGELOG.md +107 -51
- package/README.md +48 -2
- package/dist/agent/controller.js +13 -0
- package/dist/agent/headless.js +7 -0
- package/dist/commands/builtins.js +18 -3
- package/dist/commands/index-build.d.ts +39 -0
- package/dist/commands/index-build.js +61 -0
- package/dist/config/cli-commands.d.ts +1 -1
- package/dist/config/cli-commands.js +3 -0
- package/dist/config/schema.d.ts +24 -4
- package/dist/config/schema.js +4 -0
- package/dist/exec/events.d.ts +4 -1
- package/dist/exec/runner.js +13 -0
- package/dist/input/csiu-keys.js +13 -2
- package/dist/input/limits.d.ts +15 -0
- package/dist/input/limits.js +15 -0
- package/dist/input/win32-input-mode.js +7 -1
- package/dist/logging/install.d.ts +9 -0
- package/dist/logging/install.js +11 -0
- package/dist/team/human-queue.d.ts +11 -0
- package/dist/team/human-queue.js +18 -1
- package/dist/team/limits.d.ts +40 -0
- package/dist/team/limits.js +41 -0
- package/dist/team/overseer.d.ts +157 -0
- package/dist/team/overseer.js +348 -0
- package/dist/team/report.js +26 -1
- package/dist/team/runtime.d.ts +71 -6
- package/dist/team/runtime.js +380 -23
- package/dist/team/subagent.d.ts +30 -1
- package/dist/team/subagent.js +8 -1
- package/dist/team/task-tool.js +4 -1
- package/dist/team/types.d.ts +63 -0
- package/dist/ui/App.d.ts +1 -1
- package/dist/ui/App.js +61 -0
- package/dist/ui/Header.js +13 -2
- package/dist/ui/PromptInput.js +11 -1
- package/dist/ui/overlays/HelpOverlay.js +1 -0
- package/package.json +2 -2
- package/skills/project-indexer/SKILL.md +163 -0
- package/skills/project-indexer/templates/cleancode-overrides.md +46 -0
- package/skills/project-indexer/templates/cleancode-template.md +94 -0
- package/skills/project-indexer/templates/config-template.md +69 -0
- package/skills/project-indexer/templates/index-template.md +129 -0
package/dist/input/limits.d.ts
CHANGED
|
@@ -64,6 +64,21 @@ export declare const PASTE_CLOSE = "\0]";
|
|
|
64
64
|
* no payload, so one constant replaces the OPEN/CLOSE pair.
|
|
65
65
|
*/
|
|
66
66
|
export declare const ENTER_NEWLINE_FRAME = "\0n";
|
|
67
|
+
/**
|
|
68
|
+
* The inline frame that carries ONE "Ctrl+I was pressed" intent from the stdin
|
|
69
|
+
* filter to `App` (project-indexer Ctrl+I trigger).
|
|
70
|
+
*
|
|
71
|
+
* WHY A FRAME AND NOT `key.ctrl && input === 'i'`. Legacy terminals encode
|
|
72
|
+
* Ctrl+I as the same byte as Tab (0x09), so by the time the stream reaches Ink
|
|
73
|
+
* the two are indistinguishable. Under win32-input-mode (`?9001h`) and kitty
|
|
74
|
+
* CSI-u the modifiers ARE known at translation time, and both translators emit
|
|
75
|
+
* this frame instead of the legacy Tab byte. Same NUL-delimiter guarantee as
|
|
76
|
+
* `ENTER_NEWLINE_FRAME`: no terminal delivers NUL for a key and pasted text is
|
|
77
|
+
* stripped of NUL, so a payload can never forge it. Terminals without keyboard
|
|
78
|
+
* enhancement keep the legacy collapse — Ctrl+I there is Tab, documented in the
|
|
79
|
+
* README keybindings table.
|
|
80
|
+
*/
|
|
81
|
+
export declare const INDEX_KEY_FRAME = "\0i";
|
|
67
82
|
/**
|
|
68
83
|
* The one channel from the stdin filter to `App`'s notice dispatch (P1-3 / I-15).
|
|
69
84
|
*
|
package/dist/input/limits.js
CHANGED
|
@@ -64,6 +64,21 @@ export const PASTE_CLOSE = '\u0000]';
|
|
|
64
64
|
* no payload, so one constant replaces the OPEN/CLOSE pair.
|
|
65
65
|
*/
|
|
66
66
|
export const ENTER_NEWLINE_FRAME = '\u0000n';
|
|
67
|
+
/**
|
|
68
|
+
* The inline frame that carries ONE "Ctrl+I was pressed" intent from the stdin
|
|
69
|
+
* filter to `App` (project-indexer Ctrl+I trigger).
|
|
70
|
+
*
|
|
71
|
+
* WHY A FRAME AND NOT `key.ctrl && input === 'i'`. Legacy terminals encode
|
|
72
|
+
* Ctrl+I as the same byte as Tab (0x09), so by the time the stream reaches Ink
|
|
73
|
+
* the two are indistinguishable. Under win32-input-mode (`?9001h`) and kitty
|
|
74
|
+
* CSI-u the modifiers ARE known at translation time, and both translators emit
|
|
75
|
+
* this frame instead of the legacy Tab byte. Same NUL-delimiter guarantee as
|
|
76
|
+
* `ENTER_NEWLINE_FRAME`: no terminal delivers NUL for a key and pasted text is
|
|
77
|
+
* stripped of NUL, so a payload can never forge it. Terminals without keyboard
|
|
78
|
+
* enhancement keep the legacy collapse — Ctrl+I there is Tab, documented in the
|
|
79
|
+
* README keybindings table.
|
|
80
|
+
*/
|
|
81
|
+
export const INDEX_KEY_FRAME = '\u0000i';
|
|
67
82
|
/** Human-readable byte size for the refusal toast (`4.1 MB`, `812.0 KB`, `7 B`). */
|
|
68
83
|
export function formatPasteSize(bytes) {
|
|
69
84
|
if (bytes >= 1024 * 1024)
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
*
|
|
21
21
|
* Pure: no node/React imports, callable from any test environment.
|
|
22
22
|
*/
|
|
23
|
-
import { ENTER_NEWLINE_FRAME } from './limits.js';
|
|
23
|
+
import { ENTER_NEWLINE_FRAME, INDEX_KEY_FRAME } from './limits.js';
|
|
24
24
|
/** Enable / disable win32-input-mode (private mode 9001). */
|
|
25
25
|
export const WIN32_INPUT_ENABLE = '\x1b[?9001h';
|
|
26
26
|
export const WIN32_INPUT_DISABLE = '\x1b[?9001l';
|
|
@@ -190,6 +190,12 @@ export function translateWin32Key(record) {
|
|
|
190
190
|
// A modified Enter NEVER submits -- same rule as `ENTER_SEQUENCES`.
|
|
191
191
|
return m === 0 ? '\r' : ENTER_NEWLINE_FRAME;
|
|
192
192
|
case VK_TAB:
|
|
193
|
+
// Ctrl+I is the project-indexer trigger, and the ONLY place the two are
|
|
194
|
+
// tell-apart is right here: legacy encoding collapses it to the Tab
|
|
195
|
+
// byte. Shift keeps ESC [ Z (Shift+Tab) so the mode toggle stays intact,
|
|
196
|
+
// and Ctrl+Shift+I stays on that Shift branch.
|
|
197
|
+
if (ctrl && !shift && !alt)
|
|
198
|
+
return INDEX_KEY_FRAME;
|
|
193
199
|
return shift ? '\x1b[Z' : '\t';
|
|
194
200
|
case VK_ESCAPE:
|
|
195
201
|
return alt ? '\x1b\x1b' : '\x1b';
|
|
@@ -292,6 +292,15 @@ export type TeamLogEvent = {
|
|
|
292
292
|
subject?: string;
|
|
293
293
|
body?: string;
|
|
294
294
|
};
|
|
295
|
+
} | {
|
|
296
|
+
type: 'overseer';
|
|
297
|
+
dispatchId: string;
|
|
298
|
+
label?: string;
|
|
299
|
+
decision?: {
|
|
300
|
+
action?: string;
|
|
301
|
+
reason?: string;
|
|
302
|
+
};
|
|
303
|
+
trigger?: string;
|
|
295
304
|
} | {
|
|
296
305
|
type: 'dispatch_end';
|
|
297
306
|
dispatchId: string;
|
package/dist/logging/install.js
CHANGED
|
@@ -510,6 +510,17 @@ export function attachTeamEvents(logger, controller) {
|
|
|
510
510
|
subject: event.message?.subject,
|
|
511
511
|
});
|
|
512
512
|
break;
|
|
513
|
+
case 'overseer':
|
|
514
|
+
// REASON ONLY, and at debug: interventions are routine supervision,
|
|
515
|
+
// not failures; the report and the panel carry them to the user.
|
|
516
|
+
logger.debug('agent', 'team_overseer', {
|
|
517
|
+
dispatchId: event.dispatchId,
|
|
518
|
+
label: event.label,
|
|
519
|
+
action: event.decision?.action,
|
|
520
|
+
trigger: event.trigger,
|
|
521
|
+
reason: event.decision?.reason,
|
|
522
|
+
});
|
|
523
|
+
break;
|
|
513
524
|
case 'dispatch_end': {
|
|
514
525
|
const outcome = event.outcome;
|
|
515
526
|
const runs = outcome?.runs ?? [];
|
|
@@ -33,7 +33,18 @@ export declare class TeamHumanQueue {
|
|
|
33
33
|
private readonly confirm;
|
|
34
34
|
/** The chain. Never rejects: a failed request resolves `false` (deny). */
|
|
35
35
|
private tail;
|
|
36
|
+
/**
|
|
37
|
+
* Labels with a request chained but not yet answered (team-overseer I-OV3).
|
|
38
|
+
*
|
|
39
|
+
* The queue pauses the CHILD's own watchdog on enqueue; the supervisor's
|
|
40
|
+
* soft silence timer needs the same fact one level up, or a user who takes
|
|
41
|
+
* four minutes over the first dialog burns inspection budget on siblings
|
|
42
|
+
* that are third in the FIFO and provably fine.
|
|
43
|
+
*/
|
|
44
|
+
private readonly waiting;
|
|
36
45
|
constructor(confirm: (req: ConfirmRequest) => Promise<boolean>);
|
|
46
|
+
/** Whether `label` has a confirmation queued or displayed right now. */
|
|
47
|
+
isWaiting(label: string): boolean;
|
|
37
48
|
/**
|
|
38
49
|
* Ask the human about `req` on behalf of `label`, one at a time.
|
|
39
50
|
*
|
package/dist/team/human-queue.js
CHANGED
|
@@ -27,9 +27,22 @@ export class TeamHumanQueue {
|
|
|
27
27
|
confirm;
|
|
28
28
|
/** The chain. Never rejects: a failed request resolves `false` (deny). */
|
|
29
29
|
tail = Promise.resolve();
|
|
30
|
+
/**
|
|
31
|
+
* Labels with a request chained but not yet answered (team-overseer I-OV3).
|
|
32
|
+
*
|
|
33
|
+
* The queue pauses the CHILD's own watchdog on enqueue; the supervisor's
|
|
34
|
+
* soft silence timer needs the same fact one level up, or a user who takes
|
|
35
|
+
* four minutes over the first dialog burns inspection budget on siblings
|
|
36
|
+
* that are third in the FIFO and provably fine.
|
|
37
|
+
*/
|
|
38
|
+
waiting = new Set();
|
|
30
39
|
constructor(confirm) {
|
|
31
40
|
this.confirm = confirm;
|
|
32
41
|
}
|
|
42
|
+
/** Whether `label` has a confirmation queued or displayed right now. */
|
|
43
|
+
isWaiting(label) {
|
|
44
|
+
return this.waiting.has(label);
|
|
45
|
+
}
|
|
33
46
|
/**
|
|
34
47
|
* Ask the human about `req` on behalf of `label`, one at a time.
|
|
35
48
|
*
|
|
@@ -41,13 +54,17 @@ export class TeamHumanQueue {
|
|
|
41
54
|
*/
|
|
42
55
|
request(child, label, req, signal) {
|
|
43
56
|
child?.pauseIdleWatchdog();
|
|
57
|
+
this.waiting.add(label);
|
|
44
58
|
const queued = this.enqueue(async () => {
|
|
45
59
|
if (signal?.aborted)
|
|
46
60
|
return false;
|
|
47
61
|
return this.confirm({ ...req, summary: `[${label}] ${req.summary}` });
|
|
48
62
|
});
|
|
49
63
|
const raced = signal ? Promise.race([queued, abortsToFalse(signal)]) : queued;
|
|
50
|
-
return raced.finally(() =>
|
|
64
|
+
return raced.finally(() => {
|
|
65
|
+
this.waiting.delete(label);
|
|
66
|
+
child?.resumeIdleWatchdog();
|
|
67
|
+
});
|
|
51
68
|
}
|
|
52
69
|
/**
|
|
53
70
|
* Chain `run` after everything already queued.
|
package/dist/team/limits.d.ts
CHANGED
|
@@ -101,6 +101,46 @@ export declare const TEAM_LIMITS: {
|
|
|
101
101
|
* nothing a re-dispatch by the lead would not buy better. */
|
|
102
102
|
readonly maxColdStartRetries: 1;
|
|
103
103
|
readonly retryBackoffMs: 2000;
|
|
104
|
+
/**
|
|
105
|
+
* Wall-clock budget of everything one inspection may carry. The digest holds
|
|
106
|
+
* run facts, the supervisor's own memory and the tail of the child's live
|
|
107
|
+
* history; unbounded, one look at a child that read five big files would
|
|
108
|
+
* make the "cheap" supervision call cost more than the child (the argument
|
|
109
|
+
* FAST_LIMITS.digestMaxChars makes, one level up).
|
|
110
|
+
*/
|
|
111
|
+
readonly overseerDigestBytes: 8000;
|
|
112
|
+
/** Clamp on a `nudge` guidance text. Advice, not a second brief. */
|
|
113
|
+
readonly overseerGuidanceChars: 1200;
|
|
114
|
+
/** Clamp on the decision `reason` recorded in the report. */
|
|
115
|
+
readonly overseerReasonChars: 200;
|
|
116
|
+
/**
|
|
117
|
+
* Inspections PER CHILD per dispatch. An indecisive supervisor must stop
|
|
118
|
+
* burning fast-tier calls; past the budget the child simply runs under its
|
|
119
|
+
* hard ceilings and the supervisor goes quiet.
|
|
120
|
+
*/
|
|
121
|
+
readonly overseerMaxInspectionsPerChild: 4;
|
|
122
|
+
/**
|
|
123
|
+
* Rebuilds PER CHILD per dispatch. One: a replacement that also stalls dies
|
|
124
|
+
* on its own watchdog rather than ping-ponging fresh children at a task the
|
|
125
|
+
* supervisor already failed to rescue once.
|
|
126
|
+
*/
|
|
127
|
+
readonly overseerMaxReplacementsPerChild: 1;
|
|
128
|
+
/**
|
|
129
|
+
* First wall-clock check when `team.subagentTimeoutMs === 0` (the shipped
|
|
130
|
+
* default): the supervisor still looks even when no ceiling was configured.
|
|
131
|
+
*/
|
|
132
|
+
readonly overseerDefaultCheckMs: 300000;
|
|
133
|
+
/** Clamp on the model-chosen `nextCheckMs`, lower bound. */
|
|
134
|
+
readonly overseerNextCheckMinMs: 60000;
|
|
135
|
+
/** Clamp on the model-chosen `nextCheckMs`, upper bound. */
|
|
136
|
+
readonly overseerNextCheckMaxMs: 900000;
|
|
137
|
+
/**
|
|
138
|
+
* A child's OWN idle watchdog, multiplied by this while the supervisor is
|
|
139
|
+
* active, so the soft silence inspection at `idleTimeoutMs` always precedes
|
|
140
|
+
* the hard abort. The watchdog stays as the backstop for a child that
|
|
141
|
+
* wedges past every inspection.
|
|
142
|
+
*/
|
|
143
|
+
readonly overseerWatchdogFactor: 4;
|
|
104
144
|
};
|
|
105
145
|
/**
|
|
106
146
|
* The two comm tools, which are POLICY-EXEMPT inside a child (§3.11).
|
package/dist/team/limits.js
CHANGED
|
@@ -106,6 +106,47 @@ export const TEAM_LIMITS = {
|
|
|
106
106
|
* nothing a re-dispatch by the lead would not buy better. */
|
|
107
107
|
maxColdStartRetries: 1,
|
|
108
108
|
retryBackoffMs: 2_000,
|
|
109
|
+
// --- The dispatch supervisor (team-overseer) ---
|
|
110
|
+
/**
|
|
111
|
+
* Wall-clock budget of everything one inspection may carry. The digest holds
|
|
112
|
+
* run facts, the supervisor's own memory and the tail of the child's live
|
|
113
|
+
* history; unbounded, one look at a child that read five big files would
|
|
114
|
+
* make the "cheap" supervision call cost more than the child (the argument
|
|
115
|
+
* FAST_LIMITS.digestMaxChars makes, one level up).
|
|
116
|
+
*/
|
|
117
|
+
overseerDigestBytes: 8_000,
|
|
118
|
+
/** Clamp on a `nudge` guidance text. Advice, not a second brief. */
|
|
119
|
+
overseerGuidanceChars: 1_200,
|
|
120
|
+
/** Clamp on the decision `reason` recorded in the report. */
|
|
121
|
+
overseerReasonChars: 200,
|
|
122
|
+
/**
|
|
123
|
+
* Inspections PER CHILD per dispatch. An indecisive supervisor must stop
|
|
124
|
+
* burning fast-tier calls; past the budget the child simply runs under its
|
|
125
|
+
* hard ceilings and the supervisor goes quiet.
|
|
126
|
+
*/
|
|
127
|
+
overseerMaxInspectionsPerChild: 4,
|
|
128
|
+
/**
|
|
129
|
+
* Rebuilds PER CHILD per dispatch. One: a replacement that also stalls dies
|
|
130
|
+
* on its own watchdog rather than ping-ponging fresh children at a task the
|
|
131
|
+
* supervisor already failed to rescue once.
|
|
132
|
+
*/
|
|
133
|
+
overseerMaxReplacementsPerChild: 1,
|
|
134
|
+
/**
|
|
135
|
+
* First wall-clock check when `team.subagentTimeoutMs === 0` (the shipped
|
|
136
|
+
* default): the supervisor still looks even when no ceiling was configured.
|
|
137
|
+
*/
|
|
138
|
+
overseerDefaultCheckMs: 300_000,
|
|
139
|
+
/** Clamp on the model-chosen `nextCheckMs`, lower bound. */
|
|
140
|
+
overseerNextCheckMinMs: 60_000,
|
|
141
|
+
/** Clamp on the model-chosen `nextCheckMs`, upper bound. */
|
|
142
|
+
overseerNextCheckMaxMs: 900_000,
|
|
143
|
+
/**
|
|
144
|
+
* A child's OWN idle watchdog, multiplied by this while the supervisor is
|
|
145
|
+
* active, so the soft silence inspection at `idleTimeoutMs` always precedes
|
|
146
|
+
* the hard abort. The watchdog stays as the backstop for a child that
|
|
147
|
+
* wedges past every inspection.
|
|
148
|
+
*/
|
|
149
|
+
overseerWatchdogFactor: 4,
|
|
109
150
|
};
|
|
110
151
|
/**
|
|
111
152
|
* The two comm tools, which are POLICY-EXEMPT inside a child (§3.11).
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The dispatch supervisor (team-overseer).
|
|
3
|
+
*
|
|
4
|
+
* ASCII ONLY: `src/team/**` is inside the glyph scanner's scope
|
|
5
|
+
* (`glyphs.test.ts`), so no literal in this tree may hold a non-ASCII byte.
|
|
6
|
+
*
|
|
7
|
+
* A TIME CEILING ON A CHILD IS AN INSPECTION TRIGGER, NOT A DEATH SENTENCE.
|
|
8
|
+
* When a child stalls (event silence) or simply runs long (the wall clock),
|
|
9
|
+
* the supervisor looks at it through ONE bounded fast-tier call and decides:
|
|
10
|
+
* keep waiting, steer a nudge in to help it recover, rebuild it with an
|
|
11
|
+
* amended brief, or abandon it with a recorded reason. Only the supervisor
|
|
12
|
+
* "decides"; the RUNTIME applies (see `runtime.ts`), because every child
|
|
13
|
+
* lifecycle change must funnel through the worker loop that awaits it
|
|
14
|
+
* (I-OV1).
|
|
15
|
+
*
|
|
16
|
+
* THE TRANSPORT IS THE REVIEWER'S, DELIBERATELY. One `complete` call, no
|
|
17
|
+
* tools, thinking off, temperature 0, output capped at
|
|
18
|
+
* `FAST_LIMITS.reviewOutputTokens` and wall clock at
|
|
19
|
+
* `FAST_LIMITS.reviewTimeoutMs` — a supervision call that could itself hang
|
|
20
|
+
* would need a second supervisor, which is the regress this design refuses.
|
|
21
|
+
*
|
|
22
|
+
* REPAIR, NEVER REJECT (the `normalizeSubagentSpecs` rule). An unparseable
|
|
23
|
+
* or out-of-budget decision becomes `wait` with the default interval: the
|
|
24
|
+
* failure mode of a supervisor must be "looked and let it run", never
|
|
25
|
+
* "looked and killed it" (I-OV2).
|
|
26
|
+
*/
|
|
27
|
+
import type { AssistantMessage, LLMRequest, Message } from '@aragon-agent/core';
|
|
28
|
+
import type { ModelRole } from '../config/model-profiles.js';
|
|
29
|
+
import type { CliConfig } from '../config/schema.js';
|
|
30
|
+
import type { OverseerAction, OverseerDecision, SubagentRun, SubagentSpec } from './types.js';
|
|
31
|
+
/** One remembered decision, so the second look knows the first happened. */
|
|
32
|
+
export interface OverseerMemoryEntry {
|
|
33
|
+
/** Seconds into the dispatch. */
|
|
34
|
+
atSec: number;
|
|
35
|
+
action: OverseerAction;
|
|
36
|
+
reason: string;
|
|
37
|
+
}
|
|
38
|
+
/** Everything one digest is built from. Structural, so tests stub it whole. */
|
|
39
|
+
export interface ChildDigestInput {
|
|
40
|
+
run: SubagentRun;
|
|
41
|
+
/** False only in the post-mortem inspection, after the child settled. */
|
|
42
|
+
childAlive: boolean;
|
|
43
|
+
trigger: 'silence' | 'clock' | 'postmortem';
|
|
44
|
+
/** The child's LIVE history (`SubagentAgentLike.state.messages`). */
|
|
45
|
+
messages: readonly Message[];
|
|
46
|
+
memory: readonly OverseerMemoryEntry[];
|
|
47
|
+
/** Seconds the child has been running. */
|
|
48
|
+
elapsedSec: number;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Build the bounded text one inspection reads.
|
|
52
|
+
*
|
|
53
|
+
* FACTS FIRST, HISTORY SECOND: the byte clamp (`overseerDigestBytes`, via the
|
|
54
|
+
* same `truncateBytes` the report uses so a CJK tail cannot be split mid
|
|
55
|
+
* sequence) bites the tail, and the tail is the least decisive part — a
|
|
56
|
+
* supervisor that cannot see the phase, the retry state and its own memory
|
|
57
|
+
* is worse than one missing the fifth message back.
|
|
58
|
+
*/
|
|
59
|
+
export declare function buildChildDigest(input: ChildDigestInput): string;
|
|
60
|
+
/** Clamp a model-chosen next check into the structural range. */
|
|
61
|
+
export declare function clampNextCheckMs(ms: unknown): number | undefined;
|
|
62
|
+
/**
|
|
63
|
+
* Normalize a raw (JSON-parsed or garbage) decision into a safe one.
|
|
64
|
+
*
|
|
65
|
+
* `childAlive === false` PHYSICALLY REMOVES two actions: a dead child can be
|
|
66
|
+
* replaced or abandoned, never waited on or nudged, so those normalize to
|
|
67
|
+
* `abandon` rather than being rejected — a post-mortem answer the protocol
|
|
68
|
+
* cannot honour must still produce a decision the runtime can apply.
|
|
69
|
+
*/
|
|
70
|
+
export declare function normalizeOverseerDecision(raw: unknown, childAlive: boolean): OverseerDecision;
|
|
71
|
+
/**
|
|
72
|
+
* Parse the model's answer text as one JSON decision object.
|
|
73
|
+
*
|
|
74
|
+
* Forgiving on PURPOSE: models wrap JSON in prose and fences. The FIRST `{`
|
|
75
|
+
* to the LAST `}` is the only span tried, and anything that does not parse is
|
|
76
|
+
* `wait` — the caller never sees a throw from parsing.
|
|
77
|
+
*/
|
|
78
|
+
export declare function parseOverseerDecisionText(text: string, childAlive: boolean): OverseerDecision;
|
|
79
|
+
/** The slice the runtime needs: "can you look?" and "decide". */
|
|
80
|
+
export interface OverseerProvider {
|
|
81
|
+
/** Live predicate, re-read per dispatch AND per inspection. */
|
|
82
|
+
active(): boolean;
|
|
83
|
+
/** One bounded decision call. Implementations must not throw. */
|
|
84
|
+
inspect(req: {
|
|
85
|
+
label: string;
|
|
86
|
+
goal: string;
|
|
87
|
+
digest: string;
|
|
88
|
+
}): Promise<OverseerDecision>;
|
|
89
|
+
}
|
|
90
|
+
/** Everything the fast-tier-backed provider needs from the session. */
|
|
91
|
+
export interface FastOverseerDeps {
|
|
92
|
+
/** Read LIVE: a mid-dispatch `/fast off` must reach the next inspection. */
|
|
93
|
+
getConfig: () => CliConfig;
|
|
94
|
+
hasKey: (providerId: string, role?: ModelRole) => boolean;
|
|
95
|
+
getApiKey: (providerId: string, role?: ModelRole) => string | undefined;
|
|
96
|
+
/** `FastWiring.completeViaFastRegistry` — the shared fail-fast transport. */
|
|
97
|
+
complete: (providerId: string, request: LLMRequest) => Promise<AssistantMessage>;
|
|
98
|
+
/** `FastWiring.available()`. */
|
|
99
|
+
available: () => boolean;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* The fast-tier-backed provider, mirroring `createChildReviewer`'s shape so
|
|
103
|
+
* the controller wires it the same way: shared transport, live predicates,
|
|
104
|
+
* fail-soft (any failure resolves to `wait` — I-OV2).
|
|
105
|
+
*/
|
|
106
|
+
export declare function createFastOverseerProvider(deps: FastOverseerDeps): OverseerProvider;
|
|
107
|
+
/** One inspection request, as the runtime builds it. */
|
|
108
|
+
export interface OverseerInspectRequest {
|
|
109
|
+
run: SubagentRun;
|
|
110
|
+
messages: readonly Message[];
|
|
111
|
+
trigger: 'silence' | 'clock' | 'postmortem';
|
|
112
|
+
childAlive: boolean;
|
|
113
|
+
}
|
|
114
|
+
export interface TeamOverseerOptions {
|
|
115
|
+
/** The dispatch's start, so memory entries read as "N s in". */
|
|
116
|
+
startedAt: number;
|
|
117
|
+
now?: () => number;
|
|
118
|
+
}
|
|
119
|
+
export declare const OVERSEER_DEFAULT_NEXT_CHECK_MS: 300000;
|
|
120
|
+
/**
|
|
121
|
+
* Per-dispatch supervisor state: memory, budgets and the single-flight guard.
|
|
122
|
+
*
|
|
123
|
+
* THE CLASS NEVER TOUCHES A CHILD (I-OV1). It looks and decides; `runtime.ts`
|
|
124
|
+
* steers, flags, aborts and rebuilds, because only the worker loop can apply
|
|
125
|
+
* a lifecycle change without orphaning the replacement.
|
|
126
|
+
*/
|
|
127
|
+
export declare class TeamOverseer {
|
|
128
|
+
private readonly provider;
|
|
129
|
+
private readonly options;
|
|
130
|
+
private readonly memory;
|
|
131
|
+
private readonly inspections;
|
|
132
|
+
private readonly replacements;
|
|
133
|
+
private readonly inFlight;
|
|
134
|
+
constructor(provider: OverseerProvider, options: TeamOverseerOptions);
|
|
135
|
+
/** Never throws: a provider bug must not take the dispatch down. */
|
|
136
|
+
isActive(): boolean;
|
|
137
|
+
inspectionsExhausted(label: string): boolean;
|
|
138
|
+
replacementsLeft(label: string): boolean;
|
|
139
|
+
/** Called by the runtime when it APPLIES a replace, not when it merely reads one. */
|
|
140
|
+
noteReplacement(label: string): void;
|
|
141
|
+
/**
|
|
142
|
+
* ONE inspection. `null` means QUIET — not active, no budget left, or one
|
|
143
|
+
* already in flight for this child — and the caller then does nothing,
|
|
144
|
+
* which is exactly what quiet should do.
|
|
145
|
+
*/
|
|
146
|
+
inspect(req: OverseerInspectRequest): Promise<OverseerDecision | null>;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Prepend the supervisor's note to a spec's prompt, bounding the TOTAL to
|
|
150
|
+
* `TEAM_LIMITS.promptChars` so the replacement fits exactly what a
|
|
151
|
+
* model-supplied spec would have been allowed to carry.
|
|
152
|
+
*
|
|
153
|
+
* THE NOTE TELLS THE FRESH CHILD WHAT THE DEAD ONE DID (`filesTouched` is
|
|
154
|
+
* best-effort, the same caveat the report's conflict warning carries): a
|
|
155
|
+
* replacement that rewrites a half-written file blind is not a recovery.
|
|
156
|
+
*/
|
|
157
|
+
export declare function buildReplacementPrompt(spec: SubagentSpec, decision: OverseerDecision, previousRun: SubagentRun): string;
|