@ucsandman/legcli 0.7.0
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 +560 -0
- package/LICENSE +58 -0
- package/NOTICE +4 -0
- package/README.md +558 -0
- package/bin/fake-agent.mjs +143 -0
- package/bin/leg.mjs +449 -0
- package/docs/DECISIONS.md +119 -0
- package/docs/DEMO.md +214 -0
- package/docs/DEVIATIONS.md +182 -0
- package/docs/ERRORS.md +326 -0
- package/docs/README.md +40 -0
- package/docs/REUSE.md +289 -0
- package/docs/ROADMAP-v2.md +55 -0
- package/docs/VOCABULARY.md +243 -0
- package/docs/adapters.md +300 -0
- package/docs/board-guide.md +579 -0
- package/docs/cli-contracts.md +401 -0
- package/docs/concepts.md +426 -0
- package/docs/configuration.md +220 -0
- package/docs/faq.md +206 -0
- package/docs/getting-started.md +251 -0
- package/docs/real-run.md +80 -0
- package/docs/review-2026-09-14.md +72 -0
- package/docs/screenshots/board-400px.png +0 -0
- package/docs/screenshots/board-details-open.png +0 -0
- package/docs/screenshots/board-done.png +0 -0
- package/docs/screenshots/board-drawer.png +0 -0
- package/docs/screenshots/board-empty.png +0 -0
- package/docs/screenshots/board-handoff.png +0 -0
- package/docs/screenshots/board-running.png +0 -0
- package/docs/screenshots/demo-1-claude-running.png +0 -0
- package/docs/screenshots/demo-2-limit-hit.png +0 -0
- package/docs/screenshots/demo-3-handoff-bundle.png +0 -0
- package/docs/screenshots/demo-4-codex-running.png +0 -0
- package/docs/screenshots/demo-5-done.png +0 -0
- package/docs/screenshots/floor-final-1280.png +0 -0
- package/docs/screenshots/floor-final-400.png +0 -0
- package/docs/screenshots/floor-landing.png +0 -0
- package/docs/screenshots/floor.png +0 -0
- package/docs/screenshots/share-guest-1280.png +0 -0
- package/docs/screenshots/share-owner-1280.png +0 -0
- package/docs/screenshots/terminals-1280.png +0 -0
- package/docs/screenshots/thumb-done.jpg +0 -0
- package/docs/screenshots/thumb-handoff-details.jpg +0 -0
- package/docs/screenshots/thumb-limit-hit.jpg +0 -0
- package/docs/screenshots/youtube-thumbnail.jpg +0 -0
- package/fixtures/limits/agy/agy-resource-exhausted.json +11 -0
- package/fixtures/limits/claude/claude-budget-limit.json +11 -0
- package/fixtures/limits/claude/claude-max-turns.json +11 -0
- package/fixtures/limits/claude/claude-model-limit.json +11 -0
- package/fixtures/limits/claude/claude-session-limit.json +11 -0
- package/fixtures/limits/claude/claude-weekly-limit.json +11 -0
- package/fixtures/limits/codex/codex-quota-exceeded.json +11 -0
- package/fixtures/limits/codex/codex-rate-limit-exceeded.json +11 -0
- package/fixtures/limits/codex/codex-skills-notice.json +11 -0
- package/fixtures/limits/codex/codex-usage-limit-reached.json +11 -0
- package/fixtures/limits/codex/codex-usage-limit.json +11 -0
- package/fixtures/limits/generic/generic-429.json +11 -0
- package/fixtures/limits/generic/generic-overloaded.json +11 -0
- package/fixtures/limits/generic/generic-quota.json +11 -0
- package/fixtures/limits/generic/generic-rate-limit.json +11 -0
- package/fixtures/limits/generic/generic-resource-exhausted.json +11 -0
- package/fixtures/limits/generic/generic-usage-limit.json +11 -0
- package/fixtures/limits/grok/grok-not-logged-in.json +11 -0
- package/fixtures/limits/negative/auth-source-set.json +11 -0
- package/fixtures/limits/negative/compile-error.json +11 -0
- package/fixtures/limits/negative/empty-stdout-exit-0.json +11 -0
- package/fixtures/live/agy/attempt-1-scratch-workspace.out.log +1 -0
- package/fixtures/live/agy/cmd.txt +1 -0
- package/fixtures/live/agy/err.log +0 -0
- package/fixtures/live/agy/out.log +1 -0
- package/fixtures/live/agy/parsed.json +5 -0
- package/fixtures/live/agy/run.json +18 -0
- package/fixtures/live/agy/supervisor.log +2 -0
- package/fixtures/live/claude/cmd.txt +1 -0
- package/fixtures/live/claude/err.log +0 -0
- package/fixtures/live/claude/limit-rate_limit.json +21 -0
- package/fixtures/live/claude/out.log +1 -0
- package/fixtures/live/claude/parsed.json +11 -0
- package/fixtures/live/claude/run.json +18 -0
- package/fixtures/live/claude/supervisor.log +2 -0
- package/fixtures/live/codex/cmd.txt +1 -0
- package/fixtures/live/codex/err.log +1 -0
- package/fixtures/live/codex/last.md +2 -0
- package/fixtures/live/codex/limit-usage_limit_exceeded.json +25 -0
- package/fixtures/live/codex/out.log +8 -0
- package/fixtures/live/codex/parsed.json +15 -0
- package/fixtures/live/codex/run.json +18 -0
- package/fixtures/live/codex/supervisor.log +2 -0
- package/fixtures/live/grok/cmd.txt +1 -0
- package/fixtures/live/grok/err.log +32 -0
- package/fixtures/live/grok/out.log +7 -0
- package/fixtures/live/grok/parsed.json +5 -0
- package/fixtures/live/grok/run.json +18 -0
- package/fixtures/live/grok/supervisor.log +2 -0
- package/fixtures/verified.json +34 -0
- package/package.json +68 -0
- package/scripts/board-shots.mjs +69 -0
- package/scripts/build-docs-site.mjs +308 -0
- package/scripts/check-claims.mjs +150 -0
- package/scripts/clean-clone-check.sh +42 -0
- package/scripts/license-sign.mjs +23 -0
- package/scripts/limits-table.mjs +38 -0
- package/scripts/live-limits.mjs +36 -0
- package/scripts/privacy-check.mjs +63 -0
- package/scripts/probe.mjs +92 -0
- package/scripts/seed-fake-cards.mjs +34 -0
- package/scripts/seed-floor-board.mjs +69 -0
- package/scripts/seed-wes-board.mjs +91 -0
- package/scripts/stripe-setup.mjs +99 -0
- package/scripts/vercel-env.mjs +40 -0
- package/src/accounts.mjs +119 -0
- package/src/adapters/agy.mjs +56 -0
- package/src/adapters/claude.mjs +54 -0
- package/src/adapters/codex.mjs +73 -0
- package/src/adapters/common.mjs +32 -0
- package/src/adapters/fake.mjs +48 -0
- package/src/adapters/grok.mjs +51 -0
- package/src/adapters/index.mjs +27 -0
- package/src/adapters/resolve.mjs +40 -0
- package/src/attach.mjs +592 -0
- package/src/auth.mjs +85 -0
- package/src/board/board.css +543 -0
- package/src/board/board.js +1307 -0
- package/src/board/favicon.svg +1 -0
- package/src/board/floor.html +127 -0
- package/src/board/floor.js +670 -0
- package/src/board/fonts/OFL-atkinson-hyperlegible-next.txt +93 -0
- package/src/board/fonts/OFL-azeret-mono.txt +94 -0
- package/src/board/fonts/atkinson-board.woff2 +0 -0
- package/src/board/fonts/azeret-board.woff2 +0 -0
- package/src/board/index.html +251 -0
- package/src/board/sessions.js +1451 -0
- package/src/bundle.mjs +103 -0
- package/src/cards.mjs +114 -0
- package/src/chain.mjs +264 -0
- package/src/commands.mjs +79 -0
- package/src/contract.mjs +73 -0
- package/src/env.mjs +19 -0
- package/src/fsx.mjs +72 -0
- package/src/git-snapshot.mjs +152 -0
- package/src/handoff.mjs +171 -0
- package/src/hook.mjs +49 -0
- package/src/land.mjs +106 -0
- package/src/launcher.mjs +272 -0
- package/src/leases.mjs +86 -0
- package/src/ledger.mjs +369 -0
- package/src/license.mjs +168 -0
- package/src/limits.mjs +97 -0
- package/src/live-capture.mjs +69 -0
- package/src/mergequeue.mjs +225 -0
- package/src/orchestrator.mjs +365 -0
- package/src/pipeline.mjs +100 -0
- package/src/preferences.mjs +47 -0
- package/src/presets.mjs +23 -0
- package/src/ratelimit.mjs +49 -0
- package/src/redact.mjs +47 -0
- package/src/resume.mjs +354 -0
- package/src/runner.mjs +438 -0
- package/src/scheduler.mjs +118 -0
- package/src/server.mjs +845 -0
- package/src/session-detail.mjs +129 -0
- package/src/sessions.mjs +202 -0
- package/src/share.mjs +163 -0
- package/src/stations/agent.mjs +42 -0
- package/src/stations/build.mjs +9 -0
- package/src/stations/human.mjs +5 -0
- package/src/stations/land.mjs +6 -0
- package/src/stations/plan.mjs +8 -0
- package/src/stations/pr.mjs +31 -0
- package/src/stations/review.mjs +8 -0
- package/src/stations/test.mjs +27 -0
- package/src/store.mjs +90 -0
- package/src/sync/dashclaw.mjs +70 -0
- package/src/sync/index.mjs +54 -0
- package/src/sync/workboard.mjs +54 -0
- package/src/taps/agy.mjs +64 -0
- package/src/taps/claude-usage.mjs +67 -0
- package/src/taps/claude.mjs +170 -0
- package/src/taps/codex.mjs +286 -0
- package/src/trust.mjs +323 -0
- package/src/usage.mjs +179 -0
- package/src/wait.mjs +30 -0
- package/src/worktree.mjs +207 -0
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
# CLI contracts
|
|
2
|
+
|
|
3
|
+
What Leg knows about each coding-agent CLI, where every fact came from, and
|
|
4
|
+
which facts were observed live on the build machine versus read from `--help`
|
|
5
|
+
or docs. Two contracts per CLI: the headless argv a pipeline leg spawns
|
|
6
|
+
(§ per CLI below), and the interactive tap `leg <agent>` reads
|
|
7
|
+
(§ [Interactive taps](#interactive-taps)). Evidence:
|
|
8
|
+
`fixtures/help/<cli>.txt` (raw `--help`), `fixtures/live/<cli>/` (one real tiny
|
|
9
|
+
task per CLI, run through `src/runner.mjs launch` on 2026-09-11 UTC, the
|
|
10
|
+
evening of 2026-09-10 locally; paths under the local home directory are
|
|
11
|
+
replaced with `~`), and the interactive checks run on 2026-09-11.
|
|
12
|
+
|
|
13
|
+
Every fact line ends with `(source: …)`. `observed-live` means the build machine
|
|
14
|
+
did it; `docs-only` means the CLI's own `--help` or documentation says so and
|
|
15
|
+
Leg has not seen it happen.
|
|
16
|
+
|
|
17
|
+
Probe task (identical for every CLI, `scripts/probe.mjs`): "Create a file named
|
|
18
|
+
hello-<name>.txt in the current directory containing exactly the word hi.
|
|
19
|
+
Then create the directory .leg if it is missing and write the file
|
|
20
|
+
.leg/DONE containing the single line: done. Do nothing else. Do not ask
|
|
21
|
+
questions."
|
|
22
|
+
|
|
23
|
+
| CLI | version | probe result | adapter |
|
|
24
|
+
|-----|---------|--------------|---------|
|
|
25
|
+
| claude | 2.1.268 | exit 0, file yes, DONE yes, 20 s | `src/adapters/claude.mjs` |
|
|
26
|
+
| codex | codex-cli 0.153.4 | exit 0, file yes, DONE yes, 28 s | `src/adapters/codex.mjs` |
|
|
27
|
+
| agy | 1.2.0 | attempt 1 exit 0 but wrote to its scratch workspace; attempt 2 (`--add-dir`) exit 0, file yes, DONE yes, 41 s | `src/adapters/agy.mjs` |
|
|
28
|
+
| grok | 0.2.51 | exit 0, `stopReason: Cancelled`, no file: not logged in (device-code prompt) | `src/adapters/grok.mjs` exists, NOT registered |
|
|
29
|
+
|
|
30
|
+
The auth-source check: the build shell carried `ANTHROPIC_API_KEY` and
|
|
31
|
+
`OPENAI_API_KEY` (injected by the shell profile). Every adapter's `env()` deletes
|
|
32
|
+
them (`src/env.mjs`). No fixture contains "another auth source"
|
|
33
|
+
(`grep -ril "another auth source" fixtures/live` is empty), claude's and agy's
|
|
34
|
+
stderr are 0 bytes, and codex's is the one stdin notice. observed-live.
|
|
35
|
+
|
|
36
|
+
## claude
|
|
37
|
+
|
|
38
|
+
- Version 2.1.268 (source: `claude --version`, fixtures/help/claude.version.txt).
|
|
39
|
+
- Binary: `~/.local/bin/claude.exe` (native exe, 211 MB); Leg resolves it
|
|
40
|
+
there first, else `claude` on PATH; `LEG_CLAUDE_BIN` overrides (source: `ls
|
|
41
|
+
~/.local/bin/claude.exe`; src/adapters/claude.mjs `resolve()`).
|
|
42
|
+
- Headless argv (exact, from fixtures/live/claude/cmd.txt):
|
|
43
|
+
`claude.exe -p --output-format json --permission-mode acceptEdits`
|
|
44
|
+
with the prompt on **stdin** (stdin: `pipe`). Optional: `--max-turns N`,
|
|
45
|
+
`--resume <session-id>`, `--model <alias|name>`, `--allowedTools a,b`
|
|
46
|
+
(source: `claude --help` lines for each flag; observed-live for the base argv).
|
|
47
|
+
- Output: ONE JSON object on stdout, printed only when the session ends
|
|
48
|
+
(test/lessons.test.mjs `json-only-at-end`; observed-live: 20 s of silence
|
|
49
|
+
then the object). Top-level
|
|
50
|
+
keys observed: `type:"result"`, `subtype:"success"`, `is_error`,
|
|
51
|
+
`stop_reason:"end_turn"`, `terminal_reason:"completed"`, `api_error_status`,
|
|
52
|
+
`session_id`, `num_turns`, `result` (final text), `permission_denials` (array),
|
|
53
|
+
`usage{input_tokens,output_tokens,cache_read_input_tokens,…}`, `modelUsage`,
|
|
54
|
+
`total_cost_usd`, `duration_ms` (source: fixtures/live/claude/out.log).
|
|
55
|
+
Leg reads `session_id`, `result`, `stop_reason`, `subtype`, `is_error`,
|
|
56
|
+
`terminal_reason`, `api_error_status`, `permission_denials.length`.
|
|
57
|
+
- Model: with no `--model`, the account's default model answered
|
|
58
|
+
(`modelUsage` names claude-fable-5-1 and claude-haiku-4-5). A chain entry
|
|
59
|
+
should name `model` to control spend; Leg passes it through as `--model`
|
|
60
|
+
(source: fixtures/live/claude/out.log `modelUsage`; `claude --help --model`).
|
|
61
|
+
- Exit codes:
|
|
62
|
+
|
|
63
|
+
| exit | meaning | source |
|
|
64
|
+
|------|---------|--------|
|
|
65
|
+
| 0 | completed, JSON printed | observed-live, fixtures/live/claude/run.json |
|
|
66
|
+
| non-zero | launch or fatal error; JSON may be absent | docs-only (`claude --help` does not enumerate codes; phase 4 records the fixture cases) |
|
|
67
|
+
|
|
68
|
+
- Permission modes (`--permission-mode` choices in 2.1.268): `acceptEdits`,
|
|
69
|
+
`auto`, `bypassPermissions`, `manual`, `dontAsk`, `plan` (source: `claude
|
|
70
|
+
--help`). Leg: default `acceptEdits`; allowed `acceptEdits`, `auto`, `plan`,
|
|
71
|
+
`manual`, `dontAsk`; forbidden `bypassPermissions`,
|
|
72
|
+
`--dangerously-skip-permissions`, `--allow-dangerously-skip-permissions`.
|
|
73
|
+
`acceptEdits` allowed the file write headless (observed-live). The Agent tool
|
|
74
|
+
is never disallowed (test/lessons.test.mjs `agent-tool-never-disallowed`).
|
|
75
|
+
`--permission-prompts none` exists
|
|
76
|
+
("anything that would prompt is denied automatically") and is not passed;
|
|
77
|
+
headless `-p` has no host to answer prompts anyway (source: `claude --help`).
|
|
78
|
+
- Resume: `--resume <session-id>` (docs-only until phase 5 exercises it);
|
|
79
|
+
`--fork-session` creates a new id on resume (source: `claude --help`).
|
|
80
|
+
- Child-process rules: `ANTHROPIC_API_KEY`, `ANTHROPIC_AUTH_TOKEN`,
|
|
81
|
+
`ANTHROPIC_BASE_URL`, `CLAUDECODE`, `CLAUDE_CODE_*`, `CLAUDE_EFFORT`,
|
|
82
|
+
`CLAUDE_PLUGIN_DATA` deleted; `CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS=0` set
|
|
83
|
+
(source: src/env.mjs; the "another auth source" check above, observed-live).
|
|
84
|
+
- Limit signals: see § Limit signals (phase 4).
|
|
85
|
+
|
|
86
|
+
## codex
|
|
87
|
+
|
|
88
|
+
- Version codex-cli 0.153.4 (source: `codex --version`).
|
|
89
|
+
- Binary: npm package `@openai/codex` whose `bin/codex.js` only spawns the
|
|
90
|
+
platform package's native exe. Leg spawns that exe directly:
|
|
91
|
+
`~/AppData/Roaming/npm/node_modules/@openai/codex/node_modules/@openai/codex-win32-x64/vendor/x86_64-pc-windows-msvc/bin/codex.exe`
|
|
92
|
+
(one process to kill); falls back to `node <bin/codex.js>` via
|
|
93
|
+
`resolveNpmCliEntry`, then `codex` on PATH; `LEG_CODEX_BIN` overrides
|
|
94
|
+
(source: `bin/codex.js` read 2026-09-10; fixtures/live/codex/cmd.txt).
|
|
95
|
+
- Headless argv (exact, from cmd.txt):
|
|
96
|
+
`codex.exe exec --json -s workspace-write -C <worktree> -c sandbox_workspace_write.network_access=false -o <run>/last.md "<prompt>"`.
|
|
97
|
+
Prompt is the last positional argument; **stdin is `ignore`**: `codex exec`
|
|
98
|
+
reads stdin whenever it is not a TTY and hangs on an open pipe ("Reading
|
|
99
|
+
additional input from stdin..."; plan-time probe hung 170 s with a pipe,
|
|
100
|
+
finished in 20 s with stdin closed; observed-live). `-o` writes the final
|
|
101
|
+
agent message to a file (observed-live: fixtures/live/codex/last.md).
|
|
102
|
+
- Output: JSONL events on stdout (source: fixtures/live/codex/out.log):
|
|
103
|
+
`thread.started{thread_id}`, `turn.started`, `item.started/item.completed{item{type:
|
|
104
|
+
agent_message|command_execution|error, text|command|message}}`,
|
|
105
|
+
`turn.completed{usage{input_tokens,cached_input_tokens,output_tokens,reasoning_output_tokens}}`.
|
|
106
|
+
An `item.type:"error"` is a non-fatal notice (observed: "Skill descriptions
|
|
107
|
+
were shortened…"). Leg reads `thread_id` as the session id, the last
|
|
108
|
+
`agent_message` text, `turn.completed` as the stop reason, and the error
|
|
109
|
+
messages.
|
|
110
|
+
- Exit codes:
|
|
111
|
+
|
|
112
|
+
| exit | meaning | source |
|
|
113
|
+
|------|---------|--------|
|
|
114
|
+
| 0 | turn completed | observed-live, fixtures/live/codex/run.json |
|
|
115
|
+
| (none) | hangs with an open stdin pipe until it is killed | observed at plan time; no artifact kept, the behaviour is pinned by test/lessons.test.mjs `codex-stdin-ignored` |
|
|
116
|
+
| non-zero | launch/auth/fatal | docs-only |
|
|
117
|
+
|
|
118
|
+
- Sandbox modes (`-s`): `read-only`, `workspace-write`, `danger-full-access`
|
|
119
|
+
(source: `codex exec --help`). Leg: default `workspace-write`; allowed
|
|
120
|
+
`read-only`, `workspace-write`; forbidden `danger-full-access`,
|
|
121
|
+
`--dangerously-bypass-approvals-and-sandbox`, `--dangerously-bypass-hook-trust`,
|
|
122
|
+
`--approve-for-me` (routes approvals through automatic review), `--full-auto`
|
|
123
|
+
(not present in 0.153.4 `exec --help`; kept on the forbidden list). Blast
|
|
124
|
+
radius is `-C <worktree>`; network off unless the chain entry sets
|
|
125
|
+
`network: true` (PROTOCOL sandbox ladder). `workspace-write` allowed the
|
|
126
|
+
write headless (observed-live).
|
|
127
|
+
- Resume: `codex exec resume [SESSION_ID] [PROMPT]` with `--last` for the newest
|
|
128
|
+
(source: `codex exec resume --help`). Leg's adapter emits
|
|
129
|
+
`exec … resume <thread_id> <prompt>`; docs-only until phase 5 exercises it.
|
|
130
|
+
- Limit signals: see § Limit signals (phase 4).
|
|
131
|
+
|
|
132
|
+
## agy
|
|
133
|
+
|
|
134
|
+
- Version 1.2.0 (source: `agy --version`).
|
|
135
|
+
- Binary: `~/AppData/Local/agy/bin/agy.exe` (native); `LEG_AGY_BIN` overrides
|
|
136
|
+
(source: `where agy`; src/adapters/agy.mjs).
|
|
137
|
+
- Headless argv (exact, from cmd.txt):
|
|
138
|
+
`agy.exe -p "<working-directory preamble + prompt>" --output-format json --mode accept-edits --add-dir <worktree> --print-timeout 90m`;
|
|
139
|
+
stdin `ignore`.
|
|
140
|
+
- **agy does not work in the process cwd.** Attempt 1 (no `--add-dir`) exited
|
|
141
|
+
0 and wrote `hello-agy.txt` under `~/.gemini/antigravity-cli/scratch/`
|
|
142
|
+
(observed-live, fixtures/live/agy/attempt-1-scratch-workspace.out.log).
|
|
143
|
+
Attempt 2 adds `--add-dir <worktree>` and prefixes the prompt with
|
|
144
|
+
"Working directory: <worktree>"; it wrote both files in the repo
|
|
145
|
+
(observed-live).
|
|
146
|
+
- `--print-timeout` defaults to 5m0s; Leg sets it from the kill timer in Go
|
|
147
|
+
duration syntax so the supervisor decides what a runaway is (source: `agy --help`).
|
|
148
|
+
- Output: one JSON object: `conversation_id`, `status:"SUCCESS"`, `response`,
|
|
149
|
+
`duration_seconds`, `num_turns`, `usage{input_tokens,output_tokens,thinking_tokens,cache_read_tokens,total_tokens}`
|
|
150
|
+
(observed-live, fixtures/live/agy/out.log). Leg reads `conversation_id` as
|
|
151
|
+
the session id, `response`, `status`.
|
|
152
|
+
- Exit codes:
|
|
153
|
+
|
|
154
|
+
| exit | meaning | source |
|
|
155
|
+
|------|---------|--------|
|
|
156
|
+
| 0 | `status: SUCCESS` | observed-live (both attempts; attempt 1 was a success in the wrong directory) |
|
|
157
|
+
| non-zero | fatal/auth | docs-only |
|
|
158
|
+
|
|
159
|
+
- Modes (`--mode`): `accept-edits`, `plan` (source: `agy --help`). Leg: default
|
|
160
|
+
`accept-edits`; forbidden `--dangerously-skip-permissions`. `accept-edits`
|
|
161
|
+
allowed the write headless (observed-live).
|
|
162
|
+
- Resume: `--conversation <id>` (by id) or `--continue` (most recent) (source:
|
|
163
|
+
`agy --help`; docs-only until phase 5).
|
|
164
|
+
- Limit signals: see § Limit signals (phase 4).
|
|
165
|
+
|
|
166
|
+
## grok
|
|
167
|
+
|
|
168
|
+
- Version grok 0.2.51 (f4f85a649) [stable] (source: `grok --version`).
|
|
169
|
+
- Binary: `~/.grok/bin/grok.exe` (native; also an npm shim on PATH);
|
|
170
|
+
`LEG_GROK_BIN` overrides (source: `where grok`).
|
|
171
|
+
- Headless argv (from cmd.txt): `grok.exe -p "<prompt>" --output-format json --permission-mode acceptEdits`; stdin `ignore`.
|
|
172
|
+
`--prompt-file <path>` also exists (source: `grok --help`).
|
|
173
|
+
- Output: one JSON object `{text, stopReason, sessionId, requestId, thought}`
|
|
174
|
+
(observed-live, fixtures/live/grok/out.log).
|
|
175
|
+
- Exit codes:
|
|
176
|
+
|
|
177
|
+
| exit | meaning | source |
|
|
178
|
+
|------|---------|--------|
|
|
179
|
+
| 0 | printed JSON with `stopReason:"Cancelled"` and did no work: the CLI was not logged in, printed a device-code prompt on stderr (`https://accounts.x.ai/oauth2/device?user_code=…`, "Waiting for authorization...") and gave up after ~58 s | observed-live, fixtures/live/grok/err.log |
|
|
180
|
+
|
|
181
|
+
A zero exit with no DONE marker and no diff is exactly the `no_progress` class
|
|
182
|
+
the completion contract exists for.
|
|
183
|
+
- Permission modes: `default`, `acceptEdits`, `auto`, `dontAsk`,
|
|
184
|
+
`bypassPermissions`, `plan` (source: `grok --help`). Leg: default
|
|
185
|
+
`acceptEdits`; forbidden `bypassPermissions`, `--always-approve`.
|
|
186
|
+
- Login: `grok login` (source: `grok --help` Commands).
|
|
187
|
+
- **Verdict: not verified, no adapter registered.** `src/adapters/grok.mjs` is
|
|
188
|
+
built from `--help` and unit-tested for shape and forbidden flags, but stays
|
|
189
|
+
out of `src/adapters/index.mjs` until `grok login` has been completed on the
|
|
190
|
+
machine and `node scripts/probe.mjs --adapter grok --repo <toy>` passes.
|
|
191
|
+
|
|
192
|
+
## Interactive taps
|
|
193
|
+
|
|
194
|
+
What `leg claude|codex|agy` reads while the real interactive CLI runs. Same
|
|
195
|
+
tagging rule: `observed-live 2026-09-11` means the build machine did it;
|
|
196
|
+
`docs-only` means the CLI's own source or documentation says so and Leg has
|
|
197
|
+
not seen it happen. Machine: Claude Code 2.1.268, codex-cli 0.153.4, agy 1.2.0.
|
|
198
|
+
|
|
199
|
+
Shared: the agent is spawned with stdio inherited and the user's arguments
|
|
200
|
+
passed through (`src/attach.mjs` `spawnSpec`); the child environment is
|
|
201
|
+
`sanitizeEnv(process.env, { interactive: true })` plus the account's config-dir
|
|
202
|
+
variable and `LEG_SESSION` (source: src/attach.mjs, src/env.mjs).
|
|
203
|
+
|
|
204
|
+
### claude tap
|
|
205
|
+
|
|
206
|
+
- Attach: `claude <args> --settings <LEG_HOME>/sessions/<id>/claude-settings.json`
|
|
207
|
+
(source: src/attach.mjs `spawnSpec`; src/taps/claude.mjs `writeSettings`).
|
|
208
|
+
Hooks in a `--settings` file merge with the user's rather than replacing
|
|
209
|
+
them; `statusLine` is the one key that replaces, so Leg runs the user's own
|
|
210
|
+
command first (source: code.claude.com/docs/en/settings;
|
|
211
|
+
src/taps/claude.mjs `userStatusLine`). observed-live 2026-09-11: a Leg
|
|
212
|
+
session ran with every user hook still firing.
|
|
213
|
+
- Hooks written (source: src/taps/claude.mjs `settingsFor`): `SessionStart`,
|
|
214
|
+
`UserPromptSubmit`, `PostToolUse` with matcher `Edit|Write|MultiEdit|NotebookEdit`,
|
|
215
|
+
`Stop`, `StopFailure`, `SessionEnd`, each
|
|
216
|
+
`node <src>/hook.mjs claude-hook --session <id>` with a 20 s timeout.
|
|
217
|
+
observed-live 2026-09-11 (hook.log in the session directory).
|
|
218
|
+
- `autoContinueAtUsageLimit: false` in the same file, because Leg owns the
|
|
219
|
+
hand-off (source: src/taps/claude.mjs `settingsFor`).
|
|
220
|
+
- Usage: `GET https://api.anthropic.com/api/oauth/usage`
|
|
221
|
+
(`LEG_CLAUDE_USAGE_URL` overrides) with `Authorization: Bearer <accessToken>`
|
|
222
|
+
from `<CLAUDE_CONFIG_DIR>/.credentials.json` key `claudeAiOauth`, and header
|
|
223
|
+
`anthropic-beta: oauth-2025-04-20`; response fields `five_hour` and
|
|
224
|
+
`seven_day`, each `{ utilization, resets_at }`; polled every
|
|
225
|
+
`LEG_USAGE_POLL_MS` ms, default 60000 (source: src/taps/claude-usage.mjs).
|
|
226
|
+
observed-live 2026-09-11: real windows came back and were written to
|
|
227
|
+
`<LEG_HOME>/usage/claude--default.json`; a seven_day window at 93 %
|
|
228
|
+
raised the amber warning.
|
|
229
|
+
- The wall: `StopFailure` hook with `error: rate_limit` (source:
|
|
230
|
+
https://code.claude.com/docs/en/hooks#stopfailure).
|
|
231
|
+
**observed-live 2026-09-11** <!-- live:claude/rate_limit -->: a real
|
|
232
|
+
`StopFailure` with `error: rate_limit` (a 429 `rate_limit_error`) arrived
|
|
233
|
+
at 07:46:37Z and is kept, scrubbed, as
|
|
234
|
+
`fixtures/live/claude/limit-rate_limit.json` (src/live-capture.mjs). The
|
|
235
|
+
path can also be run live with `leg sessions simulate-limit` (the same
|
|
236
|
+
payload through `src/hook.mjs`, marked `leg_simulated`, never kept as
|
|
237
|
+
evidence).
|
|
238
|
+
- Status line, not usable on 2.1.268: a custom `statusLine` command passed
|
|
239
|
+
through `--settings`, and again through a project
|
|
240
|
+
`.claude/settings.local.json`, was not run at all when it was tried; an
|
|
241
|
+
`echo` command at both levels left the built-in status line in place while
|
|
242
|
+
hooks from the same `--settings` file fired (no artifact kept; note in the
|
|
243
|
+
src/taps/claude-usage.mjs header, 2026-09-11). Leg still writes the
|
|
244
|
+
`statusLine` entry, which records the
|
|
245
|
+
same `rate_limits.five_hour` / `seven_day` fields
|
|
246
|
+
(https://code.claude.com/docs/en/statusline), so the endpoint poll becomes a
|
|
247
|
+
fallback if a later build honours it.
|
|
248
|
+
|
|
249
|
+
### codex tap
|
|
250
|
+
|
|
251
|
+
- Attach: nothing injected. `codex <args>` runs as it always does, and Leg
|
|
252
|
+
finds the session's rollout file afterwards (source: src/attach.mjs
|
|
253
|
+
`spawnSpec`). Reason: injecting a hook makes codex show its hooks-review
|
|
254
|
+
prompt on every Leg session.
|
|
255
|
+
- Which file: `<CODEX_HOME>/sessions/YYYY/MM/DD/rollout-*.jsonl`, matched on
|
|
256
|
+
`session_meta.payload.cwd` equal to the session's cwd with a birth time or
|
|
257
|
+
mtime at or after the spawn time, five seconds of slack (source:
|
|
258
|
+
src/taps/codex.mjs `findRollout`). observed-live 2026-09-11: the right
|
|
259
|
+
rollout was found for a real hand-off. The file's mtime is not a content
|
|
260
|
+
clock on Windows, six rollouts from that day had mtimes 7 s to 9 min
|
|
261
|
+
behind their last line, so nothing depends on the two matching.
|
|
262
|
+
- Usage: read-only app-server `account/rateLimits/read`, polled every 60
|
|
263
|
+
seconds by the board and active attach. Leg maps windows by duration,
|
|
264
|
+
including 300 minutes to 5h and 10080 minutes to 7d, rather than assuming
|
|
265
|
+
`primary` or `secondary` names. It sends no model turn and does not hardcode
|
|
266
|
+
a quota; only explicit backend availability clears a previous wall (sources:
|
|
267
|
+
`src/taps/codex.mjs`, `src/attach.mjs`, `src/server.mjs`, `src/usage.mjs`).
|
|
268
|
+
- The wall: `event_msg.task_complete.error` with
|
|
269
|
+
`codex_error_info: "usage_limit_exceeded"` and message "You've hit your usage
|
|
270
|
+
limit … try again at \<date>" (source:
|
|
271
|
+
github.com/openai/codex `codex-rs/protocol/src/error.rs`
|
|
272
|
+
`UsageLimitReachedError`). The event shape was read from local rollouts;
|
|
273
|
+
the error itself is **observed-live 2026-09-15** <!-- live:codex/usage_limit_exceeded -->.
|
|
274
|
+
A `task_complete.error` with `codex_error_info: usage_limit_exceeded` walled
|
|
275
|
+
a codex leg once, but no scrubbed payload was kept, so
|
|
276
|
+
`fixtures/live/codex/limit-usage_limit_exceeded.json` is still the slot for
|
|
277
|
+
the first one.
|
|
278
|
+
- Transcript: `response_item.message` with `role: user` and
|
|
279
|
+
`content[].type: input_text` for prompts; `output_text` and
|
|
280
|
+
`task_complete.last_agent_message` for assistant text (source:
|
|
281
|
+
src/taps/codex.mjs; observed-live 2026-09-11).
|
|
282
|
+
- Edited files: the `*** Add File:` / `*** Update File:` / `*** Delete File:`
|
|
283
|
+
lines of an `apply_patch` payload (source: src/taps/codex.mjs; observed-live
|
|
284
|
+
2026-09-11).
|
|
285
|
+
|
|
286
|
+
### agy tap
|
|
287
|
+
|
|
288
|
+
- Attach: `agy <args> --log-file <LEG_HOME>/sessions/<id>/agy.log` (source:
|
|
289
|
+
src/attach.mjs `spawnSpec`). agy 1.2.0 is a closed Go binary with no hook
|
|
290
|
+
surface.
|
|
291
|
+
- Usage: none. No percentage is written to any file agy owns; its own status
|
|
292
|
+
line fetches a quota summary from the backend and stores nothing (source:
|
|
293
|
+
src/taps/agy.mjs header).
|
|
294
|
+
- The wall: `RESOURCE_EXHAUSTED`, "it resets in %s", "out of quota", and
|
|
295
|
+
"quota exhausted/exceeded" in the log, plus a relative reset parsed out of
|
|
296
|
+
"resets in \<n>\<s|m|h|d>" (source: strings present in `agy.exe`;
|
|
297
|
+
src/taps/agy.mjs `scanLog`).
|
|
298
|
+
**docs-only** <!-- live:agy/agy-resource-exhausted -->:
|
|
299
|
+
`RESOURCE_EXHAUSTED (code 429): Individual quota reached … Resets in
|
|
300
|
+
71h19m42s.` appeared in a session's `agy.log` at 08:02:42Z, `scanLog` read
|
|
301
|
+
the relative reset, and the agent was walled. No payload was kept, the
|
|
302
|
+
capture call in `src/attach.mjs` was added while that session's runner was
|
|
303
|
+
already running, so `fixtures/live/agy/limit-agy-resource-exhausted.json`
|
|
304
|
+
is still the slot for the next one.
|
|
305
|
+
- Prompts and conversation id: `~/.gemini/antigravity-cli/history.jsonl`, one
|
|
306
|
+
`{ display, timestamp, workspace, conversationId }` per prompt (source:
|
|
307
|
+
src/taps/agy.mjs `historyFile`; shape observed on disk, last written
|
|
308
|
+
2026-09-09, the 2026-09-11 agy leg produced no history entry).
|
|
309
|
+
- One account only: agy 1.2.0 has no config-directory override, so
|
|
310
|
+
`LAYOUT.agy.env` is `null` and `accounts add agy` is refused (source:
|
|
311
|
+
src/accounts.mjs).
|
|
312
|
+
|
|
313
|
+
### Usage store and the chooser
|
|
314
|
+
|
|
315
|
+
- `<LEG_HOME>/usage/<agent>--<account>.json`:
|
|
316
|
+
`{ five_hour: {pct, resets_at}, seven_day: {…}, limited_until,
|
|
317
|
+
limited_reason, source, updated_at }` (source: src/usage.mjs).
|
|
318
|
+
- Warning threshold `WARN_PCT`, default 85, from `LEG_WARN_PCT`; the warning
|
|
319
|
+
is an amber card, an event, and one terminal bell (source: src/usage.mjs,
|
|
320
|
+
src/attach.mjs).
|
|
321
|
+
- `markLimited()` takes the reset the CLI reported; failing that the soonest
|
|
322
|
+
known window reset; failing that now + 5 h (source: src/usage.mjs
|
|
323
|
+
`DEFAULT_LIMIT_S`).
|
|
324
|
+
- `candidates()` yields the other accounts of the same agent first, then the
|
|
325
|
+
remaining agents in the order claude, codex, agy, wrapping, so every option
|
|
326
|
+
is tried once. `chooseNext()` skips any option still walled and returns the
|
|
327
|
+
walled ones sorted by reset (source: src/usage.mjs).
|
|
328
|
+
|
|
329
|
+
### Session store
|
|
330
|
+
|
|
331
|
+
`<LEG_HOME>/sessions/<id>/` holds `session.json`, `events.jsonl`,
|
|
332
|
+
`hook.log`, and, per agent, `claude-settings.json` or `agy.log`;
|
|
333
|
+
`control.json` appears only once the board has asked for a hand-off or an
|
|
334
|
+
end (source: src/sessions.mjs header, src/attach.mjs). Statuses and event
|
|
335
|
+
types are listed
|
|
336
|
+
in [VOCABULARY.md](VOCABULARY.md). Board routes: `GET /api/sessions`,
|
|
337
|
+
`GET /api/sessions/:id`, `POST /api/sessions/:id/handoff`,
|
|
338
|
+
`POST /api/sessions/:id/end`, `DELETE /api/sessions/:id`, with the list pushed
|
|
339
|
+
as the SSE `sessions` event (source: src/server.mjs, src/board/sessions.js).
|
|
340
|
+
|
|
341
|
+
## Limit signals
|
|
342
|
+
|
|
343
|
+
Recorded in `fixtures/limits/<group>/<id>.json` and classified by
|
|
344
|
+
`src/limits.mjs` (`classify()`); the runner writes the outcome into
|
|
345
|
+
`run.json.outcome` with the matched `signal`. Every `limit` row below is
|
|
346
|
+
still **docs-only**: its text comes from the CLI's own documentation or
|
|
347
|
+
source, cited in "produced by". Real limits from all three agents were seen
|
|
348
|
+
on 2026-09-11, but only the claude payload was kept as evidence
|
|
349
|
+
(`fixtures/live/claude/limit-rate_limit.json`); the codex and agy ones went
|
|
350
|
+
to the session ledger, not to `fixtures/limits/`. The observed-live rows are
|
|
351
|
+
the non-limit cases the probes actually hit and the detector must never
|
|
352
|
+
mistake for a limit.
|
|
353
|
+
|
|
354
|
+
Outcome precedence (`src/limits.mjs`): spawn error → `launch_failed`; stderr
|
|
355
|
+
"another auth source is set" or an `auth` fixture → `auth_failed` (wins over
|
|
356
|
+
any limit text, even with exit 0); killed from the board → `killed`; kill timer
|
|
357
|
+
→ `stalled`; exit 0 + `.leg/DONE` → `completed`; adapter-specific then
|
|
358
|
+
generic `limit` fixture → `limit`; `launch` fixture → `launch_failed`; exit 0 +
|
|
359
|
+
changes, no DONE → `incomplete`; exit 0, nothing changed → `no_progress`;
|
|
360
|
+
non-zero exit → `failed`. Every outcome except `completed`, `auth_failed` and
|
|
361
|
+
`killed` asks the chain to hand off.
|
|
362
|
+
|
|
363
|
+
<!-- limits-table:start -->
|
|
364
|
+
Generated by `node scripts/limits-table.mjs` from 21 fixtures (4 observed-live, 17 docs-only). Classification `limit` hands the card to the next agent as a usage limit; `auth` is a failed launch (never a limit); `launch` is a failed launch that the next agent may still try; `budget` is a turn or spend cap set by Leg itself; `info` must never classify as a limit.
|
|
365
|
+
|
|
366
|
+
| id | adapter | class | where | source | text (excerpt) | produced by |
|
|
367
|
+
|----|---------|-------|-------|--------|----------------|-------------|
|
|
368
|
+
| agy-resource-exhausted | agy | limit | any | **docs-only** | resource-exhausted | `agy changelog` ("Fixed personal accounts hitting a resource-exhausted error at startup"); agy --help documents no limit wording; falls back to the generic matchers |
|
|
369
|
+
| claude-budget-limit | claude | budget | any | **docs-only** | Budget limit reached | https://code.claude.com/docs/en/cli-reference (--max-budget-usd) |
|
|
370
|
+
| claude-max-turns | claude | budget | stdout | **observed-live** | {"type":"result","subtype":"error_max_turns","is_error":true,"stop_reason":"tool_use","ter | fixtures/real-run/leg1/out.log: `claude -p --max-turns 2` on the real run, 2026-09-10 (docs: https://code.claude.com/docs/en/cli-reference --max-turns "Exits with an error when the limit is reached") |
|
|
371
|
+
| claude-model-limit | claude | limit | any | **docs-only** | You’ve hit your Opus limit | https://code.claude.com/docs/en/costs ("You’ve hit your Opus limit" / "You’ve hit your Sonnet limit") |
|
|
372
|
+
| claude-session-limit | claude | limit | any | **docs-only** | You’ve hit your session limit | https://code.claude.com/docs/en/costs (section: When a developer asks about a limit) |
|
|
373
|
+
| claude-weekly-limit | claude | limit | any | **docs-only** | You’ve hit your weekly limit | https://code.claude.com/docs/en/costs (section: When a developer asks about a limit) |
|
|
374
|
+
| codex-quota-exceeded | codex | limit | any | **docs-only** | quota exceeded | github.com/openai/codex codex-rs/response-debug-context/src/lib.rs (ApiError::QuotaExceeded => "quota exceeded") |
|
|
375
|
+
| codex-rate-limit-exceeded | codex | limit | any | **docs-only** | rate limit exceeded: | github.com/openai/codex codex-rs/codex-api/src/error.rs (#[error("rate limit exceeded: {message}")]) |
|
|
376
|
+
| codex-skills-notice | codex | info | stdout | **observed-live** | Skill descriptions were shortened to fit the skills context budget. Codex can still see ev | fixtures/live/codex/out.log (item.completed, item.type=error) from scripts/probe.mjs --adapter codex, 2026-09-10 |
|
|
377
|
+
| codex-usage-limit-reached | codex | limit | any | **docs-only** | Usage limit reached. You've reached your usage limit. Increase your limits to continue | github.com/openai/codex codex-rs/tui/src/chatwidget/turn_runtime.rs (WorkspaceOwnerUsageLimitReached) |
|
|
378
|
+
| codex-usage-limit | codex | limit | any | **docs-only** | You’ve hit your usage limit for {limit_name}. Switch to another model now, | github.com/openai/codex codex-rs/protocol/src/error.rs (gh search code "usage limit" --repo openai/codex, 2026-09-10) |
|
|
379
|
+
| generic-429 | * | limit | any | **docs-only** | 429 Too Many Requests | generic HTTP matcher (429 Too Many Requests); lowest priority |
|
|
380
|
+
| generic-overloaded | * | limit | any | **docs-only** | overloaded_error | generic matcher (Anthropic API 529 overloaded_error); lowest priority |
|
|
381
|
+
| generic-quota | * | limit | any | **docs-only** | quota | generic matcher; lowest priority |
|
|
382
|
+
| generic-rate-limit | * | limit | any | **docs-only** | rate limit | generic matcher; lowest priority |
|
|
383
|
+
| generic-resource-exhausted | * | limit | any | **docs-only** | RESOURCE_EXHAUSTED | generic matcher (gRPC RESOURCE_EXHAUSTED); lowest priority |
|
|
384
|
+
| generic-usage-limit | * | limit | any | **docs-only** | usage limit | generic matcher; lowest priority |
|
|
385
|
+
| grok-not-logged-in | grok | auth | stderr | **observed-live** | To sign in, open this URL in your browser: https://accounts.x.ai/oauth2/device?user_cod | fixtures/live/grok/err.log from scripts/probe.mjs --adapter grok, 2026-09-10 (stdout JSON stopReason: Cancelled, exit 0) |
|
|
386
|
+
| auth-source-set | * | auth | stderr | **docs-only** | another auth source is set | project brief (Wes, 2026-09-10): stderr saying "another auth source is set" counts as a failed launch; wording not yet observed live |
|
|
387
|
+
| compile-error | * | info | stderr | **docs-only** | SyntaxError: Unexpected token ) at compileSourceTextModule (node:internal/modules/esm/ | synthetic negative fixture (a crashed agent is not a limit) |
|
|
388
|
+
| empty-stdout-exit-0 | * | info | stdout | **observed-live** | | fixtures/live/grok (exit 0, no work): silence is not a limit |
|
|
389
|
+
<!-- limits-table:end -->
|
|
390
|
+
|
|
391
|
+
## Runner facts that apply to every CLI (observed-live in phase 2/3 tests)
|
|
392
|
+
|
|
393
|
+
- The supervisor is detached (`detached: true, windowsHide: true`) and survives
|
|
394
|
+
the launcher; `run.json` moves `launching → running → exited|killed|failed`.
|
|
395
|
+
- Kill is `taskkill /PID <pid> /T /F` on Windows with one verify-retry
|
|
396
|
+
(`LEG_TIMERS_MS=notify,kill,verify`, default 30 min / 90 min / 30 s).
|
|
397
|
+
- No adapter ever sets `shell: true`; every spawn is `spawn(bin, argv)` with a
|
|
398
|
+
native exe or `node <entry.js>` (test/lessons.test.mjs `no-shell-spawn`).
|
|
399
|
+
- A chain entry naming a forbidden mode or flag makes `argv()` throw
|
|
400
|
+
`forbidden flag: …`; the supervisor records the refusal and exits 13 without
|
|
401
|
+
spawning (test/adapters.test.mjs, test/runner.test.mjs).
|