@aragon-agent/cli 0.6.17 → 0.6.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +121 -51
- package/README.md +48 -2
- package/dist/agent/controller.js +13 -0
- package/dist/agent/headless.js +7 -0
- package/dist/commands/builtins.js +18 -3
- package/dist/commands/index-build.d.ts +39 -0
- package/dist/commands/index-build.js +61 -0
- package/dist/config/cli-commands.d.ts +1 -1
- package/dist/config/cli-commands.js +3 -0
- package/dist/config/schema.d.ts +24 -4
- package/dist/config/schema.js +4 -0
- package/dist/exec/events.d.ts +4 -1
- package/dist/exec/runner.js +13 -0
- package/dist/input/csiu-keys.js +13 -2
- package/dist/input/limits.d.ts +15 -0
- package/dist/input/limits.js +15 -0
- package/dist/input/win32-input-mode.js +19 -1
- package/dist/logging/install.d.ts +9 -0
- package/dist/logging/install.js +11 -0
- package/dist/team/human-queue.d.ts +11 -0
- package/dist/team/human-queue.js +18 -1
- package/dist/team/limits.d.ts +40 -0
- package/dist/team/limits.js +41 -0
- package/dist/team/overseer.d.ts +157 -0
- package/dist/team/overseer.js +348 -0
- package/dist/team/report.js +26 -1
- package/dist/team/runtime.d.ts +71 -6
- package/dist/team/runtime.js +380 -23
- package/dist/team/subagent.d.ts +30 -1
- package/dist/team/subagent.js +8 -1
- package/dist/team/task-tool.js +4 -1
- package/dist/team/types.d.ts +63 -0
- package/dist/ui/App.d.ts +1 -1
- package/dist/ui/App.js +61 -0
- package/dist/ui/Header.js +13 -2
- package/dist/ui/PromptInput.js +11 -1
- package/dist/ui/overlays/HelpOverlay.js +1 -0
- package/package.json +2 -2
- package/skills/project-indexer/SKILL.md +163 -0
- package/skills/project-indexer/templates/cleancode-overrides.md +46 -0
- package/skills/project-indexer/templates/cleancode-template.md +94 -0
- package/skills/project-indexer/templates/config-template.md +69 -0
- package/skills/project-indexer/templates/index-template.md +129 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,57 +5,103 @@ 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
|
-
###
|
|
11
|
-
|
|
12
|
-
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
###
|
|
25
|
-
|
|
26
|
-
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
-
|
|
8
|
+
## Unreleased
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- **`Ctrl+I` was dead on Windows.** The win32-input-mode translator only
|
|
13
|
+
recognised the chord when the console reported it as `VK_TAB` (vk 9) with
|
|
14
|
+
ctrl - a record shape no real console was ever probed for. Physical
|
|
15
|
+
Ctrl+I keeps the LETTER vk (0x49, the same Ctrl+letter rule that reports
|
|
16
|
+
Ctrl+A as vk 65 and Ctrl+J as vk 74 in the Shift+Enter probe captures)
|
|
17
|
+
while `char` carries the Tab byte, so the record fell through to the
|
|
18
|
+
generic Ctrl-combo branch, came out as plain Tab, and the keybinding
|
|
19
|
+
silently did completion instead of opening the index confirm. The
|
|
20
|
+
translator now tells Ctrl+I apart from Tab by `char === 0x09 && ctrl`
|
|
21
|
+
whatever vk it rides, with Shift still mapping to Shift+Tab; both record
|
|
22
|
+
shapes are pinned by regression rows in `win32-input-mode.test.ts`.
|
|
23
|
+
|
|
24
|
+
### Team dispatch supervisor (team-overseer)
|
|
25
|
+
|
|
26
|
+
- **A time ceiling on a subagent is now an inspection trigger, not a death
|
|
27
|
+
sentence.** With the supervisor on (new key `team.overseer`, default
|
|
28
|
+
`true`, needs the fast tier), two soft timers per child - event silence at
|
|
29
|
+
`idleTimeoutMs` and a wall clock at `subagentTimeoutMs` (or 5 minutes when
|
|
30
|
+
no ceiling is configured) - fire one bounded fast-tier inspection. The
|
|
31
|
+
supervisor decides wait / nudge / replace / abandon; the runtime applies,
|
|
32
|
+
funnelling every lifecycle change through the worker loop that awaits the
|
|
33
|
+
child, so a replacement is never built for nobody.
|
|
34
|
+
- **A nudge is steering; a replace is a rebuild with an amended brief.**
|
|
35
|
+
Guidance is queued at core's safe top-of-loop checkpoint; a replacement
|
|
36
|
+
carries the supervisor's reason, the dead child's `filesTouched`, and the
|
|
37
|
+
original brief bounded to the same prompt budget. A post-mortem inspection
|
|
38
|
+
diagnoses a child that died silently (watchdog death, stream loss) and may
|
|
39
|
+
re-assign it once.
|
|
40
|
+
- **Budgets are structural, not settings**: 4 inspections and 1 replacement
|
|
41
|
+
per child per dispatch; past them the child runs under its own (lengthened
|
|
42
|
+
while supervised) idle watchdog and the supervisor goes quiet. An
|
|
43
|
+
unparseable or failing supervision call always becomes `wait` -
|
|
44
|
+
supervision must never kill a healthy child.
|
|
45
|
+
- Every applied decision is visible three ways: the report's Supervisor
|
|
46
|
+
section (`[silence 30s] a1: nudge - ...`), the team panel and the `-p`
|
|
47
|
+
stderr line, and a `debug` log event. `aragon exec` emits it as an
|
|
48
|
+
additive `team` subtype `overseer` (`EXEC_SCHEMA_VERSION` unchanged).
|
|
49
|
+
- New command `/team overseer on|off` (refused mid-dispatch, like every
|
|
50
|
+
other `/team` setting) and new config key `team.overseer`, also settable
|
|
51
|
+
as `aragon config set team.overseer false`. With the supervisor off - or
|
|
52
|
+
no fast tier - every path is byte-for-byte the pre-feature regime:
|
|
53
|
+
`subagentTimeoutMs` is a hard abort and silence is the child's own
|
|
54
|
+
watchdog.
|
|
55
|
+
|
|
56
|
+
### 启动与缩窗不再误报 Frame diff lost sync
|
|
57
|
+
|
|
58
|
+
- 启动即现的 "Frame diffing lost sync with the terminal and fell back to a
|
|
59
|
+
full repaint … `--no-diff-render`" 警告已消除:Ink 挂载/卸载经渲染 stdout 写出的
|
|
60
|
+
光标隐藏/恢复序列(`ESC[?25l` / `ESC[?25h`)由帧写入器直接透传——不进差分、
|
|
61
|
+
不失效缓存、不计 fallback,也不再消耗会话的首写豁免。启动序列恢复
|
|
62
|
+
"种子帧透传 → 首帧全量 → 之后纯 diff"。
|
|
63
|
+
- 终端高度缩小(拖小窗口、改字号、面板重排)不再触发同一条警告:Ink 的
|
|
64
|
+
`clearTerminal` 整帧写入被差分器识别为 Ink 自身写法,透传并失效缓存但不计
|
|
65
|
+
fallback;该控制写同样不消耗首写豁免,其后的首个 log-update 帧仍按种子帧豁免,
|
|
66
|
+
下一帧自然全量重绘。
|
|
67
|
+
- `/perf` 的 `fallbacks` 重新从 0 起步,"健康会话 fallbacks === 0" 的诊断信号
|
|
68
|
+
(AC-11)恢复可判。
|
|
69
|
+
|
|
70
|
+
### Consistent TUI styling
|
|
71
|
+
|
|
72
|
+
- Interactive sessions always use Unicode and default to truecolor, including
|
|
73
|
+
PowerShell sessions without terminal or UTF locale markers. Ink's actual color
|
|
74
|
+
output follows the same policy, with renderer state restored on exit.
|
|
75
|
+
- Explicit color opt-outs keep Unicode borders and icons; the opening logo now
|
|
76
|
+
depends only on available space. Headless terminal detection is unchanged.
|
|
77
|
+
- Reloading model settings preserves session display preferences. Syntax
|
|
78
|
+
highlighting follows the same color policy, and status icons use consistent
|
|
79
|
+
single-column glyphs to prevent narrow-frame overflow.
|
|
80
|
+
- Hiding the editor caret while scrolling preserves draft geometry, preventing
|
|
81
|
+
a layout update loop when entering text above the full startup logo.
|
|
82
|
+
- Register exit cleanup before mounting the application, so immediate exits and
|
|
83
|
+
initial render failures restore terminal and color state reliably. Failed
|
|
84
|
+
mounts no longer mark an auto-installed build as healthy.
|
|
85
|
+
|
|
86
|
+
### Model context window
|
|
87
|
+
|
|
88
|
+
- Discover context limits at startup and after connection changes without blocking
|
|
89
|
+
input. The status bar and compaction use the discovered limit; `/context` names
|
|
90
|
+
API versus catalog metadata. Unknown windows show `?`, not a fabricated 128k
|
|
91
|
+
capacity or percentage. Manual `contextWindow` overrides retain precedence.
|
|
92
|
+
- Window overrides now apply to tail relief and compaction progress as well as
|
|
93
|
+
the gauge. Workers on different models/endpoints do not inherit the lead's
|
|
94
|
+
override. API-key changes refresh metadata, and ending a session cancels its
|
|
95
|
+
pending model queries. `/context` also hides unknown capacities/percentages.
|
|
96
|
+
|
|
97
|
+
### TUI 输入与排队可靠性
|
|
98
|
+
|
|
99
|
+
- Shift+Enter 与粘贴按输入顺序处理;普通 Enter 保持弹层兼容,混合输入最多提交一次,
|
|
100
|
+
余稿及拒绝提交的正文保留。结束输入流时完整排空,不泄漏内部控制标记。
|
|
101
|
+
- 超限粘贴拒绝同一外部输入块的正文和 Enter,避免误提交旧稿;未闭合粘贴继续按正文消费。
|
|
102
|
+
- 拖选后仅 Ctrl+C 复制;文字变化与丢失鼠标释放时取消无效高亮,避免卡住视口。
|
|
103
|
+
- 固定 Queue 状态行按消息 ID 回执更新,支持窄屏、暂停与多条消息计数。
|
|
104
|
+
- 保存和退出保留所有未发送全文;恢复先验证存档,旧 queued 作为警告展示且不自动重放。
|
|
59
105
|
|
|
60
106
|
### Removed
|
|
61
107
|
|
|
@@ -451,6 +497,30 @@ were written.
|
|
|
451
497
|
|
|
452
498
|
## Unreleased
|
|
453
499
|
|
|
500
|
+
### Project index on Ctrl+I (bundled `project-indexer` skill)
|
|
501
|
+
|
|
502
|
+
- The `project-indexer` skill now ships **bundled** with the package
|
|
503
|
+
(`<pkg>/skills/project-indexer`, read-only, alongside `skill-creator`): it
|
|
504
|
+
generates the `.claude-index/` navigation map and injects the Google-style
|
|
505
|
+
Clean Code Guidelines section into `CLAUDE.md`, adapted from the standalone
|
|
506
|
+
Claude Code skill (ask_user instead of AskUserQuestion; body condensed under
|
|
507
|
+
the 30 KB invocation budget with the four templates bundled as files).
|
|
508
|
+
- New keybinding `Ctrl+I`: opens a Yes/No confirm that names every write
|
|
509
|
+
target, then dispatches `/skill:project-indexer` through the ordinary
|
|
510
|
+
command path - the indexing run is a normal agent turn whose tool calls
|
|
511
|
+
stream into the transcript. While a run is in flight the key warns instead
|
|
512
|
+
of queueing; with skills disabled or the skill shadowed it explains instead
|
|
513
|
+
of failing silently.
|
|
514
|
+
- `Ctrl+I` is legacy-encoded as the Tab byte, so the stdin filter's two
|
|
515
|
+
translators (win32-input-mode on Windows, kitty CSI-u / modifyOtherKeys
|
|
516
|
+
elsewhere) now emit a distinct NUL-delimited `INDEX_KEY_FRAME` for it -
|
|
517
|
+
the same framing technique `Shift+Enter` uses. On terminals with neither
|
|
518
|
+
protocol the key keeps its legacy Tab meaning (completion).
|
|
519
|
+
- The header names the key next to the detail toggle: `^I Index` collapsed,
|
|
520
|
+
`^I build index` in the `Ctrl+G` expanded state; hidden below 72 columns and
|
|
521
|
+
while an overlay is open. `/skill:project-indexer` and `/project-indexer`
|
|
522
|
+
remain available as typed commands.
|
|
523
|
+
|
|
454
524
|
### Added
|
|
455
525
|
|
|
456
526
|
- **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
|
|
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
|
|
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`. |
|
package/dist/agent/controller.js
CHANGED
|
@@ -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,
|
package/dist/agent/headless.js
CHANGED
|
@@ -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
|
-
|
|
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
|
}
|
package/dist/config/schema.d.ts
CHANGED
|
@@ -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`
|
|
600
|
-
* 24 turns over fifteen minutes is the same profile
|
|
601
|
-
*
|
|
602
|
-
*
|
|
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.
|
package/dist/config/schema.js
CHANGED
|
@@ -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 = {
|
package/dist/exec/events.d.ts
CHANGED
|
@@ -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;
|
package/dist/exec/runner.js
CHANGED
|
@@ -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
|
}
|
package/dist/input/csiu-keys.js
CHANGED
|
@@ -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)
|