@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.
Files changed (43) hide show
  1. package/CHANGELOG.md +107 -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 +7 -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
package/CHANGELOG.md CHANGED
@@ -5,57 +5,89 @@ All notable changes to `@aragon-agent/cli` are documented here. Entries at
5
5
  — those releases really were published under that name, so they are left as they
6
6
  were written.
7
7
 
8
- ## Unreleased
9
-
10
- ### 启动与缩窗不再误报 Frame diff lost sync
11
-
12
- - 启动即现的 "Frame diffing lost sync with the terminal and fell back to a
13
- full repaint … `--no-diff-render`" 警告已消除:Ink 挂载/卸载经渲染 stdout 写出的
14
- 光标隐藏/恢复序列(`ESC[?25l` / `ESC[?25h`)由帧写入器直接透传——不进差分、
15
- 不失效缓存、不计 fallback,也不再消耗会话的首写豁免。启动序列恢复
16
- "种子帧透传 → 首帧全量 → 之后纯 diff"。
17
- - 终端高度缩小(拖小窗口、改字号、面板重排)不再触发同一条警告:Ink 的
18
- `clearTerminal` 整帧写入被差分器识别为 Ink 自身写法,透传并失效缓存但不计
19
- fallback;该控制写同样不消耗首写豁免,其后的首个 log-update 帧仍按种子帧豁免,
20
- 下一帧自然全量重绘。
21
- - `/perf` 的 `fallbacks` 重新从 0 起步,"健康会话 fallbacks === 0" 的诊断信号
22
- (AC-11)恢复可判。
23
-
24
- ### Consistent TUI styling
25
-
26
- - Interactive sessions always use Unicode and default to truecolor, including
27
- PowerShell sessions without terminal or UTF locale markers. Ink's actual color
28
- output follows the same policy, with renderer state restored on exit.
29
- - Explicit color opt-outs keep Unicode borders and icons; the opening logo now
30
- depends only on available space. Headless terminal detection is unchanged.
31
- - Reloading model settings preserves session display preferences. Syntax
32
- highlighting follows the same color policy, and status icons use consistent
33
- single-column glyphs to prevent narrow-frame overflow.
34
- - Hiding the editor caret while scrolling preserves draft geometry, preventing
35
- a layout update loop when entering text above the full startup logo.
36
- - Register exit cleanup before mounting the application, so immediate exits and
37
- initial render failures restore terminal and color state reliably. Failed
38
- mounts no longer mark an auto-installed build as healthy.
39
-
40
- ### Model context window
41
-
42
- - Discover context limits at startup and after connection changes without blocking
43
- input. The status bar and compaction use the discovered limit; `/context` names
44
- API versus catalog metadata. Unknown windows show `?`, not a fabricated 128k
45
- capacity or percentage. Manual `contextWindow` overrides retain precedence.
46
- - Window overrides now apply to tail relief and compaction progress as well as
47
- the gauge. Workers on different models/endpoints do not inherit the lead's
48
- override. API-key changes refresh metadata, and ending a session cancels its
49
- pending model queries. `/context` also hides unknown capacities/percentages.
50
-
51
- ### TUI 输入与排队可靠性
52
-
53
- - Shift+Enter 与粘贴按输入顺序处理;普通 Enter 保持弹层兼容,混合输入最多提交一次,
54
- 余稿及拒绝提交的正文保留。结束输入流时完整排空,不泄漏内部控制标记。
55
- - 超限粘贴拒绝同一外部输入块的正文和 Enter,避免误提交旧稿;未闭合粘贴继续按正文消费。
56
- - 拖选后仅 Ctrl+C 复制;文字变化与丢失鼠标释放时取消无效高亮,避免卡住视口。
57
- - 固定 Queue 状态行按消息 ID 回执更新,支持窄屏、暂停与多条消息计数。
58
- - 保存和退出保留所有未发送全文;恢复先验证存档,旧 queued 作为警告展示且不自动重放。
8
+ ## Unreleased
9
+
10
+ ### Team dispatch supervisor (team-overseer)
11
+
12
+ - **A time ceiling on a subagent is now an inspection trigger, not a death
13
+ sentence.** With the supervisor on (new key `team.overseer`, default
14
+ `true`, needs the fast tier), two soft timers per child - event silence at
15
+ `idleTimeoutMs` and a wall clock at `subagentTimeoutMs` (or 5 minutes when
16
+ no ceiling is configured) - fire one bounded fast-tier inspection. The
17
+ supervisor decides wait / nudge / replace / abandon; the runtime applies,
18
+ funnelling every lifecycle change through the worker loop that awaits the
19
+ child, so a replacement is never built for nobody.
20
+ - **A nudge is steering; a replace is a rebuild with an amended brief.**
21
+ Guidance is queued at core's safe top-of-loop checkpoint; a replacement
22
+ carries the supervisor's reason, the dead child's `filesTouched`, and the
23
+ original brief bounded to the same prompt budget. A post-mortem inspection
24
+ diagnoses a child that died silently (watchdog death, stream loss) and may
25
+ re-assign it once.
26
+ - **Budgets are structural, not settings**: 4 inspections and 1 replacement
27
+ per child per dispatch; past them the child runs under its own (lengthened
28
+ while supervised) idle watchdog and the supervisor goes quiet. An
29
+ unparseable or failing supervision call always becomes `wait` -
30
+ supervision must never kill a healthy child.
31
+ - Every applied decision is visible three ways: the report's Supervisor
32
+ section (`[silence 30s] a1: nudge - ...`), the team panel and the `-p`
33
+ stderr line, and a `debug` log event. `aragon exec` emits it as an
34
+ additive `team` subtype `overseer` (`EXEC_SCHEMA_VERSION` unchanged).
35
+ - New command `/team overseer on|off` (refused mid-dispatch, like every
36
+ other `/team` setting) and new config key `team.overseer`, also settable
37
+ as `aragon config set team.overseer false`. With the supervisor off - or
38
+ no fast tier - every path is byte-for-byte the pre-feature regime:
39
+ `subagentTimeoutMs` is a hard abort and silence is the child's own
40
+ watchdog.
41
+
42
+ ### 启动与缩窗不再误报 Frame diff lost sync
43
+
44
+ - 启动即现的 "Frame diffing lost sync with the terminal and fell back to a
45
+ full repaint … `--no-diff-render`" 警告已消除:Ink 挂载/卸载经渲染 stdout 写出的
46
+ 光标隐藏/恢复序列(`ESC[?25l` / `ESC[?25h`)由帧写入器直接透传——不进差分、
47
+ 不失效缓存、不计 fallback,也不再消耗会话的首写豁免。启动序列恢复
48
+ "种子帧透传 → 首帧全量 → 之后纯 diff"。
49
+ - 终端高度缩小(拖小窗口、改字号、面板重排)不再触发同一条警告:Ink 的
50
+ `clearTerminal` 整帧写入被差分器识别为 Ink 自身写法,透传并失效缓存但不计
51
+ fallback;该控制写同样不消耗首写豁免,其后的首个 log-update 帧仍按种子帧豁免,
52
+ 下一帧自然全量重绘。
53
+ - `/perf` 的 `fallbacks` 重新从 0 起步,"健康会话 fallbacks === 0" 的诊断信号
54
+ (AC-11)恢复可判。
55
+
56
+ ### Consistent TUI styling
57
+
58
+ - Interactive sessions always use Unicode and default to truecolor, including
59
+ PowerShell sessions without terminal or UTF locale markers. Ink's actual color
60
+ output follows the same policy, with renderer state restored on exit.
61
+ - Explicit color opt-outs keep Unicode borders and icons; the opening logo now
62
+ depends only on available space. Headless terminal detection is unchanged.
63
+ - Reloading model settings preserves session display preferences. Syntax
64
+ highlighting follows the same color policy, and status icons use consistent
65
+ single-column glyphs to prevent narrow-frame overflow.
66
+ - Hiding the editor caret while scrolling preserves draft geometry, preventing
67
+ a layout update loop when entering text above the full startup logo.
68
+ - Register exit cleanup before mounting the application, so immediate exits and
69
+ initial render failures restore terminal and color state reliably. Failed
70
+ mounts no longer mark an auto-installed build as healthy.
71
+
72
+ ### Model context window
73
+
74
+ - Discover context limits at startup and after connection changes without blocking
75
+ input. The status bar and compaction use the discovered limit; `/context` names
76
+ API versus catalog metadata. Unknown windows show `?`, not a fabricated 128k
77
+ capacity or percentage. Manual `contextWindow` overrides retain precedence.
78
+ - Window overrides now apply to tail relief and compaction progress as well as
79
+ the gauge. Workers on different models/endpoints do not inherit the lead's
80
+ override. API-key changes refresh metadata, and ending a session cancels its
81
+ pending model queries. `/context` also hides unknown capacities/percentages.
82
+
83
+ ### TUI 输入与排队可靠性
84
+
85
+ - Shift+Enter 与粘贴按输入顺序处理;普通 Enter 保持弹层兼容,混合输入最多提交一次,
86
+ 余稿及拒绝提交的正文保留。结束输入流时完整排空,不泄漏内部控制标记。
87
+ - 超限粘贴拒绝同一外部输入块的正文和 Enter,避免误提交旧稿;未闭合粘贴继续按正文消费。
88
+ - 拖选后仅 Ctrl+C 复制;文字变化与丢失鼠标释放时取消无效高亮,避免卡住视口。
89
+ - 固定 Queue 状态行按消息 ID 回执更新,支持窄屏、暂停与多条消息计数。
90
+ - 保存和退出保留所有未发送全文;恢复先验证存档,旧 queued 作为警告展示且不自动重放。
59
91
 
60
92
  ### Removed
61
93
 
@@ -451,6 +483,30 @@ were written.
451
483
 
452
484
  ## Unreleased
453
485
 
486
+ ### Project index on Ctrl+I (bundled `project-indexer` skill)
487
+
488
+ - The `project-indexer` skill now ships **bundled** with the package
489
+ (`<pkg>/skills/project-indexer`, read-only, alongside `skill-creator`): it
490
+ generates the `.claude-index/` navigation map and injects the Google-style
491
+ Clean Code Guidelines section into `CLAUDE.md`, adapted from the standalone
492
+ Claude Code skill (ask_user instead of AskUserQuestion; body condensed under
493
+ the 30 KB invocation budget with the four templates bundled as files).
494
+ - New keybinding `Ctrl+I`: opens a Yes/No confirm that names every write
495
+ target, then dispatches `/skill:project-indexer` through the ordinary
496
+ command path - the indexing run is a normal agent turn whose tool calls
497
+ stream into the transcript. While a run is in flight the key warns instead
498
+ of queueing; with skills disabled or the skill shadowed it explains instead
499
+ of failing silently.
500
+ - `Ctrl+I` is legacy-encoded as the Tab byte, so the stdin filter's two
501
+ translators (win32-input-mode on Windows, kitty CSI-u / modifyOtherKeys
502
+ elsewhere) now emit a distinct NUL-delimited `INDEX_KEY_FRAME` for it -
503
+ the same framing technique `Shift+Enter` uses. On terminals with neither
504
+ protocol the key keeps its legacy Tab meaning (completion).
505
+ - The header names the key next to the detail toggle: `^I Index` collapsed,
506
+ `^I build index` in the `Ctrl+G` expanded state; hidden below 72 columns and
507
+ while an overlay is open. `/skill:project-indexer` and `/project-indexer`
508
+ remain available as typed commands.
509
+
454
510
  ### Added
455
511
 
456
512
  - **Drag the mouse to select text, and release to copy it.** Full-screen mode
package/README.md CHANGED
@@ -406,7 +406,7 @@ signal ends the run (`130` SIGINT, `143` SIGTERM, `129` SIGHUP).
406
406
  | `/save [file]` | Save the session to JSON |
407
407
  | `/resume [file]` | Load a saved session |
408
408
  | `/plan [on\|off\|status]` | Toggle plan mode - the keyboard-free equivalent of `Shift+Tab` |
409
- | `/team [on\|off\|max <n>]` | Team subagents: status and the live roster, the on/off switch, the fan-out width |
409
+ | `/team [on\|off\|max <n>\|overseer on\|off]` | Team subagents: status and the live roster, the on/off switch, the fan-out width, the dispatch supervisor |
410
410
  | `/todo [on\|off\|panel on\|off\|follow <mode>\|clear\|continue]` | Todo planning: status, the on/off switch, the rail, follow-through, and picking an unfinished plan back up |
411
411
  | `/retry [show\|on\|off\|max <n>]` | API retry: the effective ladder, the on/off switch, the retry count. Takes effect **in the running session**, children included |
412
412
  | `/fast [on\|off\|model <id>\|provider <id>\|same\|review <n\|off>]` | Fast model tier: status and this session's totals, the on/off switch, which model, the review cadence |
@@ -436,6 +436,7 @@ completes, `Up` / `Down` moves the selection, `Esc` closes it.
436
436
  | `Ctrl+J` / `Alt+Enter` / `Shift+Enter` | 插入换行。应用默认请求终端区分修饰键(Windows 用 win32-input-mode,macOS/Linux 用 kitty/modifyOtherKeys),Shift+Enter 在支持的终端开箱即用;不支持的终端用 Ctrl+J。参见 [终端设置](#terminal-setup-for-shiftenter) |
437
437
  | `Shift+Tab` | Toggle **plan mode** (`BUILD` <-> `PLAN`). Equivalent: `/plan`. On Windows this needs a console flag Node < 22.17 does not set; aragon sets it at startup — see [Requirements](#requirements) |
438
438
  | `Ctrl+P` | The same toggle, over a channel no Windows console can swallow. Use it if `Shift+Tab` does nothing |
439
+ | `Ctrl+I` | **Build the project index** (bundled `project-indexer` skill). Asks for confirmation, then runs as a normal agent turn that writes `.claude-index/` and updates `CLAUDE.md`. On terminals without keyboard enhancement `Ctrl+I` is the Tab byte and completes input instead |
439
440
  | `Esc` | Close an overlay or popup; otherwise ask for interruption confirmation. While idle, cancel pending auto-continuation |
440
441
  | `Esc` ×2 | Interrupt the run when pressed within 1.5 seconds. Other keys or closing a menu cancel the first press |
441
442
  | `Esc` again after interruption | **Force-stop** a run that is still stopping, kill foreground shell commands and return to `idle`; background services remain running |
@@ -1146,6 +1147,17 @@ subagents wrote the same file the header says so, phrased as a warning rather
1146
1147
  than a fact: the file list is derived from `write_file` / `edit_file`, so a
1147
1148
  write made through `bash` is invisible to it.
1148
1149
 
1150
+ **The dispatch supervisor.** A time ceiling on a subagent is an inspection
1151
+ trigger, not a death sentence — as long as the fast tier is enabled. Two soft
1152
+ timers per child (event silence and a wall clock) hand a bounded snapshot to the
1153
+ fast model, which decides: keep waiting, steer a nudge in, rebuild the child
1154
+ with an amended brief, or abandon it with a recorded reason. The budgets are
1155
+ structural, not settings — 4 inspections and 1 replacement per child per
1156
+ dispatch, after which the child runs under its own hard ceilings and the
1157
+ supervisor goes quiet. Every applied decision is recorded in the report's
1158
+ Supervisor section; `/team overseer off` (or no fast tier) restores the
1159
+ pre-supervisor regime exactly.
1160
+
1149
1161
  **Talking to each other.** Subagents get two extra tools, `team_send` and
1150
1162
  `team_wait`. Messages are delivered by attaching them to the recipient's next
1151
1163
  tool result, so nobody has to poll and nobody's work is interrupted. The limits
@@ -1627,6 +1639,39 @@ A skill is a reusable expert procedure stored on disk: a directory containing a
1627
1639
  `scripts/` and `assets/`. The format matches Claude Code's, so an existing
1628
1640
  community skill directory works as-is.
1629
1641
 
1642
+ ### Project index - `Ctrl+I`
1643
+
1644
+ Two skills ship **bundled** with the package (`<pkg>/skills`, read-only):
1645
+ `skill-creator`, and `project-indexer` - a codebase indexer that generates a
1646
+ `.claude-index/` navigation map (project overview, feature map, file index,
1647
+ exported symbols) and injects a Google-style **Clean Code Guidelines** section
1648
+ into `CLAUDE.md` so future sessions inherit the project's code-quality
1649
+ thresholds.
1650
+
1651
+ `Ctrl+I` is its dedicated keybinding. The flow is deliberately boring:
1652
+
1653
+ 1. A **Yes/No confirm** opens (what will be written is spelled out - the
1654
+ dialog names `.claude-index/` and `CLAUDE.md` before anything happens).
1655
+ 2. Approving dispatches `/skill:project-indexer` through the ordinary command
1656
+ path, so the run is a normal agent turn: every `glob` / `read_file` /
1657
+ `write_file` streams into the transcript as a tool card, `Esc`
1658
+ interrupts it, and the follow-up configuration questions (exclusions,
1659
+ priority directories, clean-code preset) arrive through the same `ask_user`
1660
+ overlay any other turn uses.
1661
+ 3. The header names the key: `^I Index` collapsed, `^I build index` once
1662
+ `Ctrl+G` expands the detail row.
1663
+
1664
+ An existing `.claude-index/config.md` is reused on rebuild. While the agent is
1665
+ running, `Ctrl+I` warns instead of queueing. `/skill:project-indexer` and
1666
+ `/project-indexer` work as typed commands too.
1667
+
1668
+ > **Terminal support:** `Ctrl+I` is the same byte as `Tab` in legacy terminal
1669
+ > encoding. AragonAgent already asks for modified-key reporting at startup
1670
+ > (win32-input-mode on Windows, kitty/modifyOtherKeys elsewhere - the same
1671
+ > machinery that makes `Shift+Enter` work), and under those protocols the key
1672
+ > is distinguishable and the binding works. On a terminal that implements
1673
+ > neither, `Ctrl+I` simply behaves as `Tab`.
1674
+
1630
1675
  ### Progressive disclosure
1631
1676
 
1632
1677
  This is the part that makes skills cheap enough to install a lot of:
@@ -1956,9 +2001,10 @@ env / `.env` → CLI flags**.
1956
2001
  | `team.enabled` | `true` | Master switch for team subagents. `false` means the `task` tool is never registered — `/team on` in such a session saves the setting for next launch and says so, rather than advertising a tool that is not there. |
1957
2002
  | `team.maxSubagents` | `5` | Subagents per dispatch, clamped to `[1, 10]`. The 10 is the hard ceiling and is enforced twice — on the config value and again on what the model asks for — so hand-editing this file cannot raise it. |
1958
2003
  | `team.maxConcurrent` | `3` | Subagents in flight at once, clamped to `[1, 10]` and capped at `maxSubagents`. A separate knob because provider rate limits are a different constraint from context economics. |
1959
- | `team.subagentTimeoutMs` | `0` | One subagent's wall clock before it is aborted, clamped to `[0, 1800000]`. `0` (the default) = no ceiling — a wedged subagent still dies on its own idle watchdog, exactly like the lead. |
2004
+ | `team.subagentTimeoutMs` | `0` | One subagent's wall clock, clamped to `[0, 1800000]`. With the supervisor off it is a hard abort; with it on (the default) a positive value is the **first wall-clock inspection** — the supervisor looks and decides instead of killing. `0` (the default) = no ceiling — a wedged subagent still dies on its own idle watchdog, exactly like the lead. |
1960
2005
  | `team.dispatchTimeoutMs` | `0` | The whole dispatch's wall clock (and the `task` tool timeout), clamped to `[0, 3600000]`. `0` (the default) = no ceiling; only Esc ends a dispatch early. |
1961
2006
  | `team.maxTurnsPerSubagent` | `0` | Runaway-loop cap, clamped to `[0, 100]`. `0` (the default) = no cap, matching the lead. A subagent stopped at a positive cap is reported as `stopped: turn cap reached`, with whatever it had produced. |
2007
+ | `team.overseer` | `true` | The dispatch supervisor: time ceilings on subagents become inspection triggers for a fast-tier supervisor that decides wait / nudge / replace / abandon instead of aborting. Needs the fast tier (`fast.enabled`); with the tier off or this `false`, children run under the legacy hard ceilings exactly. `/team overseer on\|off` flips it live, and it is read per dispatch. |
1962
2008
  | `todo.enabled` | `true` | Master switch for todo planning. `false` means the `todo_write` tool is never registered — `/todo on` in such a session saves the setting for next launch and says so, rather than advertising a tool that is not there. |
1963
2009
  | `todo.panel` | `true` | Render the right-hand rail. Independent of `todo.enabled` on purpose: a screen-reader user wants the planning discipline without the column, and the system prompt varies one sentence accordingly. |
1964
2010
  | `todo.followThrough` | `"notify"` | What happens when a run ends with steps left. `notify` says so and stops; `auto` continues the plan after a 3-second grace window, bounded by two structural limits (one fruitless attempt, 25 continuations per plan) that are deliberately **not** settings; `off` is silent. An unrecognized value falls back to `notify`. |
@@ -14,6 +14,7 @@ import { createToolOutputStore, } from '../tools/tool-output-store.js';
14
14
  import { TeamRuntime } from '../team/runtime.js';
15
15
  import { createChildReviewer } from '../team/child-reviewer.js';
16
16
  import { TeamHumanQueue } from '../team/human-queue.js';
17
+ import { createFastOverseerProvider } from '../team/overseer.js';
17
18
  import { createTaskTool } from '../team/task-tool.js';
18
19
  import { buildTeamBlock } from '../team/prompt.js';
19
20
  import { buildFastBlock } from '../fast/prompt.js';
@@ -354,6 +355,18 @@ export class AgentController {
354
355
  })
355
356
  : undefined,
356
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
+ }),
357
370
  providerRegistry: this.providerRegistry,
358
371
  getCwd: () => this.cwd,
359
372
  getMode: () => this.effectiveMode,
@@ -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` +
@@ -254,7 +254,7 @@ const COMMANDS = [
254
254
  },
255
255
  {
256
256
  name: 'team',
257
- description: 'Team subagents: status | on | off | max <n>',
257
+ description: 'Team subagents: status | on | off | max <n> | overseer on|off',
258
258
  /**
259
259
  * `/team on` CANNOT turn team mode on in a session that started without it,
260
260
  * and this command must not pretend otherwise (§4.5 / D-17 / P0-2).
@@ -294,7 +294,7 @@ const COMMANDS = [
294
294
  ? `\nRunning: ${snapshot.runs.map((r) => `${r.label} (${r.phase})`).join(', ')}`
295
295
  : '';
296
296
  ctx.notify('info', `Team mode: ${state}. Max subagents: ${cfg.maxSubagents}, ` +
297
- `concurrent: ${cfg.maxConcurrent}.${roster}`);
297
+ `concurrent: ${cfg.maxConcurrent}, supervisor: ${cfg.overseer ? 'on' : 'off'} (needs the fast tier).${roster}`);
298
298
  return;
299
299
  }
300
300
  if (verb === 'on' || verb === 'off') {
@@ -331,7 +331,22 @@ const COMMANDS = [
331
331
  ctx.toast('success', `Max subagents set to ${applied.maxSubagents}.`);
332
332
  return;
333
333
  }
334
- 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].`);
335
350
  },
336
351
  },
337
352
  {
@@ -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
+ }
@@ -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).
@@ -43,6 +43,7 @@ export const TEAM_CONFIG_SET_KEYS = [
43
43
  'team.subagentTimeoutMs',
44
44
  'team.dispatchTimeoutMs',
45
45
  'team.maxTurnsPerSubagent',
46
+ 'team.overseer',
46
47
  ];
47
48
  /**
48
49
  * Config keys under `retry.` that `aragon config set` accepts
@@ -199,6 +200,8 @@ export function applyTeamConfigSet(key, value) {
199
200
  return patch(clampOne('dispatchTimeoutMs'));
200
201
  case 'team.maxTurnsPerSubagent':
201
202
  return patch(clampOne('maxTurnsPerSubagent'));
203
+ case 'team.overseer':
204
+ return patch({ overseer: isTrue(value) });
202
205
  default:
203
206
  return null;
204
207
  }
@@ -308,6 +308,11 @@ export interface TeamConfig {
308
308
  * ceiling: a subagent runs until it finishes or is aborted, exactly like
309
309
  * the lead, whose only in-run ceiling is the event-silence idle watchdog
310
310
  * a child already keeps. A wedged child still dies on its OWN watchdog.
311
+ *
312
+ * WITH `team.overseer` ON (the default) this is the FIRST wall-clock
313
+ * INSPECTION point, not a hard abort: the supervisor looks and decides
314
+ * (wait / nudge / replace / abandon), so a slow-but-healthy child is no
315
+ * longer killed at an arbitrary wall clock.
311
316
  */
312
317
  subagentTimeoutMs: number;
313
318
  /**
@@ -323,6 +328,18 @@ export interface TeamConfig {
323
328
  * the deliberate trade of "subagents as capable as the main agent".
324
329
  */
325
330
  maxTurnsPerSubagent: number;
331
+ /**
332
+ * The dispatch supervisor (team-overseer): time ceilings on children
333
+ * become inspection triggers for a fast-tier supervisor that decides
334
+ * wait / nudge / replace / abandon, instead of aborts. Default TRUE.
335
+ *
336
+ * READ PER DISPATCH. With the supervisor off (or no fast tier), children
337
+ * run exactly the pre-feature regime: `subagentTimeoutMs` is a hard abort
338
+ * and silence is the child's own watchdog. With it ON,
339
+ * `subagentTimeoutMs > 0` is the FIRST wall-clock inspection point, not
340
+ * a death sentence.
341
+ */
342
+ overseer: boolean;
326
343
  }
327
344
  /**
328
345
  * The requirement's ceiling on the fan-out (R-g).
@@ -596,10 +613,13 @@ export interface CompactionConfig {
596
613
  * (context-auto-compaction-hardening §3.4 / W3). Default TRUE.
597
614
  *
598
615
  * IT REVERSES D-15, WHOSE PREMISE IS MEASURABLY FALSE at the configured bounds:
599
- * `team.maxTurnsPerSubagent` is 24 and `team.dispatchTimeoutMs` is 900 000, and
600
- * 24 turns over fifteen minutes is the same profile as the lead run this
601
- * feature exists for. When a child overflows today its history is discarded and
602
- * the lead receives one partial sentence for the whole dispatch.
616
+ * D-15 measured `team.maxTurnsPerSubagent` 24 and `team.dispatchTimeoutMs`
617
+ * 900 000, where 24 turns over fifteen minutes is already the same profile
618
+ * as the lead run this feature exists for — and the parity defaults of
619
+ * 2ff15a9c3 later removed both ceilings (0 = no turn cap, no wall clock),
620
+ * which makes the premise falser still, not truer. When a child overflows
621
+ * today its history is discarded and the lead receives one partial sentence
622
+ * for the whole dispatch.
603
623
  *
604
624
  * `false` REPRODUCES ROUND 1 EXACTLY: `subagent.ts` spreads no `contextManager`
605
625
  * key at all, so the child's loop gate tests an absent field.
@@ -452,6 +452,9 @@ export const DEFAULT_TEAM_CONFIG = {
452
452
  subagentTimeoutMs: 0,
453
453
  dispatchTimeoutMs: 0,
454
454
  maxTurnsPerSubagent: 0,
455
+ // The dispatch supervisor, default ON (team-overseer): ceilings become
456
+ // inspections; the fast-tier supervisor decides what to do.
457
+ overseer: true,
455
458
  };
456
459
  const SUBAGENTS_RANGE = { min: 1, max: HARD_MAX_SUBAGENTS };
457
460
  const CONCURRENT_RANGE = { min: 1, max: HARD_MAX_SUBAGENTS };
@@ -482,6 +485,7 @@ export function clampTeamConfig(raw) {
482
485
  subagentTimeoutMs: clampIntAllowingZero(src.subagentTimeoutMs, DEFAULT_TEAM_CONFIG.subagentTimeoutMs, SUBAGENT_TIMEOUT_RANGE),
483
486
  dispatchTimeoutMs: clampIntAllowingZero(src.dispatchTimeoutMs, DEFAULT_TEAM_CONFIG.dispatchTimeoutMs, DISPATCH_TIMEOUT_RANGE),
484
487
  maxTurnsPerSubagent: clampIntAllowingZero(src.maxTurnsPerSubagent, DEFAULT_TEAM_CONFIG.maxTurnsPerSubagent, TURNS_RANGE),
488
+ overseer: bool(src.overseer, DEFAULT_TEAM_CONFIG.overseer),
485
489
  };
486
490
  }
487
491
  export const DEFAULT_TODO_CONFIG = {
@@ -193,7 +193,10 @@ export interface ExecTodoEvent {
193
193
  export interface ExecTeamEvent {
194
194
  type: 'team';
195
195
  sessionId: string;
196
- subtype: 'dispatch_start' | 'agent_update' | 'dispatch_end';
196
+ subtype: 'dispatch_start' | 'agent_update' | 'dispatch_end' | 'overseer';
197
+ action?: string;
198
+ trigger?: string;
199
+ reason?: string;
197
200
  label?: string;
198
201
  phase?: string;
199
202
  description?: string;
@@ -707,6 +707,19 @@ export class ExecRunner {
707
707
  total: runs.length,
708
708
  aborted: event.outcome.aborted,
709
709
  });
710
+ return;
711
+ }
712
+ if (event.type === 'overseer') {
713
+ this.emit({
714
+ type: 'team',
715
+ sessionId: this.sessionId,
716
+ subtype: 'overseer',
717
+ label: event.label,
718
+ action: event.decision.action,
719
+ trigger: event.trigger,
720
+ reason: event.decision.reason,
721
+ });
722
+ return;
710
723
  }
711
724
  }
712
725
  }
@@ -24,7 +24,7 @@
24
24
  * legacy bytes, with the modified-Enter family becoming `ENTER_NEWLINE_FRAME`.
25
25
  * Pure: no node/React imports.
26
26
  */
27
- import { ENTER_NEWLINE_FRAME } from './limits.js';
27
+ import { ENTER_NEWLINE_FRAME, INDEX_KEY_FRAME } from './limits.js';
28
28
  /** Enable / disable the two CSI-u report modes (posix side). */
29
29
  export const CSI_U_ENABLE = '\x1b[>1u\x1b[>4;2m';
30
30
  export const CSI_U_DISABLE = '\x1b[<u\x1b[>4m';
@@ -111,8 +111,19 @@ export function translateCsiUKey(key) {
111
111
  return m === 0 ? '\r' : ENTER_NEWLINE_FRAME;
112
112
  if (key.code === 27)
113
113
  return alt ? '\x1b\x1b' : '\x1b';
114
- if (key.code === 9)
114
+ if (key.code === 9) {
115
+ // A modified Tab codepoint (modifyOtherKeys shape) mirrors the VK_TAB
116
+ // decision in win32-input-mode.ts: ctrl (without shift/alt) is the
117
+ // project-indexer trigger frame, shift stays Shift+Tab.
118
+ if (ctrl && !shift && !alt)
119
+ return INDEX_KEY_FRAME;
115
120
  return shift ? '\x1b[Z' : '\t';
121
+ }
122
+ // Ctrl+I under kitty disambiguate arrives as codepoint 105 with the ctrl
123
+ // modifier; the generic printable branch below would fold it back onto the
124
+ // legacy Tab byte, which is exactly the ambiguity the frame exists to break.
125
+ if (key.code === 105 && ctrl && !shift && !alt)
126
+ return INDEX_KEY_FRAME;
116
127
  if (key.code === 127) {
117
128
  // POST-normalisation bytes, same rationale as VK_BACK in win32-input-mode.
118
129
  if (alt)