@aragon-agent/cli 0.6.16 → 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.
Files changed (79) hide show
  1. package/CHANGELOG.md +107 -51
  2. package/README.md +91 -48
  3. package/dist/agent/controller.js +52 -47
  4. package/dist/agent/headless.js +7 -0
  5. package/dist/agent/reducer.js +0 -4
  6. package/dist/cli.js +0 -13
  7. package/dist/commands/builtins.js +21 -20
  8. package/dist/commands/index-build.d.ts +39 -0
  9. package/dist/commands/index-build.js +61 -0
  10. package/dist/compaction/child.d.ts +10 -8
  11. package/dist/compaction/child.js +23 -25
  12. package/dist/compaction/limits.d.ts +0 -2
  13. package/dist/compaction/limits.js +0 -2
  14. package/dist/config/cli-commands.d.ts +2 -2
  15. package/dist/config/cli-commands.js +19 -15
  16. package/dist/config/load.d.ts +0 -17
  17. package/dist/config/load.js +0 -6
  18. package/dist/config/schema.d.ts +41 -9
  19. package/dist/config/schema.js +18 -11
  20. package/dist/exec/events.d.ts +16 -9
  21. package/dist/exec/events.js +2 -2
  22. package/dist/exec/runner.js +17 -1
  23. package/dist/fast/limits.d.ts +2 -2
  24. package/dist/fast/limits.js +2 -2
  25. package/dist/fast/prompt.d.ts +10 -11
  26. package/dist/fast/prompt.js +19 -20
  27. package/dist/fast/reviewer.d.ts +1 -1
  28. package/dist/fast/reviewer.js +1 -1
  29. package/dist/fast/types.d.ts +0 -2
  30. package/dist/fast/wiring.d.ts +18 -34
  31. package/dist/fast/wiring.js +20 -51
  32. package/dist/input/csiu-keys.js +13 -2
  33. package/dist/input/limits.d.ts +15 -0
  34. package/dist/input/limits.js +15 -0
  35. package/dist/input/win32-input-mode.js +7 -1
  36. package/dist/logging/install.d.ts +9 -0
  37. package/dist/logging/install.js +11 -0
  38. package/dist/session/validate-session.js +3 -1
  39. package/dist/team/child-reviewer.d.ts +105 -0
  40. package/dist/team/child-reviewer.js +117 -0
  41. package/dist/team/human-queue.d.ts +11 -0
  42. package/dist/team/human-queue.js +18 -1
  43. package/dist/team/limits.d.ts +40 -0
  44. package/dist/team/limits.js +41 -0
  45. package/dist/team/normalize.d.ts +7 -26
  46. package/dist/team/normalize.js +9 -22
  47. package/dist/team/overseer.d.ts +157 -0
  48. package/dist/team/overseer.js +348 -0
  49. package/dist/team/prompt.d.ts +0 -11
  50. package/dist/team/prompt.js +3 -12
  51. package/dist/team/report.d.ts +4 -19
  52. package/dist/team/report.js +27 -28
  53. package/dist/team/runtime.d.ts +76 -16
  54. package/dist/team/runtime.js +410 -57
  55. package/dist/team/subagent.d.ts +48 -19
  56. package/dist/team/subagent.js +88 -31
  57. package/dist/team/task-tool.d.ts +6 -26
  58. package/dist/team/task-tool.js +17 -48
  59. package/dist/team/types.d.ts +65 -41
  60. package/dist/ui/App.d.ts +1 -1
  61. package/dist/ui/App.js +64 -4
  62. package/dist/ui/BottomStatusRow.js +2 -1
  63. package/dist/ui/Header.js +13 -2
  64. package/dist/ui/PromptInput.js +11 -1
  65. package/dist/ui/StatusBar.js +2 -1
  66. package/dist/ui/TeamPanel.js +1 -1
  67. package/dist/ui/entries/TeamCard.js +1 -6
  68. package/dist/ui/layout/status-detail-layout.js +12 -2
  69. package/dist/ui/layout/status-layout.d.ts +4 -2
  70. package/dist/ui/layout/status-layout.js +24 -1
  71. package/dist/ui/overlays/HelpOverlay.js +2 -2
  72. package/package.json +2 -2
  73. package/skills/project-indexer/SKILL.md +163 -0
  74. package/skills/project-indexer/templates/cleancode-overrides.md +46 -0
  75. package/skills/project-indexer/templates/cleancode-template.md +94 -0
  76. package/skills/project-indexer/templates/config-template.md +69 -0
  77. package/skills/project-indexer/templates/index-template.md +129 -0
  78. package/dist/ui/ModeChip.d.ts +0 -18
  79. package/dist/ui/ModeChip.js +0 -24
@@ -23,26 +23,11 @@
23
23
  import type { ModelCost } from '@aragon-agent/core';
24
24
  import type { DispatchOutcome, SubagentRun } from './types.js';
25
25
  export interface ReportOptions {
26
- /** The lead's model cost table, so the header can state real spend (R-5). */
27
- cost?: ModelCost;
28
- /**
29
- * The FAST model's cost table (fast-model-tier §3.4 / R-7).
30
- *
31
- * A SECOND TABLE, not a second number derived from the first. Summing
32
- * fast-tier tokens at the lead's price over-reports a Haiku child under a
33
- * Sonnet lead by roughly an order of magnitude, and the whole justification
34
- * for delegating to a cheaper model is the figure on this line.
35
- */
36
- fastCost?: ModelCost;
37
- /**
38
- * The static price table has never heard of the fast model (C-11 / RV-4).
26
+ /** The lead's model cost table, so the header can state real spend (R-5).
39
27
  *
40
- * UNKNOWN PRICING IS NOT ZERO PRICING. `buildRuntimeModel` hands back
41
- * `cost: { input: 0, output: 0 }` for an unrecognised id, and the fast tier is
42
- * precisely where an unrecognised id is LIKELY — so rendering it as `$0.00`
43
- * would make the feature look free while it is spending money.
44
- */
45
- fastPricingUnknown?: boolean;
28
+ * THE ONE TABLE, because every child runs the lead's own model
29
+ * (main-agent parity): one tier, one price, one honest total. */
30
+ cost?: ModelCost;
46
31
  }
47
32
  /**
48
33
  * Cut `text` to at most `maxBytes` WITHOUT splitting a UTF-8 sequence.
@@ -106,28 +106,6 @@ function buildHeader(outcome, options) {
106
106
  `(${counts}) in ${formatDuration(durationMs)}.`);
107
107
  lines.push(`Tokens: in ${formatTokens(outcome.usage.inputTokens)}, out ` +
108
108
  `${formatTokens(outcome.usage.outputTokens)}. Cost: ${formatCost(computeCost(outcome.usage, options.cost))}.`);
109
- // A SECOND LINE, NOT A COMBINED TOTAL (fast-model-tier §3.4). Two tiers with
110
- // two price tables have two answers, and folding them into one figure is the
111
- // misattribution R-7 is about. Present only when a fast child actually ran, so
112
- // an ordinary dispatch's header is byte-identical.
113
- const fastUsage = outcome.fastUsage;
114
- if (fastUsage) {
115
- // `unknown` rather than a currency amount when the model has no price table
116
- // (RV-4): a lower bound rendered as a total is the same class of lie as
117
- // pricing a Haiku child at Sonnet rates.
118
- const fastCostLabel = options.fastPricingUnknown
119
- ? 'unknown (no price table)'
120
- : formatCost(computeCost(fastUsage, options.fastCost));
121
- lines.push(`Fast tier: in ${formatTokens(fastUsage.inputTokens)}, out ` +
122
- `${formatTokens(fastUsage.outputTokens)}. Cost: ${fastCostLabel}.`);
123
- }
124
- // THE LINE THAT KEEPS A DOWNGRADE FROM BEING SILENT (R-6). Without it the
125
- // model asked for a cheap child, got an expensive one, and has no way to learn
126
- // that its cost model is wrong.
127
- if (outcome.downgraded && outcome.downgraded > 0) {
128
- const n = outcome.downgraded;
129
- lines.push(`${n} ${n === 1 ? 'subagent' : 'subagents'} ran on the main model (fast tier off).`);
130
- }
131
109
  for (const conflict of findFileConflicts(outcome.runs)) {
132
110
  const who = conflict.labels.map((l) => `"${l}"`).join(' and ');
133
111
  lines.push(`WARNING: agents ${who} both wrote ${conflict.path} - review before trusting either.`);
@@ -139,10 +117,11 @@ function buildSection(run) {
139
117
  // the only place the lead learns that a child's first attempt died before it
140
118
  // did anything (F-4).
141
119
  const retried = run.retries ? `, retried ${run.retries}x` : '';
142
- // `[fast]` after the LABEL, on the head line, which is never trimmed. Six
143
- // bytes, and it is the only place the lead learns which of its children were
144
- // cheap — the fact the whole second cost line is about.
145
- const tier = run.tier === 'fast' ? ' [fast]' : '';
120
+ // Same head-line position as `retried Nx`, for the same reason: the ONLY
121
+ // place the lead learns that a supervisor looked at this child (N times) or
122
+ // rebuilt it (team-overseer §3.7).
123
+ const overseen = run.interventions ? `, overseer ${run.interventions}` : '';
124
+ const replaced = run.replacements ? `, replaced ${run.replacements}x` : '';
146
125
  // `compacted N` in the same position `retried Nx` uses, and for the same
147
126
  // reason (context-auto-compaction-hardening §3.4.4 / W3): this is the ONLY
148
127
  // place the lead learns that a child's own history was summarized mid-task, so
@@ -150,8 +129,8 @@ function buildSection(run) {
150
129
  // rather than an unexplained one. Absent when it never happened.
151
130
  const compacted = run.compactions ? `, compacted ${run.compactions}` : '';
152
131
  const head = [
153
- `### ${run.label}${tier} "${run.description}" ${statusBadge(run)} ${elapsed(run)}, ` +
154
- `${run.turns} turns, ${run.toolCalls} tools${retried}${compacted}`,
132
+ `### ${run.label} "${run.description}" ${statusBadge(run)} ${elapsed(run)}, ` +
133
+ `${run.turns} turns, ${run.toolCalls} tools${retried}${compacted}${overseen}${replaced}`,
155
134
  ];
156
135
  if (run.filesTouched.length > 0)
157
136
  head.push(`files: ${run.filesTouched.join(', ')}`);
@@ -163,6 +142,23 @@ function buildSection(run) {
163
142
  const summary = (run.summary ?? '').trim();
164
143
  return { head, summary: summary.length > 0 ? summary : '(no summary)' };
165
144
  }
145
+ /**
146
+ * The supervisor's ledger (team-overseer §3.7): one line per APPLIED decision,
147
+ * so the lead can see who was nudged or replaced, when, and why. Budgeted by
148
+ * the same `reportMaxBytes` ladder as everything else - it renders as a block,
149
+ * so trimming it means trimming the blocks, which the ladder already does.
150
+ */
151
+ function buildOverseer(outcome) {
152
+ const list = outcome.interventions ?? [];
153
+ if (list.length === 0)
154
+ return [];
155
+ const lines = ['### Supervisor'];
156
+ for (const item of list) {
157
+ const at = Math.max(0, Math.round((item.at - outcome.startedAt) / 1000));
158
+ lines.push(`[${item.trigger} ${at}s] ${item.label}: ${item.action} - ${item.reason}`);
159
+ }
160
+ return lines;
161
+ }
166
162
  function buildLeadMail(outcome) {
167
163
  if (outcome.leadMail.length === 0)
168
164
  return [];
@@ -188,6 +184,7 @@ export function buildDispatchReport(outcome, options = {}) {
188
184
  const header = buildHeader(outcome, options);
189
185
  const sections = outcome.runs.map(buildSection);
190
186
  const mail = buildLeadMail(outcome);
187
+ const overseer = buildOverseer(outcome);
191
188
  const summaries = sections.map((s) => truncateBytes(s.summary, TEAM_LIMITS.summaryMaxBytes));
192
189
  const trimmed = summaries.map((s, i) => s.length < sections[i].summary.length);
193
190
  const render = (bodies) => {
@@ -200,6 +197,8 @@ export function buildDispatchReport(outcome, options = {}) {
200
197
  }
201
198
  blocks.push(lines.join('\n'));
202
199
  });
200
+ if (overseer.length > 0)
201
+ blocks.push(overseer.join('\n'));
203
202
  if (mail.length > 0)
204
203
  blocks.push(mail.join('\n'));
205
204
  return blocks.join('\n\n');
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * TeamRuntime — dispatch scheduling, budgets, the `TeamEvent` stream, abort and
3
- * disposal (team-subagents §3.2 / §3.5 / §3.8).
3
+ * disposal (team-subagents §3.2 / §3.5 / §3.8; team-overseer §3.4).
4
4
  *
5
5
  * ASCII ONLY: `src/team/**` is inside the glyph scanner's scope.
6
6
  *
@@ -16,6 +16,16 @@
16
16
  * worker loops, awaited with `Promise.allSettled`. No worker abstraction, no
17
17
  * queue class, no event-loop trickery — every path is observable from the event
18
18
  * stream.
19
+ *
20
+ * THE DISPATCH SUPERVISOR (team-overseer) — a time ceiling is an inspection
21
+ * trigger, not a death sentence. When a supervisor is live for the dispatch,
22
+ * two SOFT timers per child (wall clock and event silence) fire an inspection;
23
+ * the supervisor decides wait / nudge / replace / abandon; THIS module applies.
24
+ * Lifecycle changes are funnelled exclusively through `runOne`, the only place
25
+ * a child's `prompt()` is awaited (I-OV1): the supervisor may steer, flag and
26
+ * abort, but a replacement is built by the worker loop or it is built for
27
+ * nobody. Without a supervisor, every path below is byte for byte the
28
+ * pre-feature behaviour.
19
29
  */
20
30
  import type { ModelRole } from '../config/model-profiles.js';
21
31
  import type { ProviderRegistry, SkillRegistry, ToolPolicyDecision, ToolPolicyVerdict } from '@aragon-agent/core';
@@ -23,6 +33,7 @@ import type { AgentMode } from '../agent/agent-mode.js';
23
33
  import type { CliConfig } from '../config/schema.js';
24
34
  import type { ToolPermission } from '../exec/permission.js';
25
35
  import type { TeamHumanQueue } from './human-queue.js';
36
+ import { type OverseerProvider } from './overseer.js';
26
37
  import { type SubagentAgentFactory, type SubagentDeps } from './subagent.js';
27
38
  import type { DispatchOutcome, SubagentSpec, TeamEventListener, TeamSnapshot } from './types.js';
28
39
  export interface TeamRuntimeDeps {
@@ -56,21 +67,28 @@ export interface TeamRuntimeDeps {
56
67
  /** Test seam (§8.1): a stub factory keeps the whole scheduler off the network. */
57
68
  agentFactory?: SubagentAgentFactory;
58
69
  /**
59
- * Per-tier model resolution (fast-model-tier §3.4). Passed straight through to
60
- * `SubagentDeps`; absent means every child runs on the session's model, which
61
- * is the pre-feature behaviour.
70
+ * Per-child fast review (main-agent parity), passed straight through to
71
+ * `SubagentDeps`. ABSENT means the session has no fast wiring and children
72
+ * are built exactly as they were before the reviewers existed.
62
73
  */
63
- resolveTier?: SubagentDeps['resolveTier'];
74
+ review?: SubagentDeps['review'];
64
75
  /**
65
76
  * Per-child context compaction (context-auto-compaction-hardening §3.4 / W3).
66
77
  *
67
78
  * Passed straight through to `SubagentDeps`; absent means children get none,
68
79
  * which is the pre-hardening behaviour exactly. `AgentController` supplies it
69
80
  * from `CompactionWiring.childFactory()`, and the closure returns `undefined`
70
- * when compaction is unregistered or `compaction.subagents` is false - which is
71
- * what lets `subagent.ts` spread nothing at all.
81
+ * when compaction is unregistered or `compaction.subagents` is false - which
82
+ * is what lets `subagent.ts` spread nothing at all.
72
83
  */
73
84
  contextManagerFor?: SubagentDeps['contextManagerFor'];
85
+ /**
86
+ * The dispatch supervisor (team-overseer). ABSENT means the session has no
87
+ * supervision, and every path below is the pre-feature behaviour exactly.
88
+ * `active()` is read once per dispatch AND once per inspection (inside
89
+ * `TeamOverseer`), so a mid-dispatch `/fast off` stops the next look.
90
+ */
91
+ overseer?: OverseerProvider;
74
92
  }
75
93
  export declare class TeamRuntime {
76
94
  private readonly deps;
@@ -94,6 +112,16 @@ export declare class TeamRuntime {
94
112
  * quietly not for the others.
95
113
  */
96
114
  private readonly pendingBackoffs;
115
+ /**
116
+ * The dispatch supervisor, LIVE for the current dispatch only (team-overseer).
117
+ * Null when the provider is absent/disabled/off, and reset to null at every
118
+ * dispatch end so nothing supervision-shaped leaks across dispatches.
119
+ */
120
+ private overseer;
121
+ /** The soft timers, same lifetime as `overseer`. */
122
+ private supervision;
123
+ /** Applied interventions for the in-flight dispatch's outcome. */
124
+ private interventions;
97
125
  constructor(deps: TeamRuntimeDeps);
98
126
  subscribe(listener: TeamEventListener): () => void;
99
127
  /**
@@ -104,28 +132,60 @@ export declare class TeamRuntime {
104
132
  isBusy(): boolean;
105
133
  /** The live roster, for `/team` status. `null` when nothing is dispatching. */
106
134
  snapshot(): TeamSnapshot | null;
107
- dispatch(specs: SubagentSpec[], requested: number, signal?: AbortSignal,
108
- /** Carried through from the normalizer so the report can state it (§3.4 /
109
- * R-6). Optional, so every existing caller compiles unchanged. */
110
- extra?: {
111
- downgraded?: number;
112
- }): Promise<DispatchOutcome>;
135
+ dispatch(specs: SubagentSpec[], requested: number, signal?: AbortSignal): Promise<DispatchOutcome>;
113
136
  /** One slot of the pool: take the next index until the list is exhausted. */
114
137
  private worker;
115
138
  /**
116
139
  * Run one child to a terminal phase, replacing it once if its very first
117
- * request died on a transport error before it did anything at all (F-4).
140
+ * request died on a transport error before it did anything at all (F-4), and
141
+ * replacing it when the SUPERVISOR decides to (team-overseer §3.4).
142
+ *
143
+ * ONE LOOP, ALL LIFECYCLE: the cold-start replacement, the supervisor's
144
+ * replace and the post-mortem replace all swap `handles[index]` and continue
145
+ * THIS loop, because the await below is the only place a child is driven and
146
+ * a handle swapped anywhere else would never be (I-OV1).
118
147
  */
119
148
  private runOne;
120
149
  /**
121
- * Swap a cold-started child for a fresh one, carrying only what must survive.
150
+ * One post-mortem inspection, and possibly ONE replacement driven by a
151
+ * recursive `runOne` — the worker that called us has already moved on to the
152
+ * next index, so nobody else will drive the fresh child (I-OV1 holds here
153
+ * too, one frame deeper). Recursion depth is bounded by the replacement
154
+ * budget, which is 1.
155
+ */
156
+ private maybePostMortem;
157
+ /**
158
+ * Swap one child for a fresh one, carrying only what must survive.
122
159
  *
123
160
  * `this.live` IS `handles`, so the single assignment below also makes
124
161
  * `abortAll()` abort the NEW child and makes the outcome carry the new run.
125
162
  * Anyone tempted to "clean up" that aliasing into a defensive copy must update
126
163
  * both places.
164
+ *
165
+ * `verdict === null` is the COLD-START retry (F-4): identical spec, `retries`
166
+ * carried. A verdict is the SUPERVISOR's replace: an amended brief
167
+ * (`buildReplacementPrompt`), `replacements` carried, and the supervisor's
168
+ * budget spent HERE, at the moment the rebuild is real.
169
+ */
170
+ private replaceChild;
171
+ /**
172
+ * Whether a child's silence is a legitimate wait rather than a stall:
173
+ * `team_wait` (the comm tool already pauses the child's own watchdog for
174
+ * exactly this wait) or a confirmation queued on the one human slot.
175
+ */
176
+ private isBlocked;
177
+ /**
178
+ * One trigger handled: look, then apply. Fired from a supervision timer and
179
+ * therefore async-and-forgotten; nothing here may throw.
180
+ *
181
+ * QUIET (`decision === null` — provider gone, budget spent) CLEARS the
182
+ * child's timers: supervision that has nothing left to say should stop
183
+ * waking up, and the child's own hard ceilings take over.
127
184
  */
128
- private replaceForRetry;
185
+ private inspectAndApply;
186
+ private applyDecision;
187
+ /** Record one applied decision on the outcome, the run and the event stream. */
188
+ private recordIntervention;
129
189
  /**
130
190
  * The final phase of an attempt, in a FIXED ORDER: aborted, then timed out,
131
191
  * then error, then no-summary, then done.