@aragon-agent/cli 0.6.17 → 0.6.19

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.
Files changed (43) hide show
  1. package/CHANGELOG.md +121 -51
  2. package/README.md +48 -2
  3. package/dist/agent/controller.js +13 -0
  4. package/dist/agent/headless.js +7 -0
  5. package/dist/commands/builtins.js +18 -3
  6. package/dist/commands/index-build.d.ts +39 -0
  7. package/dist/commands/index-build.js +61 -0
  8. package/dist/config/cli-commands.d.ts +1 -1
  9. package/dist/config/cli-commands.js +3 -0
  10. package/dist/config/schema.d.ts +24 -4
  11. package/dist/config/schema.js +4 -0
  12. package/dist/exec/events.d.ts +4 -1
  13. package/dist/exec/runner.js +13 -0
  14. package/dist/input/csiu-keys.js +13 -2
  15. package/dist/input/limits.d.ts +15 -0
  16. package/dist/input/limits.js +15 -0
  17. package/dist/input/win32-input-mode.js +19 -1
  18. package/dist/logging/install.d.ts +9 -0
  19. package/dist/logging/install.js +11 -0
  20. package/dist/team/human-queue.d.ts +11 -0
  21. package/dist/team/human-queue.js +18 -1
  22. package/dist/team/limits.d.ts +40 -0
  23. package/dist/team/limits.js +41 -0
  24. package/dist/team/overseer.d.ts +157 -0
  25. package/dist/team/overseer.js +348 -0
  26. package/dist/team/report.js +26 -1
  27. package/dist/team/runtime.d.ts +71 -6
  28. package/dist/team/runtime.js +380 -23
  29. package/dist/team/subagent.d.ts +30 -1
  30. package/dist/team/subagent.js +8 -1
  31. package/dist/team/task-tool.js +4 -1
  32. package/dist/team/types.d.ts +63 -0
  33. package/dist/ui/App.d.ts +1 -1
  34. package/dist/ui/App.js +61 -0
  35. package/dist/ui/Header.js +13 -2
  36. package/dist/ui/PromptInput.js +11 -1
  37. package/dist/ui/overlays/HelpOverlay.js +1 -0
  38. package/package.json +2 -2
  39. package/skills/project-indexer/SKILL.md +163 -0
  40. package/skills/project-indexer/templates/cleancode-overrides.md +46 -0
  41. package/skills/project-indexer/templates/cleancode-template.md +94 -0
  42. package/skills/project-indexer/templates/config-template.md +69 -0
  43. package/skills/project-indexer/templates/index-template.md +129 -0
@@ -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
  *
@@ -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';
@@ -210,6 +216,18 @@ export function translateWin32Key(record) {
210
216
  return char;
211
217
  if (!ctrl && alt)
212
218
  return `\x1b${char}`;
219
+ // Ctrl+I, letter-vk shape: a physical Ctrl+I keeps the LETTER vk
220
+ // (0x49, the same Ctrl+letter probe rule that reports Ctrl+A as 65
221
+ // and Ctrl+J as 74) while `char` is already the Tab byte, so the
222
+ // VK_TAB switch above never sees the record. 0x09 is both the Tab
223
+ // byte and VK_TAB, so the tell-apart from plain Tab is ctrl alone,
224
+ // and Shift keeps ESC [ Z exactly like that switch: the mode toggle
225
+ // stays intact and Ctrl+Shift+I stays on the Shift branch.
226
+ if (ctrl && record.char === VK_TAB) {
227
+ if (!shift && !alt)
228
+ return INDEX_KEY_FRAME;
229
+ return shift ? '\x1b[Z' : '\t';
230
+ }
213
231
  return ctrl ? `${alt ? '\x1b' : ''}${char}` : char;
214
232
  }
215
233
  if (isPrintable(record.char)) {
@@ -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;
@@ -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
  *
@@ -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(() => child?.resumeIdleWatchdog());
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.
@@ -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).
@@ -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;