@aragon-agent/cli 0.5.12 → 0.6.2
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 +100 -0
- package/README.md +322 -6
- package/dist/agent/agent-mode.d.ts +39 -0
- package/dist/agent/agent-mode.js +36 -0
- package/dist/agent/controller.d.ts +32 -0
- package/dist/agent/controller.js +25 -0
- package/dist/agent/system-prompt.d.ts +16 -0
- package/dist/agent/system-prompt.js +5 -0
- package/dist/cli.js +143 -2
- package/dist/config/ui-state.d.ts +29 -0
- package/dist/config/ui-state.js +14 -4
- package/dist/diagnostics/cli-commands.d.ts +10 -0
- package/dist/diagnostics/cli-commands.js +61 -0
- package/dist/diagnostics/doctor.d.ts +29 -0
- package/dist/diagnostics/doctor.js +207 -0
- package/dist/diagnostics/info.d.ts +30 -0
- package/dist/diagnostics/info.js +99 -0
- package/dist/exec/cli-commands.d.ts +31 -0
- package/dist/exec/cli-commands.js +63 -0
- package/dist/exec/emitter.d.ts +80 -0
- package/dist/exec/emitter.js +127 -0
- package/dist/exec/events.d.ts +226 -0
- package/dist/exec/events.js +43 -0
- package/dist/exec/index.d.ts +56 -0
- package/dist/exec/index.js +439 -0
- package/dist/exec/options.d.ts +76 -0
- package/dist/exec/options.js +221 -0
- package/dist/exec/permission.d.ts +66 -0
- package/dist/exec/permission.js +96 -0
- package/dist/exec/runner.d.ts +204 -0
- package/dist/exec/runner.js +559 -0
- package/dist/exec/stdin-stream.d.ts +46 -0
- package/dist/exec/stdin-stream.js +144 -0
- package/dist/logging/install.d.ts +22 -0
- package/dist/logging/install.js +22 -0
- package/dist/session/cli-commands.d.ts +30 -0
- package/dist/session/cli-commands.js +184 -0
- package/dist/session/persist.d.ts +46 -0
- package/dist/session/persist.js +8 -0
- package/dist/session/store.d.ts +147 -0
- package/dist/session/store.js +442 -0
- package/dist/team/runtime.d.ts +12 -0
- package/dist/team/runtime.js +6 -0
- package/dist/team/subagent.d.ts +12 -0
- package/dist/team/subagent.js +4 -0
- package/dist/tools/index.d.ts +20 -0
- package/dist/tools/index.js +26 -0
- package/dist/ui/ActivityLine.d.ts +16 -0
- package/dist/ui/ActivityLine.js +23 -1
- package/dist/ui/App.d.ts +14 -1
- package/dist/ui/App.js +61 -7
- package/dist/ui/BottomStatusRow.d.ts +33 -1
- package/dist/ui/BottomStatusRow.js +20 -6
- package/dist/ui/Composer.d.ts +14 -1
- package/dist/ui/Composer.js +5 -5
- package/dist/ui/overlays/HelpOverlay.js +12 -1
- package/dist/ui/use-startup-notices.d.ts +98 -4
- package/dist/ui/use-startup-notices.js +108 -2
- package/dist/ui/win-vt-force.d.ts +136 -0
- package/dist/ui/win-vt-force.js +237 -0
- package/dist/ui/win-vt-input.d.ts +57 -0
- package/dist/ui/win-vt-input.js +62 -0
- package/package.json +7 -3
package/CHANGELOG.md
CHANGED
|
@@ -5,10 +5,110 @@ 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
|
+
## 0.6.0
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **`aragon exec` — a second, machine-facing face on the same binary.** The
|
|
13
|
+
package already installed from npm and already ran from a terminal, so "it has
|
|
14
|
+
a CLI" was true. It was nonetheless not *callable* by another program:
|
|
15
|
+
everything it exposed was shaped for a person, and a build script, a CI job, a
|
|
16
|
+
web backend or a second agent had to spawn it, capture free text, and guess.
|
|
17
|
+
There was no way to read the answer without also reading the narration, no way
|
|
18
|
+
to carry a conversation across two invocations, no way to say "you may read but
|
|
19
|
+
not write" when there is no human to answer `--confirm`, and no way to bound
|
|
20
|
+
what a run may spend.
|
|
21
|
+
|
|
22
|
+
`aragon exec` adds a **contract** rather than a feature: a versioned JSON event
|
|
23
|
+
schema on stdout, a session identifier the caller may mint itself, a tool
|
|
24
|
+
permission policy enforced at the tool boundary, caller-set budgets with their
|
|
25
|
+
own exit code, and a discovery command so a wrapper can ask what it just
|
|
26
|
+
installed before depending on it. Everything underneath is reused unchanged —
|
|
27
|
+
the same controller, the same tools, the same config resolution, the same
|
|
28
|
+
skills, teams, todo and fast-tier subsystems.
|
|
29
|
+
|
|
30
|
+
- `--output-format text | json | stream-json`. `stream-json` is NDJSON: one
|
|
31
|
+
object per line, `system/init` first, exactly one `result` last, on every
|
|
32
|
+
exit path including error, budget and signal. `text` is byte-identical to
|
|
33
|
+
`aragon -p`, because it **delegates** to the same renderer rather than
|
|
34
|
+
reimplementing it.
|
|
35
|
+
- `--session-id` / `--resume` / `--continue`, backed by `~/.aragon-agent/
|
|
36
|
+
sessions/` and an exclusive lock, so two runs cannot interleave and lose a
|
|
37
|
+
turn. A second run on a held id exits 2 with `session_busy` rather than
|
|
38
|
+
blocking.
|
|
39
|
+
- `--permission-mode auto|plan|strict` plus `--allow-tool` / `--deny-tool`.
|
|
40
|
+
A denied tool is **not registered at all**, so the model never sees it and
|
|
41
|
+
never plans around it — and the policy reaches subagents, so one `task` call
|
|
42
|
+
cannot bypass it.
|
|
43
|
+
- `--max-turns` / `--max-duration`, both aborting through the same path `Esc`
|
|
44
|
+
uses, both reporting `isError: false` and exit **3**: a budget firing is not
|
|
45
|
+
a malfunction, but it is not a complete answer either, and a shell script has
|
|
46
|
+
to be able to tell without parsing JSON.
|
|
47
|
+
- `--append-system-prompt` / `--append-system-prompt-file`. Appending only;
|
|
48
|
+
there is deliberately no full `--system-prompt` replacement, because the
|
|
49
|
+
builtin prompt carries tool discipline, todo planning, the skills catalog and
|
|
50
|
+
the plan-mode block, and replacing it silently disables half the product.
|
|
51
|
+
- `--input-format stream-json`, so one process can serve several turns from a
|
|
52
|
+
pipe. A malformed line is skipped with a non-fatal `error` event; an unknown
|
|
53
|
+
message type is ignored.
|
|
54
|
+
- **`aragon sessions`** (`list` / `show` / `rm` / `prune` / `path`),
|
|
55
|
+
**`aragon info [--json]`** and **`aragon doctor [--json] [--probe]`**.
|
|
56
|
+
|
|
57
|
+
Documented in full under
|
|
58
|
+
[Use it from another project](./README.md#use-it-from-another-project),
|
|
59
|
+
including an honest Limits section: there is no filesystem sandbox, process
|
|
60
|
+
exit is terminal on Windows whether or not a `result` arrived, an exec session
|
|
61
|
+
resumed in the TUI shows no scrollback, and `sessions prune` shares a directory
|
|
62
|
+
with the TUI's `/save`.
|
|
63
|
+
|
|
64
|
+
### Changed
|
|
65
|
+
|
|
66
|
+
- **Exit code `3` is new, and the signal row is now stated as `128 + signo`.**
|
|
67
|
+
`3` means "stopped by a budget you set" and is reachable only from
|
|
68
|
+
`aragon exec`. The signal codes are unchanged in behaviour — this CLI has
|
|
69
|
+
always exited `128 + signo`, so `SIGTERM` has always been `143` — but the
|
|
70
|
+
README previously simplified all of them to `130`, which is fine for prose
|
|
71
|
+
about a CLI a human interrupts and not good enough for a wire contract.
|
|
72
|
+
- `interrupt` handling now settles the run before exiting when `aragon exec`
|
|
73
|
+
owns it, so a wrapper reading NDJSON gets a final `result` on Ctrl-C instead of
|
|
74
|
+
having to time out to learn the run ended. `aragon`, `aragon -p` and every
|
|
75
|
+
other subcommand keep the previous bare-exit behaviour byte for byte.
|
|
76
|
+
|
|
77
|
+
### Unchanged, and asserted rather than claimed
|
|
78
|
+
|
|
79
|
+
- `aragon`, `aragon -p`, every existing flag, every slash command and every byte
|
|
80
|
+
of their output. `aragon exec --output-format text` and `runHeadless` are
|
|
81
|
+
compared for byte equality on **both** stdout and stderr by a regression test,
|
|
82
|
+
and `createBuiltinTools` with no permission policy is proven by object identity
|
|
83
|
+
to return the tools it returned before.
|
|
84
|
+
|
|
8
85
|
## Unreleased
|
|
9
86
|
|
|
10
87
|
### Fixed
|
|
11
88
|
|
|
89
|
+
- **The working spinner no longer disappears when an ack takes its row.** Steer a
|
|
90
|
+
run, approve a plan, or press `Ctrl+T` while the agent is working, and the row
|
|
91
|
+
above the composer traded its spinner for the acknowledgement — and for that
|
|
92
|
+
ack's full two and a half seconds, nothing anywhere on screen moved. The status
|
|
93
|
+
bar still read `running` and the tool card still read `running`, so no
|
|
94
|
+
information had left the frame; only the evidence that the process was alive
|
|
95
|
+
had, during exactly the long tool calls where "is it stuck?" is the question you
|
|
96
|
+
are already asking. Consecutive acks extended the window, and steering is
|
|
97
|
+
something the composer invites on every run.
|
|
98
|
+
|
|
99
|
+
That row was made the sole owner of animation in this same release, and
|
|
100
|
+
everything else on screen holds still for as long as the row is believed to be
|
|
101
|
+
showing one. Believed, not checked: an ack won the row outright, so the belief
|
|
102
|
+
and the frame disagreed, and the disagreement was invisible to every test
|
|
103
|
+
because no component was wrong on its own.
|
|
104
|
+
|
|
105
|
+
The ack still owns the row and keeps every word of its text. The spinner now
|
|
106
|
+
rides beside it as a bare glyph — no phrase, no clock — so there is exactly one
|
|
107
|
+
animation at every instant of a run, with an ack up or without one: never none,
|
|
108
|
+
and never the several this release set out to remove. Terminals without Unicode
|
|
109
|
+
and anyone running with `reducedMotion` come out byte for byte unchanged; they
|
|
110
|
+
had no animation to lose, and nothing is prefixed to their acks.
|
|
111
|
+
|
|
12
112
|
- **`/clear` now takes the todo panel with it.** Finish a plan, type `/clear`,
|
|
13
113
|
and the transcript emptied while the `7/7 done` rail stayed exactly where it
|
|
14
114
|
was — on a screen you had just asked to be cleared. It stayed until your next
|
package/README.md
CHANGED
|
@@ -10,6 +10,36 @@ steer / configure the session — all without leaving the terminal.
|
|
|
10
10
|
> and there is **no per-action approval gate by default**. Only run it in
|
|
11
11
|
> workspaces you trust. An opt-in confirmation mode is available via `--confirm`.
|
|
12
12
|
|
|
13
|
+
## Requirements
|
|
14
|
+
|
|
15
|
+
Node **≥ 18**, any platform.
|
|
16
|
+
|
|
17
|
+
**On Windows with Node < 22.17.0 (or 24.0/24.1), aragon turns the missing
|
|
18
|
+
console flag on itself at startup**, which restores `Shift+Tab` on most
|
|
19
|
+
machines. Where that is blocked — a locked-down PowerShell, an old console
|
|
20
|
+
host — **`Ctrl+P` toggles the mode instead**; it is bound on every platform and
|
|
21
|
+
does not depend on the console mode, the Node version, or anything else.
|
|
22
|
+
Everything else — the agent loop, every tool, skills, teams, `aragon -p` — works
|
|
23
|
+
exactly the same on Node 18 and 20, which is why `engines` stays at `>=18`
|
|
24
|
+
rather than locking those users out.
|
|
25
|
+
|
|
26
|
+
The reason is not the terminal emulator and not a setting: on Windows both
|
|
27
|
+
`Shift+Tab` (`CSI Z`) and mouse reporting exist only while the console input
|
|
28
|
+
handle has `ENABLE_VIRTUAL_TERMINAL_INPUT` set, and Node only sets it from
|
|
29
|
+
v22.17.0 / v24.2.0 onward. On older versions libuv translates console input
|
|
30
|
+
itself, and that translation has no entry for `Shift+Tab` (it arrives as a
|
|
31
|
+
plain `Tab`) and discards mouse records outright. Node 20 is the current
|
|
32
|
+
Windows default LTS, so this is a common configuration rather than an exotic
|
|
33
|
+
one — it is why the same build behaves differently on two machines. Below that
|
|
34
|
+
version aragon sets `ENABLE_VIRTUAL_TERMINAL_INPUT` itself, from a short
|
|
35
|
+
PowerShell helper, right after it puts the console into raw mode — the flag is a
|
|
36
|
+
property of the console, not of Node, so any process attached to it can turn it
|
|
37
|
+
on. When that works, `Shift+Tab` arrives as `CSI Z` again and mouse reporting
|
|
38
|
+
goes back on with it — the wheel should follow by the same route, though only
|
|
39
|
+
the key has been measured. When it does not work, aragon says so at startup and
|
|
40
|
+
turns mouse reporting off rather than asking for reports that can never arrive;
|
|
41
|
+
`Ctrl+P` and `/plan` switch mode, and `Shift+↑` / `Shift+↓` scroll.
|
|
42
|
+
|
|
13
43
|
## Install
|
|
14
44
|
|
|
15
45
|
```bash
|
|
@@ -38,11 +68,268 @@ aragon "summarize README.md" # interactive, auto-submitting the prompt
|
|
|
38
68
|
echo "list files" | aragon -p # one-shot, prints to stdout then exits
|
|
39
69
|
```
|
|
40
70
|
|
|
71
|
+
## Use it from another project
|
|
72
|
+
|
|
73
|
+
`aragon exec` is the machine-facing face of the same binary: a versioned JSON
|
|
74
|
+
event stream on stdout, sessions that survive across process boundaries, tool
|
|
75
|
+
permissions enforced at the tool boundary, and budgets with their own exit code.
|
|
76
|
+
Install the package, spawn it, read the JSON — that is the whole integration
|
|
77
|
+
story. Nothing about the interactive TUI changes, and `aragon -p` keeps working
|
|
78
|
+
exactly as it always has.
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
npm i -g @aragon-agent/cli # or add it as a devDependency and use npx
|
|
82
|
+
|
|
83
|
+
aragon exec --output-format json "summarize src/index.ts" | jq -r .result
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Discover what you installed
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
aragon info --json # version, schema version, tools, formats, features
|
|
90
|
+
aragon doctor # "is this machine configured to run me?" — exit 0 = yes
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
`info.features` is a flat list of strings, on purpose: a wrapper that tests
|
|
94
|
+
`features.includes('sessions')` keeps working across versions that add
|
|
95
|
+
capabilities.
|
|
96
|
+
|
|
97
|
+
### Output formats
|
|
98
|
+
|
|
99
|
+
| Format | Stdout carries | Use it when |
|
|
100
|
+
| --- | --- | --- |
|
|
101
|
+
| `text` (default) | Exactly what `aragon -p` prints | A human reads the output, or you already parse `-p` |
|
|
102
|
+
| `json` | One `result` object, pretty-printed | You want the answer and the totals, nothing else |
|
|
103
|
+
| `stream-json` | One JSON object per line (NDJSON) | You want tool calls, todos and progress as they happen |
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
# text — byte-identical to `aragon -p "hi"`
|
|
107
|
+
aragon exec "hi"
|
|
108
|
+
|
|
109
|
+
# json — one object, ideal for `jq`
|
|
110
|
+
aragon exec --output-format json "hi"
|
|
111
|
+
|
|
112
|
+
# stream-json — NDJSON, one object per line
|
|
113
|
+
aragon exec --output-format stream-json "read package.json and name the deps"
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
A `stream-json` run looks like this (elided for width):
|
|
117
|
+
|
|
118
|
+
```jsonc
|
|
119
|
+
{"type":"system","subtype":"init","schemaVersion":1,"sessionId":"6f2…","cli":"0.6.0","cwd":"/w","model":{"provider":"anthropic","id":"claude-…","baseUrl":null},"permissionMode":"auto","tools":["read_file","write_file",…],"resumed":false}
|
|
120
|
+
{"type":"user","sessionId":"6f2…","turn":1,"text":"read package.json…","source":"caller"}
|
|
121
|
+
{"type":"tool_call","sessionId":"6f2…","turn":1,"id":"t1","name":"read_file","input":{"path":"package.json"}}
|
|
122
|
+
{"type":"tool_result","sessionId":"6f2…","turn":1,"id":"t1","name":"read_file","isError":false,"durationMs":4,"output":"{\n \"name\": …"}
|
|
123
|
+
{"type":"assistant","sessionId":"6f2…","turn":1,"text":"The dependencies are …"}
|
|
124
|
+
{"type":"result","schemaVersion":1,"sessionId":"6f2…","isError":false,"stopReason":"end_turn","exitCode":0,"result":"The dependencies are …","turns":1,"durationMs":4120,"usage":{"inputTokens":1204,"outputTokens":88,"totalTokens":1292},"cost":{"amount":0.0049,"currency":"USD","known":true},"model":{"provider":"anthropic","id":"claude-…"},"todos":null,"error":null}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Event types: `system` · `user` · `text_delta` · `thinking` · `assistant` ·
|
|
128
|
+
`tool_call` · `tool_result` · `todo` · `team` · `fast_review` · `retry` ·
|
|
129
|
+
`error` · `result`. `text_delta` and `thinking` are opt-in
|
|
130
|
+
(`--partial-messages`, `--include-thinking`) — a per-token line is thousands of
|
|
131
|
+
lines per turn and is only wanted by a caller rendering a live UI.
|
|
132
|
+
|
|
133
|
+
**Forward compatibility, stated as a contract.** `schemaVersion` appears on
|
|
134
|
+
`system` and on `result`. **Ignore event types you do not know, and ignore
|
|
135
|
+
fields you do not know**: adding either never bumps the version, and renaming or
|
|
136
|
+
removing one does. A consumer that holds up its end of that keeps working across
|
|
137
|
+
upgrades; one that does not will break on a release that adds an event.
|
|
138
|
+
|
|
139
|
+
One practical note: a `tool_result.output` line can approach 100 KB, so read
|
|
140
|
+
stdout with a line reader that has no small buffer cap.
|
|
141
|
+
|
|
142
|
+
### Tool permissions
|
|
143
|
+
|
|
144
|
+
`--confirm` needs a human. For a program embedding an agent there are two
|
|
145
|
+
orthogonal controls instead, and both are enforced by the wiring — a denied tool
|
|
146
|
+
is **not registered at all**, so the model never sees it and never plans around
|
|
147
|
+
it.
|
|
148
|
+
|
|
149
|
+
| Flag | Meaning |
|
|
150
|
+
| --- | --- |
|
|
151
|
+
| `--permission-mode auto` | Every tool (the default) |
|
|
152
|
+
| `--permission-mode plan` | Every tool registered; the five that write refuse at call time. Exactly `aragon -p --plan` |
|
|
153
|
+
| `--permission-mode strict` | Nothing, until `--allow-tool` says otherwise |
|
|
154
|
+
| `--allow-tool <names>` | **Adds** to the baseline. Repeatable; comma lists accepted |
|
|
155
|
+
| `--deny-tool <names>` | **Removes**. Wins over `--allow-tool` |
|
|
156
|
+
|
|
157
|
+
Each of these reads as one sentence:
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
# a research agent that cannot touch anything
|
|
161
|
+
aragon exec --permission-mode strict --allow-tool read_file,grep,glob,list_dir \
|
|
162
|
+
--output-format json "which files define the retry policy?"
|
|
163
|
+
|
|
164
|
+
# do anything except shell out
|
|
165
|
+
aragon exec --deny-tool bash --output-format json "fix the failing test"
|
|
166
|
+
|
|
167
|
+
# read-only research and a written plan, no edits
|
|
168
|
+
aragon exec --permission-mode plan --output-format json "how would you add OAuth?"
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
The policy reaches **subagents**: a `task` dispatch cannot hand a child a tool
|
|
172
|
+
the parent was denied. An unknown tool name is a usage error (exit 2) rather
|
|
173
|
+
than a silent no-op.
|
|
174
|
+
|
|
175
|
+
### Sessions across invocations
|
|
176
|
+
|
|
177
|
+
A wrapper that respawns per turn — a CI step, an HTTP handler, a queue worker —
|
|
178
|
+
needs the conversation to outlive the process.
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
aragon exec --session-id ci-42 --output-format json "start the migration plan"
|
|
182
|
+
aragon exec --session-id ci-42 --output-format json "now do step 1"
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
| Flag | If the session exists | If it does not |
|
|
186
|
+
| --- | --- | --- |
|
|
187
|
+
| `--session-id <id>` | Resume it | **Create** it (the wrapper-friendly upsert) |
|
|
188
|
+
| `--resume <id\|path>` | Resume it | Exit 2, `session_not_found` |
|
|
189
|
+
| `-c, --continue` | Resume the newest session for this directory | Exit 2, `no_session_for_cwd` |
|
|
190
|
+
| `--no-save-session` | — | Run statelessly; write nothing |
|
|
191
|
+
|
|
192
|
+
Sessions live in `~/.aragon-agent/sessions/`. An id must match
|
|
193
|
+
`[A-Za-z0-9][A-Za-z0-9._-]{0,63}` — it becomes a filename, and the caller
|
|
194
|
+
supplying it is frequently the least trusted input in the pipeline.
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
aragon sessions list [--json] [-n 20] [--all]
|
|
198
|
+
aragon sessions show <id> [--json]
|
|
199
|
+
aragon sessions rm <id> --yes
|
|
200
|
+
aragon sessions prune [--older-than 7] [--dry-run] [--all] --yes
|
|
201
|
+
aragon sessions path
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Two same-id runs cannot interleave: the second exits 2 with `session_busy`
|
|
205
|
+
rather than blocking, because a wrapper that wants a queue can build one and a
|
|
206
|
+
CLI that blocks on a lock it cannot show you is a hang.
|
|
207
|
+
|
|
208
|
+
### Budgets
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
aragon exec --max-turns 20 --max-duration 600000 --output-format json "…"
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
`--max-turns <n>` bounds assistant turns; `--max-duration <ms>` bounds the whole
|
|
215
|
+
run's wall clock. Both abort through the same path `Esc` uses, so subagents are
|
|
216
|
+
torn down and the engine settles normally. **Neither is an error** — `isError`
|
|
217
|
+
stays `false` — but both exit **3**, so a shell script can tell "ran out of
|
|
218
|
+
budget" from "finished" and from "broke" without parsing JSON.
|
|
219
|
+
|
|
220
|
+
Two neighbours they do not replace: `--idle-timeout` bounds a *silent stretch*
|
|
221
|
+
inside the run and still applies (a `--max-duration` longer than the idle
|
|
222
|
+
watchdog will usually be pre-empted by it, which surfaces as
|
|
223
|
+
`stopReason: "error"`); `--tool-timeout` bounds a *single tool call*.
|
|
224
|
+
|
|
225
|
+
A run that finishes on its own in exactly `--max-turns` turns exits `0` with
|
|
226
|
+
`stopReason: "end_turn"`. The budget fires only when a further turn would start.
|
|
227
|
+
|
|
228
|
+
### Exit codes
|
|
229
|
+
|
|
230
|
+
| Code | Meaning |
|
|
231
|
+
| --- | --- |
|
|
232
|
+
| `0` | Success |
|
|
233
|
+
| `1` | Agent / runtime error (the run started and failed) |
|
|
234
|
+
| `2` | Configuration or usage error (the run never started; stdout is empty) |
|
|
235
|
+
| `3` | Stopped by a caller-set budget (`--max-turns`, `--max-duration`) |
|
|
236
|
+
| `130` / `143` / `129` | Interrupted by `SIGINT` / `SIGTERM` / `SIGHUP` (`128 + signo`) |
|
|
237
|
+
|
|
238
|
+
### Spawning it from Node
|
|
239
|
+
|
|
240
|
+
```js
|
|
241
|
+
import { spawn } from 'node:child_process';
|
|
242
|
+
import readline from 'node:readline';
|
|
243
|
+
|
|
244
|
+
function runAragon(prompt, args = []) {
|
|
245
|
+
return new Promise((resolve, reject) => {
|
|
246
|
+
const child = spawn(
|
|
247
|
+
'aragon',
|
|
248
|
+
['exec', '--output-format', 'stream-json', ...args, prompt],
|
|
249
|
+
{ stdio: ['ignore', 'pipe', 'inherit'] },
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
let result = null;
|
|
253
|
+
const lines = readline.createInterface({ input: child.stdout });
|
|
254
|
+
lines.on('line', (line) => {
|
|
255
|
+
let event;
|
|
256
|
+
try {
|
|
257
|
+
event = JSON.parse(line);
|
|
258
|
+
} catch {
|
|
259
|
+
return; // Never fatal: forward compatibility runs both ways.
|
|
260
|
+
}
|
|
261
|
+
if (event.type === 'result') result = event;
|
|
262
|
+
// Ignore every type you do not know. That is the contract.
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
// RESOLVE ON EXIT AS WELL AS ON `result`. Process exit is terminal whether
|
|
266
|
+
// or not a `result` arrived — see Limits below — and a consumer that waits
|
|
267
|
+
// only for `result` hangs when the run is killed.
|
|
268
|
+
child.on('error', reject);
|
|
269
|
+
child.on('close', (code) => resolve({ code, result }));
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### In CI
|
|
275
|
+
|
|
276
|
+
```bash
|
|
277
|
+
aragon exec \
|
|
278
|
+
--permission-mode plan \
|
|
279
|
+
--max-turns 20 \
|
|
280
|
+
--max-duration 900000 \
|
|
281
|
+
--output-format json \
|
|
282
|
+
--session-id "ci-${GITHUB_RUN_ID}" \
|
|
283
|
+
"Review the diff on this branch and list the risks." \
|
|
284
|
+
| jq -r .result
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
`aragon doctor --json` exits 1 with a named failing check when the machine is
|
|
288
|
+
not configured, which makes a good first step in the job.
|
|
289
|
+
|
|
290
|
+
Feeding several turns to one process:
|
|
291
|
+
|
|
292
|
+
```bash
|
|
293
|
+
printf '%s\n' \
|
|
294
|
+
'{"type":"user","text":"list the files you would change"}' \
|
|
295
|
+
'{"type":"user","text":"now change the first one"}' \
|
|
296
|
+
'{"type":"end"}' \
|
|
297
|
+
| aragon exec --input-format stream-json --output-format stream-json
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
### Limits
|
|
301
|
+
|
|
302
|
+
Four things this does **not** do, stated plainly because the audience for this
|
|
303
|
+
section is the one that would be misled by their omission.
|
|
304
|
+
|
|
305
|
+
1. **There is no filesystem sandbox.** Tool paths are not confined to the
|
|
306
|
+
working directory: an absolute path resolves to itself. Tool permission —
|
|
307
|
+
not a directory allowlist — is the boundary that is real, and a run with
|
|
308
|
+
`bash` enabled can reach anything the invoking user can. There is deliberately
|
|
309
|
+
no `--add-dir`, because it would be a promise the wiring does not keep.
|
|
310
|
+
2. **Process exit is terminal, `result` or no `result`.** On POSIX, `SIGINT`,
|
|
311
|
+
`SIGTERM` and `SIGHUP` all produce a final `result` before the process ends.
|
|
312
|
+
**On Windows only Ctrl-C does** — Node does not deliver the other two to
|
|
313
|
+
listeners, so `taskkill` ends the process outright. Resolve on process exit as
|
|
314
|
+
well as on `result`, or a killed run hangs your parent process.
|
|
315
|
+
3. **A session created by `aragon exec` and resumed with `aragon --resume`
|
|
316
|
+
carries the model's full memory and shows no scrollback** for the turns that
|
|
317
|
+
ran headlessly. The reverse direction is fully faithful: a TUI `/save` resumed
|
|
318
|
+
by `aragon exec --resume <id>` keeps everything.
|
|
319
|
+
4. **`aragon sessions prune` shares a directory with the TUI's `/save`**, so it
|
|
320
|
+
only ever touches sessions `exec` created — files that carry an exec `meta`
|
|
321
|
+
block — unless you pass `--all`. `sessions show` and `sessions rm` accept any
|
|
322
|
+
id, because you named it.
|
|
323
|
+
|
|
41
324
|
## Usage
|
|
42
325
|
|
|
43
326
|
```
|
|
44
327
|
aragon [prompt] Start the interactive TUI (or one-shot with -p / piped stdin)
|
|
45
328
|
aragon -p, --print [prompt] Headless: stream the answer to stdout, then exit
|
|
329
|
+
aragon exec [prompt] Machine-facing run: JSON events, sessions, permissions, budgets
|
|
330
|
+
aragon sessions <sub> [id] Manage exec sessions: list | show | rm | prune | path
|
|
331
|
+
aragon info [--json] Print what this installation supports
|
|
332
|
+
aragon doctor [--json] [--probe] Check this machine is configured to run aragon
|
|
46
333
|
aragon config Open the settings screen
|
|
47
334
|
aragon config set <key> <value> Non-interactive config write (e.g. model, provider)
|
|
48
335
|
aragon config path Print the config file path
|
|
@@ -51,6 +338,11 @@ aragon --version | -v Print the version
|
|
|
51
338
|
aragon --help | -h Print help
|
|
52
339
|
```
|
|
53
340
|
|
|
341
|
+
`aragon exec` is documented in full under
|
|
342
|
+
[Use it from another project](#use-it-from-another-project). Every global flag
|
|
343
|
+
below works there too, in either position — `aragon --model m exec "…"` and
|
|
344
|
+
`aragon exec --model m "…"` parse identically.
|
|
345
|
+
|
|
54
346
|
### Global flags
|
|
55
347
|
|
|
56
348
|
| Flag | Meaning |
|
|
@@ -95,7 +387,8 @@ aragon --help | -h Print help
|
|
|
95
387
|
| `--quiet` | (print mode) suppress tool/usage lines on stderr |
|
|
96
388
|
|
|
97
389
|
Exit codes: `0` success · `1` agent/runtime error · `2` config/usage error ·
|
|
98
|
-
`
|
|
390
|
+
`3` stopped by a caller-set budget (`aragon exec` only) · `128 + signo` when a
|
|
391
|
+
signal ends the run (`130` SIGINT, `143` SIGTERM, `129` SIGHUP).
|
|
99
392
|
|
|
100
393
|
## Slash commands (interactive)
|
|
101
394
|
|
|
@@ -138,7 +431,8 @@ completes, `Up` / `Down` moves the selection, `Esc` closes it.
|
|
|
138
431
|
| --- | --- |
|
|
139
432
|
| `Enter` | Submit (idle) / queue a steering message (running) |
|
|
140
433
|
| `Alt+Enter` / `Shift+Enter` | Insert a newline |
|
|
141
|
-
| `Shift+Tab` | Toggle **plan mode** (`BUILD` <-> `PLAN`). Equivalent: `/plan
|
|
434
|
+
| `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) |
|
|
435
|
+
| `Ctrl+P` | The same toggle, over a channel no Windows console can swallow. Use it if `Shift+Tab` does nothing |
|
|
142
436
|
| `Esc` | Abort the run / close an overlay / close a popup |
|
|
143
437
|
| `Ctrl+C` ×2 | Exit (first press warns) |
|
|
144
438
|
| `Ctrl+L` | Redraw the frame (clear the screen in inline mode) |
|
|
@@ -146,7 +440,7 @@ completes, `Up` / `Down` moves the selection, `Esc` closes it.
|
|
|
146
440
|
| `Ctrl+O` | Expand / collapse the most recent tool card — including its diff |
|
|
147
441
|
| `PgUp` / `PgDn` | Scroll the transcript a page (full-screen mode) |
|
|
148
442
|
| `Shift+↑` / `Shift+↓` | Scroll the transcript a line (full-screen mode) |
|
|
149
|
-
| Mouse wheel | Scroll the transcript (or the open overlay) |
|
|
443
|
+
| Mouse wheel | Scroll the transcript (or the open overlay). On Windows needs Node ≥ 22.17 — see [Requirements](#requirements) |
|
|
150
444
|
| `Shift`+wheel | Scroll the transcript a page |
|
|
151
445
|
| `Home` / `End`, `Ctrl+A` / `Ctrl+E` | Cursor to line start / end |
|
|
152
446
|
| `Alt/Ctrl+←` / `→` | Word-wise cursor jump |
|
|
@@ -177,6 +471,9 @@ before you meet them:
|
|
|
177
471
|
something this CLI can opt out of while still reading wheel events.
|
|
178
472
|
- **`tmux` with `mouse on` keeps the wheel for itself**, so the app never sees
|
|
179
473
|
it. `set -g mouse off` hands it back.
|
|
474
|
+
- **A Windows console on Node below 22.17.0 cannot report the wheel at all**,
|
|
475
|
+
so aragon does not ask it to. See [Requirements](#requirements); `PgUp` /
|
|
476
|
+
`PgDn` and `Shift+↑` / `Shift+↓` scroll from the keyboard meanwhile.
|
|
180
477
|
|
|
181
478
|
Turn the whole thing off with `--no-mouse`, `ARAGON_MOUSE=0`, or
|
|
182
479
|
`aragon config set mouse false`. With mouse support off the wheel simply does
|
|
@@ -460,9 +757,28 @@ it is an informed act rather than a stray keypress.
|
|
|
460
757
|
|
|
461
758
|
### If `Shift+Tab` does nothing
|
|
462
759
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
760
|
+
**Press `Ctrl+P`.** It is the same toggle and it does not depend on any of the
|
|
761
|
+
machinery below.
|
|
762
|
+
|
|
763
|
+
To find out why, run this in the same terminal, press `Shift+Tab` once, then
|
|
764
|
+
`Ctrl+C`:
|
|
765
|
+
|
|
766
|
+
```powershell
|
|
767
|
+
node -e "process.stdin.setRawMode(true);process.stdin.resume();process.stdin.on('data',b=>{console.log(b.toString('hex'));if(b[0]===3)process.exit(0)})"
|
|
768
|
+
```
|
|
769
|
+
|
|
770
|
+
| It prints | What it means |
|
|
771
|
+
| --- | --- |
|
|
772
|
+
| `09` | The console is swallowing the modifier — the key is arriving as an ordinary `Tab`. Check `node -v`: below 22.17.0 (or on 24.0/24.1) this is expected, and aragon's own attempt to fix it was blocked. See [Requirements](#requirements) |
|
|
773
|
+
| `1b5b5a` | The key reaches the process fine, so something inside aragon is eating it — most often an overlay is open, or the run is still going and the switch is pending (the status bar shows `BUILD → PLAN`) |
|
|
774
|
+
| nothing | Your terminal, multiplexer or remote-desktop stack never sent it; some never send `CSI Z` at all |
|
|
775
|
+
|
|
776
|
+
On the first row the wheel is dead for the same reason, and with the `/` palette
|
|
777
|
+
or an `@` popup open the stray `Tab` completes that entry over your draft.
|
|
778
|
+
|
|
779
|
+
`/plan`, `/plan on`, `/plan off` and `/plan status` are first-class equivalents
|
|
780
|
+
in every case, and `--plan` / `ARAGON_PLAN=1` / `planModeDefault` cover the
|
|
781
|
+
startup case. `aragon doctor` reports this machine's answer as `vt-input`.
|
|
466
782
|
|
|
467
783
|
### Headless
|
|
468
784
|
|
|
@@ -28,6 +28,45 @@ export declare const MODE_LABEL: Record<AgentMode, string>;
|
|
|
28
28
|
* and nothing else. Two ship today (§1 non-goals).
|
|
29
29
|
*/
|
|
30
30
|
export declare function nextMode(current: AgentMode): AgentMode;
|
|
31
|
+
/**
|
|
32
|
+
* The keys that toggle the mode, as the user sees them written
|
|
33
|
+
* (shift-tab-mode-toggle-still-dead-on-windows, C2).
|
|
34
|
+
*
|
|
35
|
+
* `fallback` exists because `primary` CANNOT be made to work everywhere. On a
|
|
36
|
+
* Windows console without `ENABLE_VIRTUAL_TERMINAL_INPUT`, libuv takes the
|
|
37
|
+
* character branch for `VK_TAB` and drops the modifier wholesale, so `Shift+Tab`
|
|
38
|
+
* and `Ctrl+Tab` both arrive as a bare `0x09` - byte-identical to `Tab`. C1
|
|
39
|
+
* turns that bit back on where it can, but it has three known ways to fail that
|
|
40
|
+
* are outside this package (an old conhost refusing the bit, a locked-down
|
|
41
|
+
* PowerShell, a foreign process resetting the mode), so a rung that depends on
|
|
42
|
+
* NOTHING has to exist underneath it.
|
|
43
|
+
*
|
|
44
|
+
* `ctrl+p` IS MEASURED, not guessed. On the lossy path it arrives as `0x10`,
|
|
45
|
+
* because Windows generates a real control character for `Ctrl+<letter>` and
|
|
46
|
+
* libuv's character branch forwards it untouched; `Alt+M` produces no bytes at
|
|
47
|
+
* all and `Ctrl+Tab` collapses exactly like `Shift+Tab` (analysis section 3.6).
|
|
48
|
+
* Any idle `Ctrl+<letter>` in the `0x01-0x1A` range would work; this one is free
|
|
49
|
+
* throughout the package - `keymap.ts` claims a/e/w/u/k, `App` claims c/l/t/o,
|
|
50
|
+
* and `PromptInput` returns early on every `key.ctrl` - so binding it takes no
|
|
51
|
+
* behaviour away from anyone. `Ctrl+B` survives the same path but is tmux's
|
|
52
|
+
* default prefix, and this binding is unconditional and cross-platform, so it
|
|
53
|
+
* would be silently dead for a whole class of macOS and Linux users.
|
|
54
|
+
*
|
|
55
|
+
* BOUND UNCONDITIONALLY, on every platform. A binding that only exists where it
|
|
56
|
+
* is needed cannot be documented in one place, cannot be taught by muscle
|
|
57
|
+
* memory, and is missing on precisely the machine whose capability probe was
|
|
58
|
+
* wrong.
|
|
59
|
+
*
|
|
60
|
+
* THESE ARE THE LABELS, NOT THE BINDING. Editing a value here changes only what
|
|
61
|
+
* the user is TOLD to press - the hint row, the startup notice, `doctor`. The
|
|
62
|
+
* key that is actually listened for is `input === 'p'` in `App`'s `useInput`,
|
|
63
|
+
* and the help overlay and README spell both keys out in their own title case.
|
|
64
|
+
* Change one without the others and the product advertises a key it ignores.
|
|
65
|
+
*/
|
|
66
|
+
export declare const MODE_TOGGLE_KEYS: {
|
|
67
|
+
readonly primary: "shift+tab";
|
|
68
|
+
readonly fallback: "ctrl+p";
|
|
69
|
+
};
|
|
31
70
|
/** The two tools that only exist because a human is attached. */
|
|
32
71
|
export declare const PLAN_TOOL_NAMES: readonly ["ask_user", "submit_plan"];
|
|
33
72
|
/**
|
package/dist/agent/agent-mode.js
CHANGED
|
@@ -30,6 +30,42 @@ export function nextMode(current) {
|
|
|
30
30
|
const index = AGENT_MODES.indexOf(current);
|
|
31
31
|
return AGENT_MODES[(index + 1) % AGENT_MODES.length];
|
|
32
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* The keys that toggle the mode, as the user sees them written
|
|
35
|
+
* (shift-tab-mode-toggle-still-dead-on-windows, C2).
|
|
36
|
+
*
|
|
37
|
+
* `fallback` exists because `primary` CANNOT be made to work everywhere. On a
|
|
38
|
+
* Windows console without `ENABLE_VIRTUAL_TERMINAL_INPUT`, libuv takes the
|
|
39
|
+
* character branch for `VK_TAB` and drops the modifier wholesale, so `Shift+Tab`
|
|
40
|
+
* and `Ctrl+Tab` both arrive as a bare `0x09` - byte-identical to `Tab`. C1
|
|
41
|
+
* turns that bit back on where it can, but it has three known ways to fail that
|
|
42
|
+
* are outside this package (an old conhost refusing the bit, a locked-down
|
|
43
|
+
* PowerShell, a foreign process resetting the mode), so a rung that depends on
|
|
44
|
+
* NOTHING has to exist underneath it.
|
|
45
|
+
*
|
|
46
|
+
* `ctrl+p` IS MEASURED, not guessed. On the lossy path it arrives as `0x10`,
|
|
47
|
+
* because Windows generates a real control character for `Ctrl+<letter>` and
|
|
48
|
+
* libuv's character branch forwards it untouched; `Alt+M` produces no bytes at
|
|
49
|
+
* all and `Ctrl+Tab` collapses exactly like `Shift+Tab` (analysis section 3.6).
|
|
50
|
+
* Any idle `Ctrl+<letter>` in the `0x01-0x1A` range would work; this one is free
|
|
51
|
+
* throughout the package - `keymap.ts` claims a/e/w/u/k, `App` claims c/l/t/o,
|
|
52
|
+
* and `PromptInput` returns early on every `key.ctrl` - so binding it takes no
|
|
53
|
+
* behaviour away from anyone. `Ctrl+B` survives the same path but is tmux's
|
|
54
|
+
* default prefix, and this binding is unconditional and cross-platform, so it
|
|
55
|
+
* would be silently dead for a whole class of macOS and Linux users.
|
|
56
|
+
*
|
|
57
|
+
* BOUND UNCONDITIONALLY, on every platform. A binding that only exists where it
|
|
58
|
+
* is needed cannot be documented in one place, cannot be taught by muscle
|
|
59
|
+
* memory, and is missing on precisely the machine whose capability probe was
|
|
60
|
+
* wrong.
|
|
61
|
+
*
|
|
62
|
+
* THESE ARE THE LABELS, NOT THE BINDING. Editing a value here changes only what
|
|
63
|
+
* the user is TOLD to press - the hint row, the startup notice, `doctor`. The
|
|
64
|
+
* key that is actually listened for is `input === 'p'` in `App`'s `useInput`,
|
|
65
|
+
* and the help overlay and README spell both keys out in their own title case.
|
|
66
|
+
* Change one without the others and the product advertises a key it ignores.
|
|
67
|
+
*/
|
|
68
|
+
export const MODE_TOGGLE_KEYS = { primary: 'shift+tab', fallback: 'ctrl+p' };
|
|
33
69
|
/** The two tools that only exist because a human is attached. */
|
|
34
70
|
export const PLAN_TOOL_NAMES = ['ask_user', 'submit_plan'];
|
|
35
71
|
/**
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
import { ModelRegistry, type AgentEvent, type AgentTool, type Message, type ModelInfo, type ModelRef, type ProviderRegistry, type ThinkingLevel } from '@aragon-agent/core';
|
|
12
12
|
import type { CliConfig, FastConfig, RetryConfig, TeamConfig, ThemeName, TodoConfig } from '../config/schema.js';
|
|
13
13
|
import { type ConfirmRequest } from '../tools/index.js';
|
|
14
|
+
import type { ToolPermission } from '../exec/permission.js';
|
|
14
15
|
import { type ToolOutputListener } from '../tools/tool-output-store.js';
|
|
15
16
|
import type { FilePatch } from '../tools/patch.js';
|
|
16
17
|
import type { TeamEvent, TeamSnapshot } from '../team/types.js';
|
|
@@ -70,6 +71,28 @@ export interface ControllerDeps {
|
|
|
70
71
|
* mid-run, and the sentence is guidance rather than a contract.
|
|
71
72
|
*/
|
|
72
73
|
todoPanelCapable?: boolean;
|
|
74
|
+
/**
|
|
75
|
+
* The `aragon exec` tool policy (cli-integration-surface §4.2).
|
|
76
|
+
*
|
|
77
|
+
* FORWARDED TO **TWO** PLACES, AND BOTH ARE REQUIRED (R-4 / R-5). It goes into
|
|
78
|
+
* `createBuiltinTools` for the lead's own array, and into `TeamRuntimeDeps` so
|
|
79
|
+
* children inherit it. Without the second, `--deny-tool bash` is bypassed by
|
|
80
|
+
* one `task` call and NOTHING ANYWHERE REPORTS IT — a security control that
|
|
81
|
+
* silently does not hold. Plan mode's "reaches one level down" guarantee is
|
|
82
|
+
* the precedent; AC-12 is the test.
|
|
83
|
+
*
|
|
84
|
+
* Absent on every existing path (the TUI, `-p`, every subcommand), which is
|
|
85
|
+
* what keeps the tool array identical there — see the option's own comment in
|
|
86
|
+
* `tools/index.ts`.
|
|
87
|
+
*/
|
|
88
|
+
permission?: ToolPermission;
|
|
89
|
+
/**
|
|
90
|
+
* `--append-system-prompt` text, appended verbatim to the composed prompt
|
|
91
|
+
* (§4.1). Threaded through `composeSystemPrompt` so it survives every rebuild
|
|
92
|
+
* — a `/cwd` change, a skills rescan, a mode flip — rather than being spliced
|
|
93
|
+
* once at construction and lost at the first `rebuildSystemPrompt()`.
|
|
94
|
+
*/
|
|
95
|
+
appendSystemPrompt?: string;
|
|
73
96
|
}
|
|
74
97
|
/** What `setAgentMode` actually adopted — see `AgentController.setAgentMode`. */
|
|
75
98
|
export interface AgentModeState {
|
|
@@ -98,6 +121,15 @@ export declare class AgentController {
|
|
|
98
121
|
private onSkillsChanged?;
|
|
99
122
|
/** Mutable session cwd — tools resolve relative paths against this. */
|
|
100
123
|
private cwd;
|
|
124
|
+
/**
|
|
125
|
+
* `--append-system-prompt` text (cli-integration-surface §4.1).
|
|
126
|
+
*
|
|
127
|
+
* A FIELD, NOT A LOCAL, because `composeSystemPrompt()` is called again on
|
|
128
|
+
* every rebuild — a `/cwd` change, a skills rescan, a plan-mode flip — and a
|
|
129
|
+
* value spliced once at construction would silently vanish at the first one.
|
|
130
|
+
* Empty on every path but `aragon exec`.
|
|
131
|
+
*/
|
|
132
|
+
private readonly appendSystemPrompt;
|
|
101
133
|
private effectiveMode;
|
|
102
134
|
private pendingMode;
|
|
103
135
|
private askRounds;
|