@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
@@ -12,7 +12,9 @@ import { createBuiltinTools } from '../tools/index.js';
12
12
  import { createFileChangeStore } from '../tools/file-change-store.js';
13
13
  import { createToolOutputStore, } from '../tools/tool-output-store.js';
14
14
  import { TeamRuntime } from '../team/runtime.js';
15
+ import { createChildReviewer } from '../team/child-reviewer.js';
15
16
  import { TeamHumanQueue } from '../team/human-queue.js';
17
+ import { createFastOverseerProvider } from '../team/overseer.js';
16
18
  import { createTaskTool } from '../team/task-tool.js';
17
19
  import { buildTeamBlock } from '../team/prompt.js';
18
20
  import { buildFastBlock } from '../fast/prompt.js';
@@ -161,14 +163,14 @@ export class AgentController {
161
163
  // Fast model tier (fast-model-tier §3.1 / §3.3)
162
164
  //
163
165
  // ONE FIELD AND FOUR THIN FORWARDERS. The flag pair, the tier cache, the
164
- // reviewer's lifetime and the `resolveTier` closure all live in
166
+ // reviewer's lifetime and the shared fail-fast review transport all live in
165
167
  // `fast/wiring.ts`, which is a BUDGET decision rather than an aesthetic one
166
168
  // (C-12 / RV-5): `CLAUDE.md` caps a source file at 1000 lines and this one had
167
169
  // 40 to spare when the design was written.
168
170
  //
169
171
  // `null` when the tier could not be resolved at construction, which is the
170
- // byte-identity branch: no reviewer is subscribed, `task` carries no `model`
171
- // property, and `<fast_tier>` is never spliced (I-2).
172
+ // byte-identity branch: no reviewer is subscribed and `<fast_tier>` is
173
+ // never spliced (I-2).
172
174
  // -----------------------------------------------------------------------
173
175
  fast;
174
176
  // -----------------------------------------------------------------------
@@ -311,8 +313,8 @@ export class AgentController {
311
313
  this.todos = this.todoRegistered ? new TodoStore() : null;
312
314
  // --- Fast model tier ---------------------------------------------------
313
315
  //
314
- // RESOLVED BEFORE THE TOOL ARRAY, because `fastRegistered` decides whether
315
- // `task`'s schema carries a `model` property at all and the array is built
316
+ // RESOLVED BEFORE THE AGENT, because `fastRegistered` decides whether the
317
+ // `FastWiring` (reviewer, tier cache) exists at all and the wiring is built
316
318
  // ONCE (C-2). `FastWiring` re-runs the same pure function in its own
317
319
  // constructor; the two agree by construction because they read the same
318
320
  // config through the same resolver.
@@ -324,24 +326,47 @@ export class AgentController {
324
326
  this.teamRuntime = this.teamRegistered
325
327
  ? new TeamRuntime({
326
328
  getConfig: () => this.config,
327
- // Read LIVE through the wiring, exactly as `resolveTier` below is: the
328
- // closure is only ever called from inside a dispatch, and
329
- // `childFactory()` re-reads `compaction.subagents` on every call so a
330
- // settings-screen edit reaches the next dispatch.
329
+ // Read LIVE: the closure is only ever called from inside a dispatch,
330
+ // and `childFactory()` re-reads `compaction.subagents` on every call
331
+ // so a settings-screen edit reaches the next dispatch.
331
332
  contextManagerFor: (req) => this.compaction?.childFactory()?.(req),
332
- // Read LIVE through the wiring, which does not exist yet — the closure
333
- // is only ever called from inside a dispatch, long after construction.
334
- resolveTier: (tier) => this.fast
335
- ? this.fast.resolveTier(tier)
336
- : {
337
- ref: {
338
- providerId: this.config.provider,
339
- modelId: this.config.model,
340
- ...(this.config.baseUrl ? { baseUrl: this.config.baseUrl } : {}),
341
- },
342
- thinkingLevel: this.config.thinkingLevel,
343
- role: 'main',
333
+ // PER-CHILD FAST REVIEW (main-agent parity): one reviewer per
334
+ // child, sharing the lead wiring's tier, policy and fail-fast
335
+ // transport. `this.fast` is null until after the Agent exists; these
336
+ // closures run only from inside a dispatch, long after construction.
337
+ review: {
338
+ // EVERY `this.fast` READ LIVES INSIDE A CLOSURE: the wiring is
339
+ // assigned AFTER this constructor block (it needs the Agent), and
340
+ // a read in the constructor's own flow would be a use-before-assign.
341
+ // The closures run only from inside a dispatch, long after
342
+ // construction, when the field has its final value.
343
+ active: () => this.fast !== null && this.fast.available() && this.config.fast.review,
344
+ modelId: () => {
345
+ const tier = this.fast?.getTier();
346
+ return tier && tier.ok ? tier.ref.modelId : '';
344
347
  },
348
+ create: (request) => this.fast
349
+ ? createChildReviewer(request, {
350
+ getConfig: () => this.config,
351
+ hasKey: (id, role) => this.hasApiKey(id, role),
352
+ getApiKey: (id, role) => this.resolveKey(id, role),
353
+ complete: (providerId, llmRequest) => this.fast.completeViaFastRegistry(providerId, llmRequest),
354
+ available: () => this.fast?.available() === true,
355
+ })
356
+ : undefined,
357
+ },
358
+ // THE DISPATCH SUPERVISOR (team-overseer): the fast tier that powers the
359
+ // per-child reviewers also powers the supervisor one-shot decision
360
+ // calls. Same live-predicate discipline as review above - every
361
+ // this.fast read lives in a closure that only runs from inside a
362
+ // dispatch, long after the wiring exists.
363
+ overseer: createFastOverseerProvider({
364
+ getConfig: () => this.config,
365
+ hasKey: (id, role) => this.hasApiKey(id, role),
366
+ getApiKey: (id, role) => this.resolveKey(id, role),
367
+ complete: (providerId, llmRequest) => this.fast.completeViaFastRegistry(providerId, llmRequest),
368
+ available: () => this.fast?.available() === true,
369
+ }),
345
370
  providerRegistry: this.providerRegistry,
346
371
  getCwd: () => this.cwd,
347
372
  getMode: () => this.effectiveMode,
@@ -488,23 +513,12 @@ export class AgentController {
488
513
  runtime: this.teamRuntime,
489
514
  // Read LIVE: `/team off` flips the flag, it cannot unregister.
490
515
  isTeamEnabled: () => this.teamEnabled,
491
- // FROZEN AT CONSTRUCTION (C-2). `/fast on` in a session launched
492
- // without the tier cannot add a schema property, and it says so
493
- // rather than advertising one that is not there.
494
- fastRegistered,
495
- // Read LIVE at dispatch time (RV-3): the tier can stop resolving
496
- // mid-session, and the normalizer and the subagent factory must
497
- // agree about that at the same instant.
498
- fastAvailable: () => this.fast?.delegationAvailable() === true,
499
516
  maxSubagents: () => this.config.team.maxSubagents,
500
517
  hasApiKey: () => this.hasApiKey(),
501
518
  activeProvider: () => this.config.provider,
519
+ // Every child runs the lead's own model, so the lead's cost table
520
+ // is the one honest price for the dispatch total.
502
521
  modelCost: () => this.getModelInfo().cost,
503
- fastModelCost: () => {
504
- const ref = this.fast?.fastRef();
505
- return ref ? this.getModelInfoFor(ref).cost : undefined;
506
- },
507
- fastPricingUnknown: () => this.fast?.snapshot().pricingUnknown === true,
508
522
  withPausedWatchdog: (fn) => this.withPausedWatchdog(fn),
509
523
  }),
510
524
  ]
@@ -603,6 +617,10 @@ export class AgentController {
603
617
  // number only bites BECAUSE `task` races `ctx.signal` — the
604
618
  // executor's timeout is cooperative, so an override is inert for any
605
619
  // tool that does not listen (I-2).
620
+ //
621
+ // `dispatchTimeoutMs: 0` (the default) reaches the executor as an
622
+ // override of `0` = NO TIMER AT ALL: the dispatch lasts as long as its
623
+ // slowest child, and only an Esc ends it early (main-agent parity).
606
624
  ...(this.teamRegistered ? { task: config.team.dispatchTimeoutMs } : {}),
607
625
  },
608
626
  },
@@ -672,13 +690,6 @@ export class AgentController {
672
690
  });
673
691
  if (this.fast) {
674
692
  this.rebuildSystemPrompt();
675
- // Fast-tier children are counted from the outcome rather than from the
676
- // specs, so a downgraded child is not miscounted as a saving (§3.6).
677
- this.teamRuntime?.subscribe((event) => {
678
- if (event.type !== 'dispatch_end')
679
- return;
680
- this.fast?.noteDelegated(event.outcome.runs.filter((r) => r.tier === 'fast').length);
681
- });
682
693
  }
683
694
  // ONE LINE FOR THE TWO CONFIGURATION MISTAKES THAT ARE OTHERWISE INVISIBLE
684
695
  // (§3.3): the tier is on but unnamed, or named against a provider with no
@@ -738,11 +749,6 @@ export class AgentController {
738
749
  ? buildTeamBlock({
739
750
  maxSubagents: config.team.maxSubagents,
740
751
  maxConcurrent: config.team.maxConcurrent,
741
- // One cross-reference sentence, present only when the capability
742
- // is (§3.8). `this.fast` is null during the FIRST compose (the
743
- // wiring needs the Agent), which is why the constructor composes
744
- // again once it exists.
745
- fastDelegation: !!fastAvailable && config.fast.delegate,
746
752
  })
747
753
  : '',
748
754
  // BOTH FLAGS, exactly as team mode uses both, and for the identical
@@ -762,7 +768,6 @@ export class AgentController {
762
768
  fastBlock: fastAvailable
763
769
  ? buildFastBlock({
764
770
  model: tier.ok ? tier.ref.modelId : '',
765
- delegate: config.fast.delegate,
766
771
  review: config.fast.review,
767
772
  })
768
773
  : '',
@@ -116,6 +116,13 @@ export async function runHeadless(controller, prompt, options = {}) {
116
116
  }
117
117
  return;
118
118
  }
119
+ if (event.type === 'overseer') {
120
+ // ONE LINE PER DECISION, on stderr with the rest of the team feed:
121
+ // a machine consumer reads the JSON events; a human watching a long
122
+ // dispatch reads who the supervisor touched and why.
123
+ err.write(`[team] supervisor ${event.label}: ${event.decision.action} (${event.trigger}) ${event.decision.reason}\n`);
124
+ return;
125
+ }
119
126
  if (event.type === 'dispatch_end') {
120
127
  const ok = event.outcome.runs.filter((r) => r.phase === 'done' && !r.error).length;
121
128
  err.write(`[team] done ${ok}/${event.outcome.runs.length} ok` +
@@ -860,10 +860,6 @@ export function viewReducer(state, action) {
860
860
  const runs = action.specs.map((spec) => ({
861
861
  label: spec.label,
862
862
  description: spec.description,
863
- // Already downgraded by the normalizer when the tier was unavailable, so
864
- // the card and the report agree from the first frame (fast-model-tier
865
- // §3.4).
866
- tier: spec.tier,
867
863
  phase: 'queued',
868
864
  turns: 0,
869
865
  toolCalls: 0,
package/dist/cli.js CHANGED
@@ -160,11 +160,6 @@ function toFlags(opts) {
160
160
  fastModel: opts.fastModel,
161
161
  fastProvider: opts.fastProvider,
162
162
  fastReview: opts.fastReview,
163
- // …and again for `--fast-delegate` / `--no-fast-delegate`, one feature
164
- // later — NINE features, one trap. It is `fast.delegate`'s only channel:
165
- // there is no `ARAGON_FAST_DELEGATE`, so missing this line leaves a wrapper
166
- // with no way at all to say "do not delegate on this run".
167
- fastDelegate: opts.fastDelegate,
168
163
  // `--verbose` is just a shorthand for `--log-level debug`; an explicit
169
164
  // `--log-level` still wins, so the two can be combined without surprise.
170
165
  logLevel: opts.logLevel ?? (opts.verbose ? 'debug' : undefined),
@@ -1537,14 +1532,6 @@ function buildProgram() {
1537
1532
  .option('--fast-model <id>', 'The fast model id (implies nothing about --fast)')
1538
1533
  .option('--fast-provider <id>', 'Provider for the fast model (default: the main provider)')
1539
1534
  .option('--fast-review <n|off>', 'Turns between automatic fast reviews, or "off"')
1540
- // BOTH FORMS, POSITIVE FIRST, and here it matters more than anywhere above:
1541
- // `fast.delegate` is PERSISTED and defaults to TRUE, so declaring only
1542
- // `--no-fast-delegate` would make commander default `opts.fastDelegate` to
1543
- // `true` and force `delegate: true` on EVERY run that passed no flag at all
1544
- // — silently overwriting a user's stored `false`. Same polarity, same trap
1545
- // and same fix as `--update` / `--compaction`.
1546
- .option('--fast-delegate', 'Let the fast tier take mechanical sub-steps (the default)')
1547
- .option('--no-fast-delegate', 'Run every sub-step on the main model')
1548
1535
  // BOTH FORMS, positive first, for the reason recorded seven times above —
1549
1536
  // and this is the pair whose omission would be worst. `update.mode` is
1550
1537
  // PERSISTED and defaults to `'auto'`, so a lone `--no-update` would make
@@ -106,8 +106,7 @@ function formatFastStatus(controller) {
106
106
  else {
107
107
  lines.push(`Fast tier: ${state}. Model: ${cfg.model || '(not set)'}.`);
108
108
  }
109
- lines.push(`Review: ${cfg.review ? `every ${cfg.reviewEveryTurns} turns` : 'off'}. ` +
110
- `Delegation: ${cfg.delegate ? 'on' : 'off'}.`);
109
+ lines.push(`Review: ${cfg.review ? `every ${cfg.reviewEveryTurns} turns` : 'off'}.`);
111
110
  const snapshot = status.snapshot;
112
111
  const cost = snapshot.pricingUnknown
113
112
  ? `unknown (no price table for ${snapshot.model})`
@@ -124,7 +123,6 @@ function formatFastStatus(controller) {
124
123
  // numbers are `0` placeholders and `reviews >= reviewBudget` would be true.
125
124
  const budget = snapshot.budgetReached ? ' (budget reached - raise with /fast budget <n>)' : '';
126
125
  lines.push(`This session: ${snapshot.reviews}/${snapshot.reviewBudget} reviews${budget}, ` +
127
- `${snapshot.delegated} delegated subagents, ` +
128
126
  `in ${formatTokens(snapshot.usage.inputTokens)} / out ` +
129
127
  `${formatTokens(snapshot.usage.outputTokens)}, cost ${cost}.`);
130
128
  return lines.join('\n');
@@ -256,7 +254,7 @@ const COMMANDS = [
256
254
  },
257
255
  {
258
256
  name: 'team',
259
- description: 'Team subagents: status | on | off | max <n>',
257
+ description: 'Team subagents: status | on | off | max <n> | overseer on|off',
260
258
  /**
261
259
  * `/team on` CANNOT turn team mode on in a session that started without it,
262
260
  * and this command must not pretend otherwise (§4.5 / D-17 / P0-2).
@@ -296,7 +294,7 @@ const COMMANDS = [
296
294
  ? `\nRunning: ${snapshot.runs.map((r) => `${r.label} (${r.phase})`).join(', ')}`
297
295
  : '';
298
296
  ctx.notify('info', `Team mode: ${state}. Max subagents: ${cfg.maxSubagents}, ` +
299
- `concurrent: ${cfg.maxConcurrent}.${roster}`);
297
+ `concurrent: ${cfg.maxConcurrent}, supervisor: ${cfg.overseer ? 'on' : 'off'} (needs the fast tier).${roster}`);
300
298
  return;
301
299
  }
302
300
  if (verb === 'on' || verb === 'off') {
@@ -333,13 +331,28 @@ const COMMANDS = [
333
331
  ctx.toast('success', `Max subagents set to ${applied.maxSubagents}.`);
334
332
  return;
335
333
  }
336
- ctx.notify('warn', `Unknown argument "${verb}" - use /team [status|on|off|max <n>].`);
334
+ if (verb === 'overseer') {
335
+ if (value !== 'on' && value !== 'off') {
336
+ ctx.notify('warn', 'Usage: /team overseer on|off');
337
+ return;
338
+ }
339
+ // Read LIVE per dispatch by the runtime, so no rebuild is needed - the
340
+ // same reason this command does not touch the tool array (its own header
341
+ // comment). Clamped by `setTeamConfig` and again on the persist path.
342
+ const applied = controller.setTeamConfig({ overseer: value === 'on' });
343
+ ctx.persistConfig({
344
+ team: { overseer: applied.overseer },
345
+ });
346
+ ctx.toast('success', `Dispatch supervisor ${applied.overseer ? 'on' : 'off'}.`);
347
+ return;
348
+ }
349
+ ctx.notify('warn', `Unknown argument "${verb}" - use /team [status|on|off|max <n>|overseer on|off].`);
337
350
  },
338
351
  },
339
352
  {
340
353
  name: 'fast',
341
354
  description: 'Fast model tier: status | on | off | model <id> | provider <id> | same | ' +
342
- 'review <n|off> | budget <n> | delegate on|off',
355
+ 'review <n|off> | budget <n>',
343
356
  /**
344
357
  * `/fast on` CANNOT turn the tier on in a session that started without it,
345
358
  * for the reason `/team on` above records at length: the tool array is built
@@ -516,20 +529,8 @@ const COMMANDS = [
516
529
  `${snapshot.reviews} used.`);
517
530
  return;
518
531
  }
519
- if (verb === 'delegate') {
520
- const raw = (value ?? '').trim().toLowerCase();
521
- if (raw !== 'on' && raw !== 'off') {
522
- ctx.notify('warn', 'Usage: /fast delegate <on|off>');
523
- return;
524
- }
525
- if (!persist({ delegate: raw === 'on' }))
526
- return;
527
- ctx.toast('success', `Fast delegation ${raw}.`);
528
- return;
529
- }
530
532
  ctx.notify('warn', `Unknown argument "${verb}" - use /fast ` +
531
- '[status|on|off|model <id>|provider <id>|same|review <n|off>|budget <n>|' +
532
- 'delegate on|off].');
533
+ '[status|on|off|model <id>|provider <id>|same|review <n|off>|budget <n>].');
533
534
  },
534
535
  },
535
536
  {
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Ctrl+I project-index trigger (bundled `project-indexer` skill).
3
+ *
4
+ * WHY A MODULE AND NOT INLINE IN `App.tsx`. `App.tsx` is a documented
5
+ * historical oversize file (see `.claude-index/config.md`); the convention
6
+ * those entries record is that a feature only wires into it and keeps its
7
+ * logic in a small named module. Everything here is pure and unit-tested:
8
+ * the key handler in `App.tsx` guards, confirms, and dispatches; this file
9
+ * owns the words and the names.
10
+ *
11
+ * THE TRIGGER IS A SKILL COMMAND, NOT A REIMPLEMENTATION. Ctrl+I dispatches
12
+ * `/skill:project-indexer <args>` through the ordinary command path, so the
13
+ * load/activate/queue-frame/submit lifecycle is the SAME one a typed command
14
+ * gets, and the whole indexing run streams into the transcript like any
15
+ * agent turn. All strings here are ASCII-only: `commands/` is inside the
16
+ * `glyphs.test.ts` scan scope and every byte reaches a terminal.
17
+ */
18
+ /** The bundled skill Ctrl+I runs. Bundled skills are read-only by scope. */
19
+ export declare const INDEX_SKILL_NAME = "project-indexer";
20
+ /** True when a previous run left a main index file behind (rebuild, not first build). */
21
+ export declare function hasProjectIndex(cwd: string): boolean;
22
+ /**
23
+ * Args handed to `/skill:project-indexer`. They land in the skill body's
24
+ * `$ARGUMENTS` placeholder, so the model sees WHY the turn started - a
25
+ * confirmed keypress, not a typed request - and skips asking "proceed?" a
26
+ * second time (the skill's own edge-case list says exactly that).
27
+ */
28
+ export declare function indexBuildInvocationArgs(hasExistingIndex: boolean): string;
29
+ export interface IndexConfirmInput {
30
+ cwd: string;
31
+ /** True when `.claude-index/index.md` already exists (rebuild, not first build). */
32
+ hasExistingIndex: boolean;
33
+ }
34
+ /**
35
+ * The Yes/No dialog body. It names WHAT WILL BE WRITTEN - an index run is a
36
+ * batch of file writes the user never typed, and the one question a confirm
37
+ * gate must answer is "what did I just agree to?".
38
+ */
39
+ export declare function buildIndexConfirmSummary(input: IndexConfirmInput): string;
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Ctrl+I project-index trigger (bundled `project-indexer` skill).
3
+ *
4
+ * WHY A MODULE AND NOT INLINE IN `App.tsx`. `App.tsx` is a documented
5
+ * historical oversize file (see `.claude-index/config.md`); the convention
6
+ * those entries record is that a feature only wires into it and keeps its
7
+ * logic in a small named module. Everything here is pure and unit-tested:
8
+ * the key handler in `App.tsx` guards, confirms, and dispatches; this file
9
+ * owns the words and the names.
10
+ *
11
+ * THE TRIGGER IS A SKILL COMMAND, NOT A REIMPLEMENTATION. Ctrl+I dispatches
12
+ * `/skill:project-indexer <args>` through the ordinary command path, so the
13
+ * load/activate/queue-frame/submit lifecycle is the SAME one a typed command
14
+ * gets, and the whole indexing run streams into the transcript like any
15
+ * agent turn. All strings here are ASCII-only: `commands/` is inside the
16
+ * `glyphs.test.ts` scan scope and every byte reaches a terminal.
17
+ */
18
+ import { existsSync } from 'node:fs';
19
+ import { join } from 'node:path';
20
+ /** The bundled skill Ctrl+I runs. Bundled skills are read-only by scope. */
21
+ export const INDEX_SKILL_NAME = 'project-indexer';
22
+ /** True when a previous run left a main index file behind (rebuild, not first build). */
23
+ export function hasProjectIndex(cwd) {
24
+ try {
25
+ return existsSync(join(cwd, '.claude-index', 'index.md'));
26
+ }
27
+ catch {
28
+ // A cwd that cannot be probed simply has no index to reuse.
29
+ return false;
30
+ }
31
+ }
32
+ /**
33
+ * Args handed to `/skill:project-indexer`. They land in the skill body's
34
+ * `$ARGUMENTS` placeholder, so the model sees WHY the turn started - a
35
+ * confirmed keypress, not a typed request - and skips asking "proceed?" a
36
+ * second time (the skill's own edge-case list says exactly that).
37
+ */
38
+ export function indexBuildInvocationArgs(hasExistingIndex) {
39
+ return hasExistingIndex
40
+ ? 'The user pressed Ctrl+I and confirmed. Rebuild the project index for this workspace now, reusing the existing .claude-index/config.md preferences.'
41
+ : 'The user pressed Ctrl+I and confirmed. Build the project index for this workspace now, asking the configuration questions first.';
42
+ }
43
+ /**
44
+ * The Yes/No dialog body. It names WHAT WILL BE WRITTEN - an index run is a
45
+ * batch of file writes the user never typed, and the one question a confirm
46
+ * gate must answer is "what did I just agree to?".
47
+ */
48
+ export function buildIndexConfirmSummary(input) {
49
+ const action = input.hasExistingIndex ? 'Rebuild' : 'Build';
50
+ const existing = input.hasExistingIndex
51
+ ? '\nThe existing .claude-index/config.md preferences are reused.'
52
+ : '\nThe bundled skill asks a few configuration questions first.';
53
+ return (`${action} the project index for this workspace?\n` +
54
+ `\nRuns the bundled "${INDEX_SKILL_NAME}" skill as a normal agent turn:` +
55
+ `\n- scans the workspace (exclusions apply), extracts exported symbols` +
56
+ `\n- writes .claude-index/index.md and .claude-index/config.md` +
57
+ `\n- updates CLAUDE.md (index section + Clean Code Guidelines)` +
58
+ `${existing}` +
59
+ `\nWorkspace: ${input.cwd}` +
60
+ `\nEvery step is visible in the transcript; Esc interrupts.`);
61
+ }
@@ -13,15 +13,17 @@
13
13
  * turn the child already ran, and receives one partial sentence for all of it.
14
14
  * Two fast-model summarizations is the cheap side of that trade.
15
15
  *
16
- * A CHILD MANAGER IS THE SAME `Compactor` UNDER A DIFFERENT POLICY, and the
17
- * overlay needs TWO mechanisms rather than one (RV-2):
16
+ * SINCE MAIN-AGENT PARITY, A CHILD MANAGER RUNS THE LEAD'S OWN POLICY. The only
17
+ * overlay left is the CONNECTION (provider / model / base URL / manual window),
18
+ * which must follow the model the child actually runs on. `keepRecentTurns`,
19
+ * `onFailure`, `useFastTier`, `archive` and the per-run compaction bound are all
20
+ * read from the SAME live config the lead reads, so a user who tunes compaction
21
+ * tunes every child identically - the requirement "same mode and configuration
22
+ * as the main agent", taken literally.
18
23
  *
19
- * A. A CONFIG VIEW, for `keepRecentTurns` / `onFailure` / `useFastTier`, which
20
- * `Compactor` reads through `deps.getConfig()`.
21
- * B. AN INSTANCE BOUND, for `maxPerRun`, which is STRUCTURAL and has no config
22
- * path by design (`limits.ts` header). Without it the child silently
23
- * inherits the lead's 5 - two and a half times the bound the reversal above
24
- * rests on.
24
+ * THE INSTANCE BOUND stays (mechanism B of RV-2) because `maxPerRun` is
25
+ * STRUCTURAL with no config path by design (`limits.ts` header); it is now the
26
+ * SAME `COMPACTION_LIMITS.maxPerRun` the lead's own manager runs under.
25
27
  *
26
28
  * NO UI. A child's compaction produces no transcript card (DH-7): the lead's
27
29
  * transcript describes the LEAD's context, and a card there would claim the
@@ -13,15 +13,17 @@
13
13
  * turn the child already ran, and receives one partial sentence for all of it.
14
14
  * Two fast-model summarizations is the cheap side of that trade.
15
15
  *
16
- * A CHILD MANAGER IS THE SAME `Compactor` UNDER A DIFFERENT POLICY, and the
17
- * overlay needs TWO mechanisms rather than one (RV-2):
16
+ * SINCE MAIN-AGENT PARITY, A CHILD MANAGER RUNS THE LEAD'S OWN POLICY. The only
17
+ * overlay left is the CONNECTION (provider / model / base URL / manual window),
18
+ * which must follow the model the child actually runs on. `keepRecentTurns`,
19
+ * `onFailure`, `useFastTier`, `archive` and the per-run compaction bound are all
20
+ * read from the SAME live config the lead reads, so a user who tunes compaction
21
+ * tunes every child identically - the requirement "same mode and configuration
22
+ * as the main agent", taken literally.
18
23
  *
19
- * A. A CONFIG VIEW, for `keepRecentTurns` / `onFailure` / `useFastTier`, which
20
- * `Compactor` reads through `deps.getConfig()`.
21
- * B. AN INSTANCE BOUND, for `maxPerRun`, which is STRUCTURAL and has no config
22
- * path by design (`limits.ts` header). Without it the child silently
23
- * inherits the lead's 5 - two and a half times the bound the reversal above
24
- * rests on.
24
+ * THE INSTANCE BOUND stays (mechanism B of RV-2) because `maxPerRun` is
25
+ * STRUCTURAL with no config path by design (`limits.ts` header); it is now the
26
+ * SAME `COMPACTION_LIMITS.maxPerRun` the lead's own manager runs under.
25
27
  *
26
28
  * NO UI. A child's compaction produces no transcript card (DH-7): the lead's
27
29
  * transcript describes the LEAD's context, and a card there would claim the
@@ -45,11 +47,11 @@ export function createChildContextManager(req, deps) {
45
47
  * which is the same reason `subagent.ts` reuses the lead's `getApiKey` closure
46
48
  * rather than the resolved key.
47
49
  *
48
- * `useFastTier: true` EXPRESSES A PREFERENCE, NOT AN OUTCOME. `resolveFastTier`
49
- * still decides, and when nothing resolves the child falls back exactly as the
50
- * lead does - to its OWN model, because `provider` / `model` are overlaid here
51
- * too.
52
- *
50
+ * `useFastTier` is read LIVE from the base config (main-agent
51
+ * parity - no overlay): it expresses a PREFERENCE, not an outcome.
52
+ * `resolveFastTier` still decides, and when nothing resolves the child
53
+ * falls back exactly as the lead does - to its OWN model, because
54
+ * `provider` / `model` are overlaid here too.
53
55
  * Failed summaries preserve the child's task just as they preserve the lead's.
54
56
  */
55
57
  const childConfig = () => {
@@ -63,16 +65,11 @@ export function createChildContextManager(req, deps) {
63
65
  baseUrl: req.model.baseUrl,
64
66
  // The lead's manual limit describes its connection, not every worker.
65
67
  contextWindow: sameConnection ? base.contextWindow : null,
66
- compaction: {
67
- ...base.compaction,
68
- keepRecentTurns: COMPACTION_LIMITS.childKeepRecentTurns,
69
- onFailure: 'stop',
70
- useFastTier: true,
71
- // ONE ARCHIVE PER LEAD COMPACTION IS AUDITABLE; TWENTY PER DISPATCH IS
72
- // NOISE. The child's dropped slice is therefore never retained either -
73
- // `runCompaction` reads this key before it stashes anything.
74
- archive: false,
75
- },
68
+ // NO POLICY OVERLAY (main-agent parity): `keepRecentTurns`, `onFailure`,
69
+ // `useFastTier` and `archive` are read LIVE from the base config, so the
70
+ // child compacts under exactly the policy the lead compacts under. A
71
+ // settings-screen edit mid-dispatch reaches a running child because this
72
+ // view is re-read on every call.
76
73
  };
77
74
  };
78
75
  const meter = new ContextMeter({
@@ -121,8 +118,9 @@ export function createChildContextManager(req, deps) {
121
118
  // user's transcript with no card to explain which agent it is about.
122
119
  notify: () => { },
123
120
  },
124
- // MECHANISM B - the instance bound (RV-2 / DH-16).
125
- { maxPerRun: COMPACTION_LIMITS.childMaxPerRun });
121
+ // MECHANISM B - the instance bound (RV-2 / DH-16). The SAME bound the
122
+ // lead's own manager runs under (main-agent parity).
123
+ { maxPerRun: COMPACTION_LIMITS.maxPerRun });
126
124
  return {
127
125
  // THERE IS NO `/compact` FOR A CHILD, so nothing is ever queued and the
128
126
  // manual branch of `shouldCompact` is unreachable by construction.
@@ -28,8 +28,6 @@ export declare const COMPACTION_LIMITS: {
28
28
  readonly archiveMaxFiles: 20;
29
29
  readonly archiveMaxAgeMs: 604800000;
30
30
  readonly archiveMaxBytes: 8000000;
31
- readonly childKeepRecentTurns: 2;
32
- readonly childMaxPerRun: 2;
33
31
  readonly cardTextRows: 6;
34
32
  readonly statusCompactCols: 100;
35
33
  };
@@ -29,8 +29,6 @@ export const COMPACTION_LIMITS = {
29
29
  archiveMaxFiles: 20,
30
30
  archiveMaxAgeMs: 604_800_000,
31
31
  archiveMaxBytes: 8_000_000,
32
- childKeepRecentTurns: 2,
33
- childMaxPerRun: 2,
34
32
  cardTextRows: 6,
35
33
  statusCompactCols: 100,
36
34
  };
@@ -20,7 +20,7 @@ export declare const LOG_CONFIG_SET_KEYS: readonly ["log.level", "log.toFile", "
20
20
  * switch falls through every case, writes nothing, and still prints
21
21
  * `Set team.enabled = false`.
22
22
  */
23
- export declare const TEAM_CONFIG_SET_KEYS: readonly ["team.enabled", "team.maxSubagents", "team.maxConcurrent", "team.subagentTimeoutMs", "team.dispatchTimeoutMs", "team.maxTurnsPerSubagent"];
23
+ export declare const TEAM_CONFIG_SET_KEYS: readonly ["team.enabled", "team.maxSubagents", "team.maxConcurrent", "team.subagentTimeoutMs", "team.dispatchTimeoutMs", "team.maxTurnsPerSubagent", "team.overseer"];
24
24
  /**
25
25
  * Config keys under `retry.` that `aragon config set` accepts
26
26
  * (llm-api-retry-backoff §8.2).
@@ -42,7 +42,7 @@ export declare const RETRY_CONFIG_SET_KEYS: readonly ["retry.enabled", "retry.ma
42
42
  * would silently never take effect, and `config set` would still print
43
43
  * `Set fast.model = haiku`.
44
44
  */
45
- export declare const FAST_CONFIG_SET_KEYS: readonly ["fast.enabled", "fast.provider", "fast.model", "fast.baseUrl", "fast.thinkingLevel", "fast.delegate", "fast.review", "fast.reviewEveryTurns", "fast.reviewContextTurns", "fast.reviewMaxChars", "fast.reviewMaxPerSession"];
45
+ export declare const FAST_CONFIG_SET_KEYS: readonly ["fast.enabled", "fast.provider", "fast.model", "fast.baseUrl", "fast.thinkingLevel", "fast.review", "fast.reviewEveryTurns", "fast.reviewContextTurns", "fast.reviewMaxChars", "fast.reviewMaxPerSession"];
46
46
  /**
47
47
  * Config keys under `update.` that `aragon config set` accepts (cli-auto-update
48
48
  * §4.1).