@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,55 @@
|
|
|
1
|
+
# Roadmap v2: the software factory
|
|
2
|
+
|
|
3
|
+
**0.3.0 (2026-09-11) shipped item 1 and the first half of continuous landing.**
|
|
4
|
+
A second live session in one checkout gets its own worktree and branch, and
|
|
5
|
+
**Land** on its card sends that branch through the merge queue (rebase, tests,
|
|
6
|
+
fast-forward, or a bounce with the reason), with the landed-on-trunk list
|
|
7
|
+
naming the terminal that landed each commit. `leg share` puts more than one
|
|
8
|
+
human on the board, off by default: a token and a name per human, per-human
|
|
9
|
+
actor ids on every event, another human's terminal read-only with everything it
|
|
10
|
+
said or touched left out, a **Request handoff** the owner approves, and rate
|
|
11
|
+
limits on the API. Still open from item 1: TLS (there is none, so the board
|
|
12
|
+
belongs on Tailscale or a trusted LAN), token scopes finer than owner and
|
|
13
|
+
guest, and an audit view.
|
|
14
|
+
|
|
15
|
+
**0.2.0 (2026-09-11) shipped the terminal-first entry**: `leg claude|codex|agy`
|
|
16
|
+
runs the real interactive agent with a board, usage tracking and a hand-off
|
|
17
|
+
alongside it. Against the list below, it delivers the hand-off-on-demand half
|
|
18
|
+
of the "hand off now to \<adapter>" item (a button on every terminal card,
|
|
19
|
+
which takes the next option in the chain; the picker is still open), and it
|
|
20
|
+
puts the machine's terminals on one board, which is the surface item 1's
|
|
21
|
+
multi-human work was built on.
|
|
22
|
+
|
|
23
|
+
v1 ships one wedge: a card runs a fallback chain of coding-agent CLIs, and when
|
|
24
|
+
a leg hits its usage limit the next agent resumes from a handoff bundle in the
|
|
25
|
+
same worktree. Where it is headed (Wes, 2026-09-10, after @mikehostetler's
|
|
26
|
+
"massive multiplayer software factory"): many cards, many agents and several
|
|
27
|
+
humans working one repo at once; stations handing work to each other
|
|
28
|
+
continuously; trunk moving in small landed pieces all day; every human judgment
|
|
29
|
+
a button on a shared floor. Not live co-editing of the same files: that is a
|
|
30
|
+
conflict machine with agents. The v1 shapes already point there: stations not
|
|
31
|
+
fixed columns, a scheduler with path leases, a merge queue, a ledger where every
|
|
32
|
+
event names its actor, a board that reads only the ledger, and an auth/bind seam.
|
|
33
|
+
|
|
34
|
+
## Next
|
|
35
|
+
|
|
36
|
+
1. **Multi-human network access**, shipped in 0.3.0 as `leg share` (a token
|
|
37
|
+
and a name per human, per-human actor ids on every event, rate limits, a
|
|
38
|
+
guest's read-only redacted board). What is left: TLS termination, token
|
|
39
|
+
scopes finer than owner and guest, and an audit view of who did what across
|
|
40
|
+
sessions.
|
|
41
|
+
2. **Review station with human reviewers**: a `human` station kind that shows
|
|
42
|
+
the diff, the bundle and the test tail, with Approve / Request changes /
|
|
43
|
+
Reassign as buttons; reviewer identity from the token.
|
|
44
|
+
3. **`pr` land mode live**: `gh pr create` argv is built and stub-tested today;
|
|
45
|
+
run it for real behind an explicit per-card opt-in and a remote allowlist.
|
|
46
|
+
4. **More adapters**: grok (adapter written, unregistered until `grok login` and
|
|
47
|
+
a passing probe on the machine) and muse only if a real CLI is verified; a
|
|
48
|
+
generic "argv + JSON result" adapter for anything else.
|
|
49
|
+
5. **OpenClaw Workboard mirror** once the bundled plugin is allowed
|
|
50
|
+
(`plugins.allow`): the verb table in `src/sync/workboard.mjs` is the only
|
|
51
|
+
thing to check against `openclaw workboard --help`.
|
|
52
|
+
|
|
53
|
+
Also on the list: per-station prompt templates editable from the board, a
|
|
54
|
+
"hand off now to <adapter>" picker, lease suggestions from the diff of the
|
|
55
|
+
previous leg, and a floor view that shows lease contention over time.
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
# Vocabulary
|
|
2
|
+
|
|
3
|
+
One page of the words Leg uses for state, events and actions, pulled
|
|
4
|
+
straight from the source (not invented). Each table lists the exact
|
|
5
|
+
identifiers the code uses; UI copy is allowed to be more readable ("Hand off
|
|
6
|
+
now" for `handoff_now`) but must not use a different word for the same thing.
|
|
7
|
+
Regenerate this by re-running the greps in each section header if the source
|
|
8
|
+
changes.
|
|
9
|
+
|
|
10
|
+
## Session statuses
|
|
11
|
+
|
|
12
|
+
One interactive terminal under `leg claude|codex|agy`. Source:
|
|
13
|
+
`src/sessions.mjs` `SESSION_STATUSES`; board labels from `STATUS` in
|
|
14
|
+
`src/board/sessions.js`.
|
|
15
|
+
|
|
16
|
+
| status | board label | active? | meaning |
|
|
17
|
+
|--------|-------------|---------|---------|
|
|
18
|
+
| `starting` | starting | yes | the runner registered the session; the agent has not reported in yet |
|
|
19
|
+
| `running` | running | yes | the agent is up and taking turns |
|
|
20
|
+
| `warning` | near limit | yes | a usage window crossed `LEG_WARN_PCT` (default 85) |
|
|
21
|
+
| `limit` | limit hit | yes | the agent reported its usage limit; the account is walled |
|
|
22
|
+
| `handing_off` | handing off | yes | the bundle is being saved and the next option chosen |
|
|
23
|
+
| `waiting` | waiting for reset | yes | every option is walled; the terminal counts down to the first reset (`session.waiting`) and then starts that agent from the bundle |
|
|
24
|
+
| `handed_off` | handed off | no | this leg is done; the next agent owns the terminal |
|
|
25
|
+
| `ended` | ended | no | the agent exited, or End was pressed |
|
|
26
|
+
| `lost` | lost | no | the runner process that owned the terminal is gone; never shown as live |
|
|
27
|
+
|
|
28
|
+
The six active statuses are what the board counts as a live session for
|
|
29
|
+
overlap flags and for the accounts strip's live dot.
|
|
30
|
+
|
|
31
|
+
## Session event types
|
|
32
|
+
|
|
33
|
+
Source: the `appendEvent`/`updateSession` call sites in `src/attach.mjs`,
|
|
34
|
+
`src/sessions.mjs`, `src/taps/claude.mjs` and `src/server.mjs`; written to
|
|
35
|
+
`$LEG_HOME/sessions/<id>/events.jsonl`.
|
|
36
|
+
|
|
37
|
+
| type | meaning |
|
|
38
|
+
|------|---------|
|
|
39
|
+
| `started` | the session was created: agent, account and directory |
|
|
40
|
+
| `leg` | one agent is starting in this terminal, noting whether it starts from a handoff bundle |
|
|
41
|
+
| `agent_ready` | the agent reported its own session id (claude's `SessionStart` hook, codex's rollout `session_meta`) |
|
|
42
|
+
| `turn` | a human prompt was submitted; carries the turn number and the first 120 characters |
|
|
43
|
+
| `turn_done` | the agent's reply for that turn, first 160 characters |
|
|
44
|
+
| `warning` | a usage window crossed the warning threshold; names the window, the percentage and the next option |
|
|
45
|
+
| `limit` | a usage limit was detected; carries the agent's own wording, `(simulated)` when `leg sessions simulate-limit` produced it |
|
|
46
|
+
| `handoff_requested` | someone pressed Hand off now, ran `leg sessions handoff`, or (on a shared board) asked for or approved a hand-off; `by` names the human (a dismissed request logs as `status` instead) |
|
|
47
|
+
| `handoff` | the switch happened: from, to, reason, bundle id |
|
|
48
|
+
| `all_out` | every option is walled; the resets are printed and the terminal waits for the first one (`ended` with "quit while waiting" if Ctrl-C or End cuts the wait short, exit 3) |
|
|
49
|
+
| `agent_exit` | the agent process exited, with its code |
|
|
50
|
+
| `ended` | the session ended |
|
|
51
|
+
| `lost` | the runner pid is gone; the session was marked `lost` |
|
|
52
|
+
| `error` | a spawn error, a tap error, a failed bundle checkpoint, or an error the agent reported |
|
|
53
|
+
| `status` | a note that does not fit another type |
|
|
54
|
+
| `worktree` | another live session was in the checkout, so this one got its own worktree: path, branch, base |
|
|
55
|
+
| `land_requested` | Land was pressed: the branch and its base |
|
|
56
|
+
| `land_warning` | the landing ran without a test command |
|
|
57
|
+
| `landed` | the base was fast-forwarded to the branch: sha range, files, who pressed Land |
|
|
58
|
+
| `bounced` | the landing stopped with a [bounce reason](#bounce-reasons-land-station); the full detail is in `body` |
|
|
59
|
+
| `land_noop` | Land found nothing on the branch beyond its base |
|
|
60
|
+
|
|
61
|
+
## Land states (terminal cards)
|
|
62
|
+
|
|
63
|
+
`$LEG_HOME/sessions/<id>/land.json`, written by the board server only
|
|
64
|
+
(`src/land.mjs`); the runner never touches it.
|
|
65
|
+
|
|
66
|
+
| state | card line | meaning |
|
|
67
|
+
|-------|-----------|---------|
|
|
68
|
+
| `landing` | landing `<branch>` onto `<base>`… | the merge queue has the branch |
|
|
69
|
+
| `landed` | ✓ landed on `<base>` · `<sha>` | the base was fast-forwarded |
|
|
70
|
+
| `noop` | nothing to land | the branch had nothing beyond its base |
|
|
71
|
+
| `bounced` | ✗ bounced (`<reason>`) | a step failed and the base is untouched |
|
|
72
|
+
| `interrupted` | the landing was cut off | the board restarted while it was landing; press Land again |
|
|
73
|
+
|
|
74
|
+
## Share roles (more than one human)
|
|
75
|
+
|
|
76
|
+
`$LEG_HOME/share.json`, written by `leg share` (`src/share.mjs`).
|
|
77
|
+
|
|
78
|
+
| role | the terminals lane | the pipeline side | their own terminals |
|
|
79
|
+
|------|--------------------|-------------------|---------------------|
|
|
80
|
+
| `owner` | every card in full | everything | control, Land, Remove, approve or dismiss a request |
|
|
81
|
+
| `guest` | their own cards in full; every other card read-only, with the prompt, paths, file names, limit text, bundle and events left out | 403 | control, Land and Remove their own; **Request handoff** on anyone else's |
|
|
82
|
+
|
|
83
|
+
A request is `{ by, at, state: pending\|approved\|dismissed }` in the session's
|
|
84
|
+
`requests.json`; approving it sends the runner `handoff` with
|
|
85
|
+
`by: "<owner> for <guest>"`.
|
|
86
|
+
|
|
87
|
+
## Card statuses
|
|
88
|
+
|
|
89
|
+
Source: `src/chain.mjs` `TERMINAL` + `NON_TERMINAL`.
|
|
90
|
+
|
|
91
|
+
| status | terminal? | meaning |
|
|
92
|
+
|--------|-----------|---------|
|
|
93
|
+
| `backlog` | no | card created, not yet queued |
|
|
94
|
+
| `queued` | no | waiting for the scheduler to start a leg under its leases |
|
|
95
|
+
| `running` | no | a leg (agent/test/land station) is executing |
|
|
96
|
+
| `handing_off` | no | the leg ended (limit/incomplete/no_progress/stalled/failed) and Leg is writing the handoff bundle |
|
|
97
|
+
| `waiting_human` | no | parked at a `human` station for a button press |
|
|
98
|
+
| `needs_approval` | no | next chain entry has `approve: true`; parked for Approve |
|
|
99
|
+
| `paused` | no | human paused it; child killed, bundle written |
|
|
100
|
+
| `done` | yes | all pipeline stations complete |
|
|
101
|
+
| `failed` | yes | chain exhausted, an environment fault, or land attempts exhausted |
|
|
102
|
+
| `killed` | yes | killed from the board |
|
|
103
|
+
|
|
104
|
+
## Leg outcomes
|
|
105
|
+
|
|
106
|
+
Source: `src/limits.mjs` `OUTCOMES` (`classify()`'s return value).
|
|
107
|
+
|
|
108
|
+
| outcome | meaning |
|
|
109
|
+
|---------|---------|
|
|
110
|
+
| `completed` | exit 0 and `.leg/DONE` present |
|
|
111
|
+
| `incomplete` | exit 0 with changes but no DONE marker |
|
|
112
|
+
| `no_progress` | exit 0, no DONE marker, no changes |
|
|
113
|
+
| `limit` | an adapter-specific or generic usage-limit signal fired |
|
|
114
|
+
| `stalled` | the kill timer fired before the leg finished |
|
|
115
|
+
| `auth_failed` | an auth signal fired, or stderr says another auth source is set; not treated as a limit |
|
|
116
|
+
| `launch_failed` | the CLI failed to spawn, or a launch signal fired |
|
|
117
|
+
| `killed` | killed from the board |
|
|
118
|
+
| `failed` | none of the above; non-zero exit with no recognized signal |
|
|
119
|
+
|
|
120
|
+
`limit`, `incomplete`, `no_progress`, `stalled` and `failed` hand off to the
|
|
121
|
+
next adapter in the chain (`HANDOFF_OUTCOMES`); `auth_failed` and
|
|
122
|
+
`launch_failed` do not advance the chain: a human fixes the environment and
|
|
123
|
+
clicks Rerun (`NO_ADVANCE_OUTCOMES`).
|
|
124
|
+
|
|
125
|
+
## Station kinds
|
|
126
|
+
|
|
127
|
+
Source: `src/pipeline.mjs` `KINDS`.
|
|
128
|
+
|
|
129
|
+
| kind | meaning |
|
|
130
|
+
|------|---------|
|
|
131
|
+
| `agent` | runs the station's chain (one or more adapters) with that station's prompt |
|
|
132
|
+
| `human` | parks the card for a button press; no automatic work |
|
|
133
|
+
| `test` | runs the repo's test command; bounces the card on red |
|
|
134
|
+
| `land` | merge queue: rebase, test, fast-forward trunk (see Bounce reasons below); at most one, must be last |
|
|
135
|
+
|
|
136
|
+
## Event types
|
|
137
|
+
|
|
138
|
+
Source: `src/ledger.mjs` `EVENT_TYPES`, written by `ledgerAppend`/`ev()` calls
|
|
139
|
+
across `src/chain.mjs`, `src/orchestrator.mjs`, `src/scheduler.mjs`,
|
|
140
|
+
`src/land.mjs` and `src/store.mjs`.
|
|
141
|
+
|
|
142
|
+
| type | meaning |
|
|
143
|
+
|------|---------|
|
|
144
|
+
| `card_created` | a card was added |
|
|
145
|
+
| `leg_started` | an adapter leg started running |
|
|
146
|
+
| `leg_progress` | progress recorded mid-leg |
|
|
147
|
+
| `leg_exited` | the adapter process exited |
|
|
148
|
+
| `limit_detected` | a limit signal fired during a leg |
|
|
149
|
+
| `handoff_written` | the handoff bundle was written for the next leg |
|
|
150
|
+
| `leg_resumed` | a leg resumed from a bundle (pause/resume, or a bounce) |
|
|
151
|
+
| `station_done` | a station finished and the card advanced |
|
|
152
|
+
| `bounced` | a test or land failure sent the card back to `build` |
|
|
153
|
+
| `landed` | the land station merged the card's work onto trunk |
|
|
154
|
+
| `land_warning` | the land station landed without running tests (no test command found) |
|
|
155
|
+
| `land_retry` | trunk moved during land; one fast-forward retry |
|
|
156
|
+
| `blocked_by` | the scheduler could not start the card because a lease is held |
|
|
157
|
+
| `scheduler_started` | the scheduler process started |
|
|
158
|
+
| `scheduler_stopped` | the scheduler process stopped |
|
|
159
|
+
| `approval_needed` | the card is parked for Approve (gated leg or a PR was opened) |
|
|
160
|
+
| `approved` | a human clicked Approve |
|
|
161
|
+
| `reassigned` | a human changed the current leg's adapter/mode |
|
|
162
|
+
| `paused` | a human clicked Pause |
|
|
163
|
+
| `resumed` | a human clicked Resume |
|
|
164
|
+
| `killed` | a human clicked Kill |
|
|
165
|
+
| `done` | the card finished all stations |
|
|
166
|
+
| `failed` | the card failed (chain exhausted, land attempts exhausted, or an environment fault) |
|
|
167
|
+
| `error` | an unexpected error (orchestrator crash, handoff bundle write failure, land station crash) |
|
|
168
|
+
| `status` | a status note that doesn't fit another type (e.g. "rerun from build leg 0") |
|
|
169
|
+
|
|
170
|
+
## Actor types
|
|
171
|
+
|
|
172
|
+
Source: `src/ledger.mjs` (`parseActor`/`actorKey`).
|
|
173
|
+
|
|
174
|
+
| type | meaning |
|
|
175
|
+
|------|---------|
|
|
176
|
+
| `agent` | an adapter CLI acting on a leg; carries `adapter` (and optionally `model`) |
|
|
177
|
+
| `human` | a person acting through the board or CLI; carries `id` |
|
|
178
|
+
| `leg` | Leg itself (scheduler, orchestrator) acting with no human or agent behind it |
|
|
179
|
+
|
|
180
|
+
## Human actions and board buttons
|
|
181
|
+
|
|
182
|
+
Source: `src/chain.mjs` `HUMAN_ACTIONS`; board labels from
|
|
183
|
+
`ACTION_LABELS` in `src/board/board.js` (~line 17).
|
|
184
|
+
|
|
185
|
+
| action | board button | what it does |
|
|
186
|
+
|--------|--------------|--------------|
|
|
187
|
+
| `pause` | Pause | stop after the current leg |
|
|
188
|
+
| `resume` | Resume | pick up where it stopped |
|
|
189
|
+
| `kill` | Kill | stop the running agent; card ends as `killed` |
|
|
190
|
+
| `reassign` | Reassign | pick the next adapter/mode from a picker instead of the chain order |
|
|
191
|
+
| `handoff_now` | Hand off now | end the current leg, write the bundle, start the next adapter |
|
|
192
|
+
| `approve` | Approve | release a leg gated with `approve: true`, or clear a `waiting_human` station |
|
|
193
|
+
| `rerun` | Rerun | start the station over from the last bundle |
|
|
194
|
+
|
|
195
|
+
The board also shows a **Run** button for `enqueue` (queues a `backlog` card;
|
|
196
|
+
API path `run`). `enqueue` moves a card via the station machine's
|
|
197
|
+
`backlog`→`queued` transition and is not in `HUMAN_ACTIONS`, since it is
|
|
198
|
+
reached through card creation/queueing rather than `humanAction()`.
|
|
199
|
+
|
|
200
|
+
## Bounce reasons (land station)
|
|
201
|
+
|
|
202
|
+
Source: `src/mergequeue.mjs` (`bounce()` call sites, documented in the
|
|
203
|
+
file's own header comment).
|
|
204
|
+
|
|
205
|
+
| reason | meaning |
|
|
206
|
+
|--------|---------|
|
|
207
|
+
| `dirty-trunk` | the repo root isn't on the trunk branch, or has uncommitted changes; root is left untouched. A terminal's Land allows local changes in the checkout and bounces this way only when the fast-forward would overwrite one, naming the files |
|
|
208
|
+
| `rebase-conflict` | rebasing the card's branch onto trunk conflicted |
|
|
209
|
+
| `rebase-failed` | git refused the rebase for a reason other than a conflict (a hook, an unusable upstream); the detail carries git's own words |
|
|
210
|
+
| `tests-red` | the land station's own test run (no test command found → falls back per `land_warning`, otherwise `test_command`/`npm test`/`pytest`) failed |
|
|
211
|
+
| `trunk-moved` | trunk moved while tests ran; one fast-forward retry also failed |
|
|
212
|
+
|
|
213
|
+
Card-level `bounce_reason` is stored as `"<reason>: <detail>"`
|
|
214
|
+
(`src/orchestrator.mjs`); the board's status chip shows the text up to the
|
|
215
|
+
first colon or opening parenthesis, truncated to 24 characters. The separate
|
|
216
|
+
pipeline `test`
|
|
217
|
+
station (kind `test`, distinct from a `land` station's own internal test
|
|
218
|
+
step) bounces with its own free-text reason ("test red (…): …"), not one of
|
|
219
|
+
the four words above, it is a different failure path through the same
|
|
220
|
+
`bounced` event type.
|
|
221
|
+
|
|
222
|
+
## Cross-check against visible strings
|
|
223
|
+
|
|
224
|
+
Checked against `src/board/board.js`, `src/board/floor.js`,
|
|
225
|
+
`src/board/index.html`, `src/board/floor.html`, `bin/leg.mjs`'s usage text
|
|
226
|
+
and `README.md`.
|
|
227
|
+
|
|
228
|
+
- Board button labels (`ACTION_LABELS`, `WAIT_LABELS`) match README's "Board
|
|
229
|
+
buttons" table word for word (Run, Approve, Pause, Resume, Hand off now,
|
|
230
|
+
Reassign, Kill, Rerun).
|
|
231
|
+
- Board status labels (`STATUS_LABELS`) and chain-rail glyphs
|
|
232
|
+
(`stateGlyph`: ✓ done, ↷ handed, ✗ failed, ● active, · pending) match the
|
|
233
|
+
README's drawer description.
|
|
234
|
+
- Station kinds shown in the drawer (`${name} (${kind})`) and README's
|
|
235
|
+
"Station kinds" list both use `agent`/`human`/`test`/`land`.
|
|
236
|
+
- CLI human-action verbs in `bin/leg.mjs` (`pause`, `resume`, `kill`,
|
|
237
|
+
`approve`, `handoff-now`, `rerun`, `reassign`) are the same 7 words as
|
|
238
|
+
`HUMAN_ACTIONS`, spelled with hyphens instead of underscores on the CLI
|
|
239
|
+
surface (`handoff-now` vs `handoff_now`), a deliberate, consistent
|
|
240
|
+
per-surface convention, not a mismatch.
|
|
241
|
+
- One mismatch found: the bounce chip splits on the first colon **or**
|
|
242
|
+
opening parenthesis (`src/board/board.js:325`), which the Bounce reasons
|
|
243
|
+
note above now states. Nothing else needed changing in `src/board/*`.
|
package/docs/adapters.md
ADDED
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
# Adapters
|
|
2
|
+
|
|
3
|
+
Two things per agent: what Leg reads from an interactive session
|
|
4
|
+
(`leg claude|codex|agy`), and the headless argv the v0.1 pipeline spawns.
|
|
5
|
+
Every fact here was written against `src/taps/*.mjs`, `src/attach.mjs` and
|
|
6
|
+
`src/adapters/*.mjs`; the evidence trail, including which lines an artifact
|
|
7
|
+
backs, is [cli-contracts.md](cli-contracts.md).
|
|
8
|
+
|
|
9
|
+
An agent is never run through a shell. Interactive sessions are
|
|
10
|
+
`spawn(bin, argv, { stdio: 'inherit' })` (`src/attach.mjs` `spawnSpec`);
|
|
11
|
+
headless legs are `spawn(spec.bin, spec.args, …)` (`src/runner.mjs`). Both
|
|
12
|
+
strip the API-key and base-URL variables and the Claude Code nested-session
|
|
13
|
+
markers from the child environment (`src/env.mjs` `sanitizeEnv`).
|
|
14
|
+
|
|
15
|
+
## What Leg reads from each agent
|
|
16
|
+
|
|
17
|
+
Nothing is screen-scraped. Each tap was read from the CLI's own source or
|
|
18
|
+
documentation, then checked on a real machine on 2026-09-11 (Claude Code
|
|
19
|
+
2.1.268, codex-cli 0.153.4, agy 1.2.0). Lines that a live run or a fixture
|
|
20
|
+
backs say observed-live; lines read only from a CLI's source or
|
|
21
|
+
documentation say docs-only.
|
|
22
|
+
|
|
23
|
+
### claude
|
|
24
|
+
|
|
25
|
+
- **How Leg attaches**: one extra settings file per session, passed as
|
|
26
|
+
`claude <your args> --settings <~/.leg/sessions/<id>/claude-settings.json>`
|
|
27
|
+
(`src/taps/claude.mjs` `settingsFor`). Hooks from `--settings` merge with
|
|
28
|
+
yours rather than replacing them. Observed live: a Leg session ran with
|
|
29
|
+
every user hook still firing.
|
|
30
|
+
- **Hooks wired**: `SessionStart`, `UserPromptSubmit`,
|
|
31
|
+
`PostToolUse` (matcher `Edit|Write|MultiEdit|NotebookEdit`), `Stop`,
|
|
32
|
+
`StopFailure`, `SessionEnd`, each running
|
|
33
|
+
`node src/hook.mjs claude-hook --session <id>`. Observed live.
|
|
34
|
+
- **`autoContinueAtUsageLimit` is set to `false`** in that settings file,
|
|
35
|
+
because Leg owns what happens at the limit.
|
|
36
|
+
- **Usage percentages**: `GET https://api.anthropic.com/api/oauth/usage`
|
|
37
|
+
(`LEG_CLAUDE_USAGE_URL` overrides), with the `accessToken` Claude Code
|
|
38
|
+
stored in `<CLAUDE_CONFIG_DIR>/.credentials.json` under `claudeAiOauth`, and
|
|
39
|
+
the header `anthropic-beta: oauth-2025-04-20`. The response carries
|
|
40
|
+
`five_hour` and `seven_day`, each `{ utilization, resets_at }`. Polled every
|
|
41
|
+
60 s (`LEG_USAGE_POLL_MS`). Observed live: real percentages come back
|
|
42
|
+
and land in `<LEG_HOME>/usage/claude--default.json` with
|
|
43
|
+
`source: claude usage endpoint`; a 7-day window at 93 % raised the amber
|
|
44
|
+
warning on 2026-09-11.
|
|
45
|
+
- **The wall**: the `StopFailure` hook fires with `error: rate_limit`
|
|
46
|
+
([docs](https://code.claude.com/docs/en/hooks#stopfailure)). Status:
|
|
47
|
+
**observed-live 2026-09-11** <!-- live:claude/rate_limit -->. A real
|
|
48
|
+
`StopFailure` arrived on 2026-09-11 at 07:46:37Z, a 429 `rate_limit_error`
|
|
49
|
+
from the API, and is kept, secrets scrubbed, as
|
|
50
|
+
`fixtures/live/claude/limit-rate_limit.json` (`src/live-capture.mjs`); a
|
|
51
|
+
payload `leg sessions simulate-limit <id>` produces is marked and never
|
|
52
|
+
kept (`src/live-capture.mjs` `isSimulated`). The path is also covered by
|
|
53
|
+
the hook contract test and can be driven end to end with
|
|
54
|
+
`leg sessions simulate-limit <id>`, which sends the same payload through
|
|
55
|
+
`src/hook.mjs`.
|
|
56
|
+
- **Why not the status line.** Leg writes a `statusLine` entry into the same
|
|
57
|
+
settings file that would record `rate_limits.five_hour.used_percentage` and
|
|
58
|
+
`resets_at`, and chains your own `statusLine` command first. Claude Code
|
|
59
|
+
2.1.268 did not run it when it was tried on this machine: an `echo` command
|
|
60
|
+
passed through `--settings` and again through a project
|
|
61
|
+
`.claude/settings.local.json` left the built-in status line in place, while
|
|
62
|
+
hooks from the same `--settings` file fired. No artifact of that check was
|
|
63
|
+
kept; the note lives in the `src/taps/claude-usage.mjs` header. The endpoint
|
|
64
|
+
poll is therefore the live source; the status-line route becomes a fallback
|
|
65
|
+
the moment a build honours it.
|
|
66
|
+
- **Token handling**: the stored token is read by the polling process only,
|
|
67
|
+
sent only to `api.anthropic.com`, and written nowhere. The ledger scrubs
|
|
68
|
+
bearer tokens from every line regardless (`src/redact.mjs`).
|
|
69
|
+
|
|
70
|
+
### codex
|
|
71
|
+
|
|
72
|
+
- **How Leg attaches**: nothing is injected. `leg codex` runs `codex` with
|
|
73
|
+
your arguments, then finds and tails that session's rollout file. A hook
|
|
74
|
+
would have to be trusted by codex on first use, which is why this tap
|
|
75
|
+
reads instead.
|
|
76
|
+
- **Which file**: `~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl`
|
|
77
|
+
(`CODEX_HOME` when an extra account is in use), picked by
|
|
78
|
+
`session_meta.payload.cwd` equal to the session's directory and a birth
|
|
79
|
+
time or mtime at or after the spawn, with five seconds of slack
|
|
80
|
+
(`src/taps/codex.mjs` `findRollout`). Observed live: the tap found the
|
|
81
|
+
right rollout for a real hand-off. On Windows the file's mtime lags its
|
|
82
|
+
contents, across six rollouts from 2026-09-11 the mtime was 7 s to 9 min
|
|
83
|
+
behind the last line's timestamp, so `findRollout` matches on cwd and
|
|
84
|
+
accepts a birth time or mtime at or after the spawn rather than relying on
|
|
85
|
+
the two agreeing.
|
|
86
|
+
- **Usage percentages**: the read-only app-server `account/rateLimits/read`
|
|
87
|
+
response. Leg identifies the returned windows by duration rather than by
|
|
88
|
+
field name: 300 minutes is 5h and 10080 minutes is 7d. The board and active
|
|
89
|
+
attach poll it every 60 seconds; neither sends a model turn nor assumes a
|
|
90
|
+
quota. Only an explicit available response clears a prior wall.
|
|
91
|
+
- **The wall**: `event_msg.task_complete.error` with
|
|
92
|
+
`codex_error_info: "usage_limit_exceeded"` and the message "You've hit your
|
|
93
|
+
usage limit … try again at \<date>". The wording comes from
|
|
94
|
+
`codex-rs/protocol/src/error.rs` (`UsageLimitReachedError`); the event shape
|
|
95
|
+
was read from local rollouts. The error itself:
|
|
96
|
+
**observed-live 2026-09-15** <!-- live:codex/usage_limit_exceeded -->, a
|
|
97
|
+
real `task_complete.error` with `codex_error_info: usage_limit_exceeded`
|
|
98
|
+
walled a codex leg at 07:56:24Z and handed the session to agy. No payload
|
|
99
|
+
was kept: the capture call in `src/attach.mjs` was added while that
|
|
100
|
+
session's runner was already running, so
|
|
101
|
+
`fixtures/live/codex/limit-usage_limit_exceeded.json` is still the slot
|
|
102
|
+
for the next one.
|
|
103
|
+
- **Transcript**: user prompts from `response_item.message` with `role: user`
|
|
104
|
+
and `content[].type: input_text`; assistant text from `output_text` and from
|
|
105
|
+
`task_complete.last_agent_message`. Observed live.
|
|
106
|
+
- **Edited files**: parsed from `apply_patch` payloads, the
|
|
107
|
+
`*** Add File:` / `*** Update File:` / `*** Delete File:` lines. Observed
|
|
108
|
+
live.
|
|
109
|
+
|
|
110
|
+
### agy
|
|
111
|
+
|
|
112
|
+
- **How Leg attaches**: `agy <your args> --log-file
|
|
113
|
+
<~/.leg/sessions/<id>/agy.log>`. agy 1.2.0 is a closed Go binary with no
|
|
114
|
+
hook surface.
|
|
115
|
+
- **Usage percentages**: none. agy exposes no percentage anywhere on disk; its
|
|
116
|
+
own status line fetches a quota summary from the backend and writes it
|
|
117
|
+
nowhere. When agy is not walled the board shows a "no % from agy" chip
|
|
118
|
+
instead of empty bars (`src/board/sessions.js`); when it is walled the
|
|
119
|
+
chip shows the wall and its reset.
|
|
120
|
+
- **The wall**: `RESOURCE_EXHAUSTED`, "it resets in %s" and "out of quota" in
|
|
121
|
+
the log. Those strings are present in `agy.exe`, and `scanLog()` also reads a
|
|
122
|
+
relative reset out of "resets in \<n>\<s|m|h|d>". Status:
|
|
123
|
+
**docs-only** <!-- live:agy/agy-resource-exhausted --> —
|
|
124
|
+
`RESOURCE_EXHAUSTED (code 429): Individual quota reached … Resets in
|
|
125
|
+
71h19m42s.` appeared in a session's `agy.log` at 08:02:42Z and walled the
|
|
126
|
+
agent. No payload was kept: the capture call in `src/attach.mjs` was added
|
|
127
|
+
while that session's runner was already running, so
|
|
128
|
+
`fixtures/live/agy/limit-agy-resource-exhausted.json` is still the slot
|
|
129
|
+
for the next one.
|
|
130
|
+
- **Prompts and conversation id**: `~/.gemini/antigravity-cli/history.jsonl`,
|
|
131
|
+
one `{ display, timestamp, workspace, conversationId }` per prompt. Observed
|
|
132
|
+
live.
|
|
133
|
+
- **One account only**: agy 1.2.0 has no config-directory override, so
|
|
134
|
+
`leg accounts add agy …` is refused.
|
|
135
|
+
|
|
136
|
+
### Resume prompt per agent
|
|
137
|
+
|
|
138
|
+
After a hand-off the next agent starts in the same terminal with the pointer
|
|
139
|
+
prompt as its first positional argument: `claude "<prompt>"`,
|
|
140
|
+
`codex "<prompt>"`, `agy -i "<prompt>"` (`src/attach.mjs` `spawnSpec`).
|
|
141
|
+
|
|
142
|
+
## Headless adapters (the v0.1 pipeline)
|
|
143
|
+
|
|
144
|
+
These are what a pipeline card's chain spawns. Unchanged since 0.2.0.
|
|
145
|
+
|
|
146
|
+
### claude (headless)
|
|
147
|
+
|
|
148
|
+
- **Binary**: `LEG_CLAUDE_BIN`, else `~/.local/bin/claude.exe` (Windows)
|
|
149
|
+
or `~/.local/bin/claude`, else `claude` on PATH.
|
|
150
|
+
- **Argv**: `claude -p --output-format json --permission-mode <mode>`, plus
|
|
151
|
+
`--max-turns <n>`, `--resume <session-id>`, `--model <name>`,
|
|
152
|
+
`--allowedTools <a,b>` when set.
|
|
153
|
+
- **Stdin**: `pipe`: the prompt is written to stdin, not argv.
|
|
154
|
+
- **Modes**: default `acceptEdits`; allowed `acceptEdits`, `auto`, `plan`,
|
|
155
|
+
`manual`, `dontAsk`.
|
|
156
|
+
- **Forbidden flags**: `--dangerously-skip-permissions`,
|
|
157
|
+
`--allow-dangerously-skip-permissions`,
|
|
158
|
+
`--permission-mode=bypassPermissions`, `bypassPermissions`.
|
|
159
|
+
- **Gotchas**: prints nothing on stdout until the session ends (poll
|
|
160
|
+
`run.json`, never a stdout timeout). Stderr saying "another auth source is
|
|
161
|
+
set" is an `auth_failed` leg, not a limit: it means an
|
|
162
|
+
`ANTHROPIC_API_KEY` or similar is shadowing the subscription login.
|
|
163
|
+
|
|
164
|
+
### codex (headless)
|
|
165
|
+
|
|
166
|
+
- **Binary**: `LEG_CODEX_BIN`, else the `@openai/codex` npm package's
|
|
167
|
+
native platform exe (resolved under `%APPDATA%\npm\node_modules\@openai\codex\...`
|
|
168
|
+
on Windows), else `node <bin/codex.js>` via `resolveNpmCliEntry`, else
|
|
169
|
+
`codex` on PATH.
|
|
170
|
+
- **Argv**: `codex exec --json -s <mode> -C <cwd> -c
|
|
171
|
+
sandbox_workspace_write.network_access=<true|false>`, plus `-m <model>`,
|
|
172
|
+
`-o <runDir>/last.md`, `resume <session-id>` and the prompt as the last
|
|
173
|
+
positional argument.
|
|
174
|
+
- **Stdin**: `ignore`, deliberately. `codex exec` reads stdin whenever it
|
|
175
|
+
is not a TTY and hangs on an open pipe ("Reading additional input from
|
|
176
|
+
stdin..."); a plan-time probe with a pipe hung until it was killed at
|
|
177
|
+
170 s; the same task with stdin closed finished normally (the kept probe
|
|
178
|
+
run took 28 s).
|
|
179
|
+
- **Modes**: default `workspace-write`; allowed `read-only`,
|
|
180
|
+
`workspace-write`.
|
|
181
|
+
- **Forbidden flags**: `danger-full-access`,
|
|
182
|
+
`--dangerously-bypass-approvals-and-sandbox`,
|
|
183
|
+
`--dangerously-bypass-hook-trust`, `--full-auto`, `--approve-for-me`.
|
|
184
|
+
- **Gotchas**: network access is off unless the chain entry sets
|
|
185
|
+
`network: true`. If you run `codex exec` by hand outside Leg, always
|
|
186
|
+
pass the prompt as an argument, never on a pipe.
|
|
187
|
+
|
|
188
|
+
### agy (headless)
|
|
189
|
+
|
|
190
|
+
- **Binary**: `LEG_AGY_BIN`, else `%LOCALAPPDATA%\agy\bin\agy.exe`, else
|
|
191
|
+
`agy` on PATH.
|
|
192
|
+
- **Argv**: `agy -p "<working-directory preamble>\n\n<prompt>"
|
|
193
|
+
--output-format json --mode <mode> --add-dir <cwd> --print-timeout
|
|
194
|
+
<duration>`, plus `--model <model>`, `--conversation <session-id>`. The
|
|
195
|
+
duration is the card's kill timer converted to Go syntax, for example `90m`.
|
|
196
|
+
- **Stdin**: `ignore`.
|
|
197
|
+
- **Modes**: default `accept-edits`; allowed `accept-edits`, `plan`.
|
|
198
|
+
- **Forbidden flags**: `--dangerously-skip-permissions`.
|
|
199
|
+
- **Gotchas**: agy does not act in the process cwd by default: with no
|
|
200
|
+
`--add-dir` it writes into its own scratch workspace
|
|
201
|
+
(`~/.gemini/antigravity-cli/scratch/`, observed live). Leg always passes
|
|
202
|
+
`--add-dir <worktree>` and prefixes the prompt with "Working directory:
|
|
203
|
+
`<worktree>`" so the agent writes where the card expects. `--print-timeout`
|
|
204
|
+
defaults to 5 minutes in the CLI itself; Leg always sets it explicitly
|
|
205
|
+
from the leg's kill timer so the supervisor, not agy, decides what a
|
|
206
|
+
runaway is.
|
|
207
|
+
|
|
208
|
+
### fake (and fake-claude / fake-codex / fake-agy / fake-nostdin)
|
|
209
|
+
|
|
210
|
+
- **Binary**: always `node bin/fake-agent.mjs` (`process.execPath` +
|
|
211
|
+
the script path); nothing to install.
|
|
212
|
+
- **Argv**: `fake-agent.mjs [--mode <m>] [--max-turns <n>] [--resume <id>]`.
|
|
213
|
+
- **Stdin**: `pipe` for `fake`, `ignore` for the `fake-*` named variants
|
|
214
|
+
(`fake-nostdin` is the same agent with stdin ignored), useful for
|
|
215
|
+
exercising both stdin conventions without a real CLI.
|
|
216
|
+
- **Modes**: default `acceptEdits`; allowed `acceptEdits`, `plan`,
|
|
217
|
+
`workspace-write`, `read-only`, `accept-edits`, `auto_edit` (a superset
|
|
218
|
+
covering every real adapter's vocabulary, so a chain can mix fakes and
|
|
219
|
+
real adapters without a mode conflict).
|
|
220
|
+
- **Forbidden flags**: `--dangerously-skip-permissions`, `--yolo`.
|
|
221
|
+
- **What it does**: driven entirely by the `FAKE_MODE` environment
|
|
222
|
+
variable, set per chain entry with `--fake-mode <adapter>=<mode>` (see
|
|
223
|
+
[getting-started.md](getting-started.md#try-a-pipeline-with-no-real-agent)
|
|
224
|
+
for the full list of modes). `fake-claude` / `fake-codex` / `fake-agy` set
|
|
225
|
+
`emulates` to that CLI's name, so the limit classifier applies that CLI's own
|
|
226
|
+
fixtures to it and a demo chain reads like a real fallback.
|
|
227
|
+
|
|
228
|
+
### grok (built, not registered)
|
|
229
|
+
|
|
230
|
+
- **Binary**: `LEG_GROK_BIN`, else `~/.grok/bin/grok.exe`, else `grok` on
|
|
231
|
+
PATH.
|
|
232
|
+
- **Argv**: `grok -p "<prompt>" --output-format json --permission-mode
|
|
233
|
+
<mode>`, plus `-m <model>`, `-r <session-id>`.
|
|
234
|
+
- **Stdin**: `ignore`.
|
|
235
|
+
- **Modes**: default `acceptEdits`; allowed `default`, `acceptEdits`,
|
|
236
|
+
`auto`, `dontAsk`, `plan`.
|
|
237
|
+
- **Forbidden flags**: `--always-approve`, `bypassPermissions`,
|
|
238
|
+
`--permission-mode=bypassPermissions`.
|
|
239
|
+
- **Status**: `src/adapters/grok.mjs` exists and is unit-tested, but is
|
|
240
|
+
**not** in `src/adapters/index.mjs`'s registry. The build machine had no
|
|
241
|
+
`grok` login: the probe printed a device-code prompt and exited
|
|
242
|
+
`Cancelled`. Register it (add an entry to `REGISTRY` in
|
|
243
|
+
`src/adapters/index.mjs`) once `grok login` has been completed and
|
|
244
|
+
`node scripts/probe.mjs --adapter grok --repo <toy-repo>` passes.
|
|
245
|
+
|
|
246
|
+
## How to add an adapter
|
|
247
|
+
|
|
248
|
+
An adapter is a plain object (see `src/adapters/common.mjs` for the shared
|
|
249
|
+
helpers, `src/adapters/fake.mjs` for the simplest full example):
|
|
250
|
+
|
|
251
|
+
```js
|
|
252
|
+
{
|
|
253
|
+
name: 'mycli',
|
|
254
|
+
stdin: 'pipe' | 'ignore',
|
|
255
|
+
modes: { default: 'acceptEdits', allowed: ['acceptEdits', 'plan'] },
|
|
256
|
+
forbiddenFlags: ['--any-bypass-flag'],
|
|
257
|
+
emulates: null, // optional: another registered adapter's name
|
|
258
|
+
resolve() { return { bin, viaNode, entry } },
|
|
259
|
+
argv(opts) { return { bin, args } }, // opts: mode, maxTurns, resume, cwd, prompt, model, ...
|
|
260
|
+
env(base) { return sanitizeEnv(base) }, // from src/env.mjs, always
|
|
261
|
+
parseResult(text) { return { session_id, last_message, stop_reason, raw } | null },
|
|
262
|
+
}
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
1. Write `src/adapters/<name>.mjs` exporting that shape as `default`.
|
|
266
|
+
`argv()` must call `assertAllowed(adapter, opts)` (from
|
|
267
|
+
`src/adapters/common.mjs`) first, so a forbidden mode or flag throws
|
|
268
|
+
before anything spawns.
|
|
269
|
+
2. Add an entry to `REGISTRY` in `src/adapters/index.mjs`:
|
|
270
|
+
`<name>: { path: './<name>.mjs' }`.
|
|
271
|
+
3. Add its limit/auth/launch signal fixtures under `fixtures/limits/` (see
|
|
272
|
+
the existing ones for the JSON shape `src/limits.mjs` expects: `id`,
|
|
273
|
+
`adapter`, `source`, `produced_by`, `where`, `text`, `classification`).
|
|
274
|
+
4. Run the probe script against a real login before trusting it:
|
|
275
|
+
|
|
276
|
+
```
|
|
277
|
+
node scripts/probe.mjs --adapter <name> --repo <existing-git-repo> [--mode <m>] [--timeout-s 300]
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
It runs one real tiny task (write a file, write `.leg/DONE`) through
|
|
281
|
+
the same runner a card uses, and prints `probe <name>: exit=<code>
|
|
282
|
+
file=<yes|no> done=<yes|no> auth_source=<yes|no> seconds=<n>`. Keep the
|
|
283
|
+
evidence it produces under `fixtures/live/<name>/` and cite it in
|
|
284
|
+
[cli-contracts.md](cli-contracts.md), the way every existing adapter's
|
|
285
|
+
section does.
|
|
286
|
+
|
|
287
|
+
An interactive tap is a separate, larger job: a new agent needs a
|
|
288
|
+
`src/taps/<name>.mjs` that answers three questions (what are the usage
|
|
289
|
+
percentages, what does the wall look like, what are the prompts and edited
|
|
290
|
+
files) and a branch in `src/attach.mjs` `spawnSpec`.
|
|
291
|
+
|
|
292
|
+
## See also
|
|
293
|
+
|
|
294
|
+
- [cli-contracts.md](cli-contracts.md): the full evidence trail, exit
|
|
295
|
+
codes, the interactive tap sources, and every limit-signal fixture, tagged
|
|
296
|
+
observed-live or docs-only.
|
|
297
|
+
- [configuration.md](configuration.md): the `LEG_<ADAPTER>_BIN`
|
|
298
|
+
overrides and the accounts layout.
|
|
299
|
+
- [concepts.md](concepts.md): sessions, usage windows, the interactive
|
|
300
|
+
hand-off, and how a headless leg's outcome is classified.
|