@enderfga/claw-orchestrator 7.13.0 → 7.15.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/README.md +163 -137
- package/configs/autoloop-reviewer-prompt.md +1 -1
- package/configs/engines/README.md +45 -5
- package/configs/engines/dsh.json +17 -0
- package/configs/engines/kimi-code.json +17 -0
- package/dist/bin/acp-server.js +7 -12
- package/dist/bin/acp-server.js.map +1 -1
- package/dist/bin/cli.js +127 -67
- package/dist/bin/cli.js.map +1 -1
- package/dist/bin/mcp-server.js +2 -54
- package/dist/bin/mcp-server.js.map +1 -1
- package/dist/src/acp-server.d.ts +17 -9
- package/dist/src/acp-server.js +151 -30
- package/dist/src/acp-server.js.map +1 -1
- package/dist/src/autoloop/agent-tools.d.ts +2 -0
- package/dist/src/autoloop/agent-tools.js +7 -0
- package/dist/src/autoloop/agent-tools.js.map +1 -1
- package/dist/src/autoloop/dispatcher.d.ts +6 -0
- package/dist/src/autoloop/dispatcher.js +23 -1
- package/dist/src/autoloop/dispatcher.js.map +1 -1
- package/dist/src/autoloop/planner-tools.js +2 -0
- package/dist/src/autoloop/planner-tools.js.map +1 -1
- package/dist/src/council.d.ts +8 -0
- package/dist/src/council.js +45 -12
- package/dist/src/council.js.map +1 -1
- package/dist/src/dashboard/index.html +28 -14
- package/dist/src/embedded-server.js +34 -10
- package/dist/src/embedded-server.js.map +1 -1
- package/dist/src/engine-presets.d.ts +7 -3
- package/dist/src/engine-presets.js +39 -6
- package/dist/src/engine-presets.js.map +1 -1
- package/dist/src/engine-spawn.js +3 -1
- package/dist/src/engine-spawn.js.map +1 -1
- package/dist/src/fanout.d.ts +13 -3
- package/dist/src/fanout.js +10 -0
- package/dist/src/fanout.js.map +1 -1
- package/dist/src/inbox-manager.d.ts +9 -0
- package/dist/src/inbox-manager.js +8 -2
- package/dist/src/inbox-manager.js.map +1 -1
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +136 -52
- package/dist/src/index.js.map +1 -1
- package/dist/src/kernel/conditions.d.ts +1 -1
- package/dist/src/kernel/conditions.js +1 -1
- package/dist/src/kernel/engine.d.ts +28 -8
- package/dist/src/kernel/engine.js +93 -19
- package/dist/src/kernel/engine.js.map +1 -1
- package/dist/src/kernel/nodes/agent.js +2 -0
- package/dist/src/kernel/nodes/agent.js.map +1 -1
- package/dist/src/kernel/nodes/council.js +9 -4
- package/dist/src/kernel/nodes/council.js.map +1 -1
- package/dist/src/kernel/nodes/fanout.js +5 -0
- package/dist/src/kernel/nodes/fanout.js.map +1 -1
- package/dist/src/kernel/nodes/goal.d.ts +8 -1
- package/dist/src/kernel/nodes/goal.js +40 -19
- package/dist/src/kernel/nodes/goal.js.map +1 -1
- package/dist/src/kernel/nodes/subflow.js +9 -1
- package/dist/src/kernel/nodes/subflow.js.map +1 -1
- package/dist/src/kernel/nodes/ultraapp.d.ts +1 -0
- package/dist/src/kernel/nodes/ultraapp.js +1 -0
- package/dist/src/kernel/nodes/ultraapp.js.map +1 -1
- package/dist/src/kernel/nodes/verifier.js +5 -1
- package/dist/src/kernel/nodes/verifier.js.map +1 -1
- package/dist/src/kernel/projections.d.ts +1 -0
- package/dist/src/kernel/projections.js +7 -1
- package/dist/src/kernel/projections.js.map +1 -1
- package/dist/src/kernel/templates/index.d.ts +6 -3
- package/dist/src/kernel/templates/index.js +8 -3
- package/dist/src/kernel/templates/index.js.map +1 -1
- package/dist/src/kernel/types.d.ts +30 -10
- package/dist/src/kernel/types.js.map +1 -1
- package/dist/src/logger.d.ts +6 -0
- package/dist/src/logger.js +14 -0
- package/dist/src/logger.js.map +1 -1
- package/dist/src/mcp-annotations.d.ts +13 -0
- package/dist/src/mcp-annotations.js +100 -0
- package/dist/src/mcp-annotations.js.map +1 -0
- package/dist/src/models.d.ts +7 -0
- package/dist/src/models.js +9 -0
- package/dist/src/models.js.map +1 -1
- package/dist/src/openai-compat.d.ts +11 -5
- package/dist/src/openai-compat.js +55 -54
- package/dist/src/openai-compat.js.map +1 -1
- package/dist/src/persistent-acp-session.d.ts +83 -0
- package/dist/src/persistent-acp-session.js +433 -0
- package/dist/src/persistent-acp-session.js.map +1 -0
- package/dist/src/persistent-codex-app-session.d.ts +2 -0
- package/dist/src/persistent-codex-app-session.js +18 -9
- package/dist/src/persistent-codex-app-session.js.map +1 -1
- package/dist/src/persistent-codex-session.d.ts +14 -3
- package/dist/src/persistent-codex-session.js +22 -19
- package/dist/src/persistent-codex-session.js.map +1 -1
- package/dist/src/persistent-custom-session.d.ts +9 -0
- package/dist/src/persistent-custom-session.js +41 -21
- package/dist/src/persistent-custom-session.js.map +1 -1
- package/dist/src/persistent-grok-session.js +1 -1
- package/dist/src/persistent-grok-session.js.map +1 -1
- package/dist/src/run-ledger.d.ts +3 -3
- package/dist/src/session-manager.d.ts +34 -1
- package/dist/src/session-manager.js +169 -30
- package/dist/src/session-manager.js.map +1 -1
- package/dist/src/types.d.ts +63 -12
- package/dist/src/types.js +2 -0
- package/dist/src/types.js.map +1 -1
- package/dist/src/ultraapp/build.d.ts +5 -11
- package/dist/src/ultraapp/build.js +5 -11
- package/dist/src/ultraapp/build.js.map +1 -1
- package/dist/src/ultraapp/council-adapter.d.ts +5 -2
- package/dist/src/ultraapp/council-adapter.js +6 -6
- package/dist/src/ultraapp/council-adapter.js.map +1 -1
- package/dist/src/ultraapp/files.d.ts +7 -0
- package/dist/src/ultraapp/files.js +7 -1
- package/dist/src/ultraapp/files.js.map +1 -1
- package/dist/src/ultraapp/manager.d.ts +23 -3
- package/dist/src/ultraapp/manager.js +170 -72
- package/dist/src/ultraapp/manager.js.map +1 -1
- package/dist/src/ultraapp/narrator.js +1 -1
- package/dist/src/ultraapp/narrator.js.map +1 -1
- package/dist/src/ultraapp/versions.d.ts +27 -5
- package/dist/src/ultraapp/versions.js +58 -13
- package/dist/src/ultraapp/versions.js.map +1 -1
- package/openclaw.plugin.json +4 -13
- package/package.json +13 -6
- package/skills/SKILL.md +52 -24
- package/skills/references/acp.md +140 -57
- package/skills/references/autoloop.md +141 -35
- package/skills/references/claude-cli-tracking.md +9 -6
- package/skills/references/cli.md +129 -91
- package/skills/references/council.md +44 -22
- package/skills/references/dashboard.md +37 -21
- package/skills/references/getting-started.md +49 -15
- package/skills/references/inbox.md +13 -8
- package/skills/references/mcp.md +25 -17
- package/skills/references/multi-engine.md +180 -144
- package/skills/references/observability.md +90 -58
- package/skills/references/openai-compat.md +172 -82
- package/skills/references/sessions.md +101 -65
- package/skills/references/tools.md +209 -157
- package/skills/references/ultra.md +7 -7
- package/skills/references/ultraapp.md +101 -56
- package/skills/references/verification.md +112 -56
- package/skills/references/workflow.md +227 -93
- package/assets/banner.jpg +0 -0
|
@@ -16,24 +16,44 @@ SessionManager
|
|
|
16
16
|
│ └── Wraps: agy -p (Google Antigravity CLI, per-message spawning, stream-json output)
|
|
17
17
|
├── engine: 'grok' → PersistentGrokSession
|
|
18
18
|
│ └── Wraps: grok -p --output-format json (xAI Grok Build, per-message spawning)
|
|
19
|
-
├── engine: 'cursor' → PersistentCursorSession (legacy)
|
|
20
|
-
│ └── Wraps: cursor-agent -p --trust --output-format stream-json (per-message spawning)
|
|
21
19
|
├── engine: 'opencode' → PersistentOpencodeSession
|
|
22
20
|
│ └── Wraps: opencode run --format json, message on stdin (per-message spawning)
|
|
21
|
+
├── engine: 'acp' → PersistentAcpSession
|
|
22
|
+
│ └── Wraps: any Agent Client Protocol agent on stdio (long-running, one ACP session)
|
|
23
23
|
└── engine: 'custom' → PersistentCustomSession
|
|
24
24
|
└── Wraps: any CLI via user-provided CustomEngineConfig
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
+
The `gemini` and `cursor` engines are legacy: still wired for existing callers, not documented
|
|
28
|
+
as options (see the notes under [Antigravity](#google-antigravity-engine-agy) and
|
|
29
|
+
[Grok Build](#grok-build-engine-grok)).
|
|
30
|
+
|
|
31
|
+
### At a glance
|
|
32
|
+
|
|
33
|
+
| Engine | Process | How the conversation carries over | Where cost comes from |
|
|
34
|
+
| ----------- | ----------------------------------- | ----------------------------------------------------------------------- | ---------------------------------------------------------------------- |
|
|
35
|
+
| `claude` | one long-running `claude -p` | the live process; `--resume <id>` when a session is restarted | the CLI's own `total_cost_usd` (registry estimate in proxy mode) |
|
|
36
|
+
| `codex` | one `codex exec` per send | `codex exec resume <thread_id>` | registry price × reported tokens |
|
|
37
|
+
| `codex-app` | one long-running `codex app-server` | the live thread; `thread/resume` when a session is restarted | registry price × reported tokens |
|
|
38
|
+
| `agy` | one `agy -p` per send | `--conversation <id>` | registry price × reported tokens |
|
|
39
|
+
| `grok` | one `grok -p` per send | `--resume <sessionId>` | grok's own `total_cost_usd` |
|
|
40
|
+
| `opencode` | one `opencode run` per send | `--session <id>` | opencode's own `step_finish` cost (registry estimate when none) |
|
|
41
|
+
| `acp` | one long-running agent process | one ACP session; `session/resume` or `session/load` on restart | what the agent reports (estimated from `pricing` when it reports none) |
|
|
42
|
+
| `custom` | persistent, or one process per send | persistent: the live process (`args.resume` on restart); one-shot: none | registry price × reported tokens (see `pricing` below) |
|
|
43
|
+
|
|
27
44
|
## Supported Engines
|
|
28
45
|
|
|
29
46
|
### Claude Code (`engine: 'claude'`)
|
|
30
47
|
|
|
31
|
-
Default engine. Long-running subprocess with streaming JSON I/O. Tested with Claude Code CLI **2.1.
|
|
48
|
+
Default engine. Long-running subprocess with streaming JSON I/O. Tested with Claude Code CLI **2.1.310**.
|
|
32
49
|
|
|
33
50
|
- Persistent multi-turn conversations
|
|
34
51
|
- Real-time streaming (text, tool_use, tool_result, system events)
|
|
35
52
|
- Session resume via `--resume`
|
|
36
|
-
-
|
|
53
|
+
- Cost is the CLI's own `total_cost_usd`, which prices cache writes. It is a running total for the
|
|
54
|
+
process, so spend advances by the difference between reports. The registry estimate is used
|
|
55
|
+
instead in proxy mode (the CLI is told it runs `opus`) and for the first turn of a process started
|
|
56
|
+
with `--resume`, whose first report includes the spend of the turns it resumed
|
|
37
57
|
- Cross-session peer messaging (`crossSessionInbound`): this session's policy for messages from
|
|
38
58
|
other Claude Code sessions on the same machine — `accept` delivers them, `hold` waits for approval
|
|
39
59
|
in that session's terminal, `refuse` rejects them. It is a settings key, passed through
|
|
@@ -68,18 +88,16 @@ Wraps the `codex exec` subcommand. Each `send()` spawns a new process. Tested wi
|
|
|
68
88
|
- Real `usage` from the `turn.completed` JSON event (input, output, cached, reasoning tokens). **These are cumulative over the thread, not per turn**, so they replace the session totals rather than being added to them; subtracting consecutive values gives one turn's prompt
|
|
69
89
|
- `contextPercent` is the per-turn prompt measured against **codex's own context limit** (`model_context_window`, read from the thread's rollout file), not the model's published window in the registry. Resuming a thread also seeds the token baseline from the rollout, so the first send does not count the whole thread history as one prompt. This is best-effort: an unreadable or `--ephemeral` thread falls back to the registry window
|
|
70
90
|
- `item.completed` parsing distinguishes `reasoning` / `todo_list` (logged, not counted) from real tool items (`command_execution`, `file_change`, `mcp_tool_call`, `web_search`, which increment `toolCalls`; a non-zero `command_execution.exit_code` increments `toolErrors`)
|
|
71
|
-
- Reasoning effort: the engine-agnostic `effort` maps to `-c model_reasoning_effort=<level>` for `low|medium|high|xhigh|max|ultra`; `auto`
|
|
91
|
+
- Reasoning effort: the engine-agnostic `effort` maps to `-c model_reasoning_effort=<level>` for `low|medium|high|xhigh|max|ultra`; `auto` passes nothing. Only those levels are passed, because codex does not validate `-c` values at spawn: an unlisted one would fail at the API rather than at the command line
|
|
72
92
|
- `appendSystemPrompt`: codex has no system-prompt flag, so it is placed at the top of the first message of each new conversation (a resumed thread already carries it)
|
|
73
93
|
- `jsonSchema` → `--output-schema <file>` (written to a temp file, accepted by `exec` and `exec resume`)
|
|
74
94
|
- `noSessionPersistence` → `--ephemeral` (accepted by `exec` and `exec resume`); `ignoreUserConfig` → `--ignore-user-config`, which stops `$CODEX_HOME/config.toml` from choosing the model for an orchestrated run (auth still resolves from `CODEX_HOME`); `addDir` → `--add-dir` on the first turn only, since `exec resume` rejects it and the resumed thread keeps the roots it opened with
|
|
75
95
|
- `codexProfile` → `--profile <name>` (named config profile from `~/.codex/config.toml`), first turn only
|
|
76
96
|
- `--worktree` is not passed. With it, codex writes the turn's edits to `~/.codex/worktrees/<hash>/<repo>` instead of the session's `cwd`, while acceptance contracts, evidence diffs and the baseline change set all read the session's `cwd`. Council's per-agent git worktrees cover the isolation use case
|
|
77
|
-
-
|
|
78
|
-
- `sandboxMode` maps to `--sandbox <mode>` on the first turn. A resumed thread does not keep it, and `codex exec resume` rejects `--sandbox`, so the policy is restated as `-c sandbox_mode="<mode>"` on every resumed turn
|
|
79
|
-
- One-shot execution per message (no persistent subprocess between sends)
|
|
80
|
-
- Captures the real Codex thread ID and persists it, so later sends and process-level session resume use `codex exec resume <thread_id>`
|
|
97
|
+
- Continuity: the `thread_id` from the first turn's `thread.started` event is captured and reused via `codex exec resume <id>` for later sends, so the model sees prior turns. The id is persisted too, so a restarted session (and `resumeSessionId`) resumes the same thread
|
|
98
|
+
- `sandboxMode` maps to `--sandbox <mode>` on the first turn (default `workspace-write`). A resumed thread does not keep it, and `codex exec resume` rejects `--sandbox`, so the policy is restated as `-c sandbox_mode="<mode>"` on every resumed turn
|
|
81
99
|
- Working directory passed via `-C` on the first turn
|
|
82
|
-
- Model: with no `model`, codex runs its own default — the `model` in `~/.codex/config.toml` if set. Cost is then priced as the model codex reports it ran (the rollout's `turn_context`; `thread/start` on `codex-app`), and as `gpt-5.5` only when that cannot be read
|
|
100
|
+
- Model: with no `model`, codex runs its own default — the `model` in `~/.codex/config.toml` if set. Cost is then priced as the model codex reports it ran (the rollout's `turn_context`; `thread/start` on `codex-app`), and as `gpt-5.5` only when that cannot be read. Cached reads are inside codex's `input_tokens`, so they are taken out and priced at the cached rate
|
|
83
101
|
- Requires `codex` CLI >= 0.119 (for `exec resume`): `npm install -g @openai/codex`
|
|
84
102
|
- The prompt is sent on stdin (`codex exec … -`), never on the command line.
|
|
85
103
|
- **Windows:** the npm `.cmd` shim launches through `src/engine-spawn.ts` (cross-spawn), which escapes the remaining arguments for `cmd.exe`. Because the prompt is on stdin it arrives unchanged, newlines and quotes included. A timed-out turn ends the whole process tree (`taskkill /T /F`), not only `cmd.exe`.
|
|
@@ -109,11 +127,14 @@ Wraps `codex app-server --listen stdio:// --enable goals` as a long-running JSON
|
|
|
109
127
|
- Real-time streaming via `item/agentMessage/delta` notifications
|
|
110
128
|
- Cumulative token tracking from `thread/tokenUsage/updated` notifications. The same notification's `last` breakdown and `modelContextWindow` drive `contextPercent`, so it reports live occupancy against the window the server enforces rather than a running total over the model's published window
|
|
111
129
|
- `appendSystemPrompt` is placed at the top of the first message of a new thread (app-server has no system-prompt flag); a resumed thread already carries it
|
|
130
|
+
- Reasoning effort: the session `effort` opens or resumes the thread with `config: { model_reasoning_effort }`, so the goal loop's own turns use it too, and each `turn/start` carries `effort` (the per-send override, else the session's), so `setEffort` reaches the next turn. The levels are the same as `codex`'s; `auto` passes nothing, and codex keeps the last effort it was sent for the turns after it
|
|
112
131
|
- Goals through the typed RPCs `thread/goal/set` / `get` / `clear`: setting an objective starts the server's own turn loop, which runs until the goal is `complete`, `budgetLimited`, `blocked`, `usageLimited` or `paused`. Each change arrives as `thread/goal/updated`. The `codex_goal_*` tools wrap these; the kernel's `goal` node drives them for unattended runs ([workflow.md](./workflow.md#goal))
|
|
113
132
|
- `error` notifications are read as `{ error: { message, codexErrorInfo }, willRetry }`. The session keeps the last one (`lastError`, with `codexErrorInfo` as a name such as `serverOverloaded`) and emits it as `codex:error`; an error notification never throws when nothing listens for errors
|
|
114
133
|
- v2 RPC tools (Codex 0.137): `codex_interrupt` (`turn/interrupt`), `codex_steer` (`turn/steer`, including into a turn the goal loop started), `codex_fork` (`thread/fork`), `codex_rollback` (`thread/rollback`), `codex_models` (`model/list`), `codex_thread_list` (`thread/list`). A `turn/completed` with `status: 'failed'` rejects the turn and increments `toolErrors`.
|
|
115
134
|
- Threads are opened and resumed with `approvalPolicy: "never"`, and a server request the session cannot handle (an approval, a request for user input) gets an error reply at once: nothing here can answer one, and an unanswered request blocks its turn. The sandbox mode is the boundary.
|
|
116
|
-
- Thread resume: starting with `resumeSessionId` loads the existing thread via `thread/resume` instead of `thread/start`. The id the session manager stores and replays (model switch, restart) is the app-server's own thread id. A thread resumed with an active goal continues it on its own.
|
|
135
|
+
- Thread resume: starting with `resumeSessionId` loads the existing thread via `thread/resume` instead of `thread/start`. The id the session manager stores and replays (model switch, restart) is the app-server's own thread id. A thread resumed with an active goal continues it on its own. An id that `thread/resume` rejects falls back to a new thread (logged); the `goal` node refuses that case rather than start the goal over.
|
|
136
|
+
- `compact` asks the app-server to compact the thread (`thread/compact/start`).
|
|
137
|
+
- `clawo goal "<objective>" --check "npm test"` runs the `goal` workflow from the command line: one goal on one thread, then the runtime's own checks, then repair on the same thread ([cli.md](./cli.md#clawo-goal)).
|
|
117
138
|
|
|
118
139
|
```typescript
|
|
119
140
|
await manager.startSession({
|
|
@@ -127,22 +148,11 @@ await manager.codexGoal('codex-goal-task', { action: 'set', objective: 'build a
|
|
|
127
148
|
// await tool('codex_goal_set', { name: 'codex-goal-task', objective: 'build a tic-tac-toe app' });
|
|
128
149
|
```
|
|
129
150
|
|
|
130
|
-
### Running an engine under a launcher
|
|
131
|
-
|
|
132
|
-
`SessionConfig.launcher` is an argv prefix the engine binary runs under, for every
|
|
133
|
-
engine: `launcher: ['/opt/task/sandbox.sh', 'task', '0 1']` spawns
|
|
134
|
-
`/opt/task/sandbox.sh task '0 1' codex app-server …`. This is how a caller puts
|
|
135
|
-
the agent inside its own sandbox (bubblewrap, a container entry point, `taskset`)
|
|
136
|
-
without a custom engine. The launcher is the caller's own command, at the same
|
|
137
|
-
trust level as a contract's command check, which the HTTP API also accepts: set
|
|
138
|
-
it through the SessionManager API, `/session/start` or a workflow spec (the `goal`
|
|
139
|
-
node's `launcher`). It is not a parameter of the `session_start` tool.
|
|
140
|
-
|
|
141
151
|
### Google Antigravity (`engine: 'agy'`)
|
|
142
152
|
|
|
143
153
|
Wraps Google's **Antigravity CLI** (`agy`) — the successor to Gemini CLI (consumer
|
|
144
154
|
Gemini CLI tiers stopped serving 2026-06-18). Each `send()` spawns a new process
|
|
145
|
-
in print mode. Tested with `agy` **1.4.
|
|
155
|
+
in print mode, with the message as the `-p` argument. Tested with `agy` **1.4.6**.
|
|
146
156
|
|
|
147
157
|
- One-shot execution per message (no persistent subprocess)
|
|
148
158
|
- **Structured output and real usage** — `--output-format stream-json` emits an
|
|
@@ -178,8 +188,9 @@ in print mode. Tested with `agy` **1.4.4**.
|
|
|
178
188
|
passes the requested effort through rather than substituting a tier the caller
|
|
179
189
|
did not ask for; run `agy models` to see the tiers a slug actually exposes.
|
|
180
190
|
|
|
181
|
-
- Permission modes: `bypassPermissions` →
|
|
182
|
-
`default` and `manual` → `--sandbox`
|
|
191
|
+
- Permission modes: `bypassPermissions` (or `dangerouslySkipPermissions`) →
|
|
192
|
+
`--dangerously-skip-permissions`, `default` and `manual` → `--sandbox`
|
|
193
|
+
(terminal-restricted), and
|
|
183
194
|
`sandboxMode: 'read-only'` → `--mode plan` (takes precedence). Other modes
|
|
184
195
|
run agy's own approval flow, which can block in headless print mode. A caller
|
|
185
196
|
must explicitly choose `bypassPermissions` for a write-enabled session; it is
|
|
@@ -202,7 +213,8 @@ in print mode. Tested with `agy` **1.4.4**.
|
|
|
202
213
|
pass through unregistered. The `agy/` prefix forces Antigravity routing for
|
|
203
214
|
provider-like model strings.
|
|
204
215
|
- Consumer auth is a one-time `agy` Google OAuth login (subscription quotas, no
|
|
205
|
-
per-token billing — registry pricing mirrors Gemini API rates as a value proxy)
|
|
216
|
+
per-token billing — registry pricing mirrors Gemini API rates as a value proxy).
|
|
217
|
+
An effort-qualified slug such as `gemini-3.1-pro-high` is priced as its base model
|
|
206
218
|
- Requires `agy` installed: `curl -fsSL https://antigravity.google/cli/install.sh | bash`
|
|
207
219
|
- Binary: `agy` (set `AGY_BIN` env var to override)
|
|
208
220
|
|
|
@@ -218,16 +230,17 @@ await manager.startSession({
|
|
|
218
230
|
|
|
219
231
|
> **Legacy: `engine: 'gemini'`.** Google sunset the consumer Gemini CLI (tiers stopped
|
|
220
232
|
> serving 2026-06-18) in favour of Antigravity. The `gemini` engine still exists and
|
|
221
|
-
> still works
|
|
222
|
-
>
|
|
223
|
-
>
|
|
224
|
-
>
|
|
225
|
-
>
|
|
233
|
+
> still works for existing callers, but it is no longer a documented option, is not
|
|
234
|
+
> version-tracked, and gets no new work. Use `agy` for Google. The OpenAI-compatible
|
|
235
|
+
> endpoint and the ACP agent pick the engine from the model name, and there `gemini-*`
|
|
236
|
+
> strings outside agy's registered slugs (including the `gemini-pro` and `gemini-flash`
|
|
237
|
+
> aliases) still route to the `gemini` engine. (Unrelated: the multi-model **proxy**
|
|
238
|
+
> still talks to the Gemini **API**; that is a different subsystem and is unaffected.)
|
|
226
239
|
|
|
227
240
|
### Grok Build (`engine: 'grok'`)
|
|
228
241
|
|
|
229
242
|
Wraps xAI's **Grok Build** CLI. Each `send()` spawns `grok -p <msg> --output-format json`, which
|
|
230
|
-
prints a single JSON object and exits. Tested with `grok` **1.0.
|
|
243
|
+
prints a single JSON object and exits. Tested with `grok` **1.0.70**.
|
|
231
244
|
|
|
232
245
|
- **Cost comes from the engine, not from the price table.** The result object carries
|
|
233
246
|
`total_cost_usd`, and the wrapper writes it straight into the session's spend, so the run ledger
|
|
@@ -238,19 +251,21 @@ prints a single JSON object and exits. Tested with `grok` **1.0.67**.
|
|
|
238
251
|
- **Real conversation continuity**: the `sessionId` from turn 1 is replayed as `--resume <id>`.
|
|
239
252
|
`--continue` is not used — it means "the most recent session for this cwd", which collides
|
|
240
253
|
between concurrent sessions.
|
|
241
|
-
- Real token counts from `usage` (`input_tokens`, `output_tokens`, `cache_read_input_tokens
|
|
242
|
-
These are **per-turn**, unlike codex, where the same field is
|
|
254
|
+
- Real token counts from `usage` (`input_tokens`, `output_tokens`, `cache_read_input_tokens`,
|
|
255
|
+
`cache_creation_input_tokens`). These are **per-turn**, unlike codex, where the same field is
|
|
256
|
+
cumulative, and `input_tokens` excludes the cached reads.
|
|
243
257
|
- Permission modes pass straight through: grok's `--permission-mode` takes the same vocabulary we
|
|
244
258
|
use. The one exception is our `manual`, which grok spells `default`.
|
|
245
259
|
- Reasoning effort maps to `--effort`; grok accepts `low|medium|high|xhigh`, so `max` and `ultra`
|
|
246
260
|
clamp to `xhigh`.
|
|
247
|
-
- Session options that reach grok: `appendSystemPrompt` → `--rules` (appends,
|
|
248
|
-
`systemPrompt` → `--system-prompt-override`, which replaces), `
|
|
249
|
-
`
|
|
250
|
-
JSON output format already asked for), `agent` →
|
|
251
|
-
`
|
|
252
|
-
→ `--fork-session
|
|
253
|
-
|
|
261
|
+
- Session options that reach grok: `cwd` → `--cwd`, `appendSystemPrompt` → `--rules` (appends,
|
|
262
|
+
unlike `systemPrompt` → `--system-prompt-override`, which replaces), `maxTurns` → `--max-turns`,
|
|
263
|
+
`allowedTools` → `--tools`, `disallowedTools` → `--disallowed-tools`, `jsonSchema` →
|
|
264
|
+
`--json-schema` (inline, and it implies the JSON output format already asked for), `agent` →
|
|
265
|
+
`--agent`, `agents` → `--agents`, `dangerouslySkipPermissions` → `--always-approve`,
|
|
266
|
+
`forkSession` → `--fork-session` (on a resumed turn), `customSessionId` → `--session-id` (on a
|
|
267
|
+
first turn, or a forked one). **grok validates neither tool list**: a name that does not exist is
|
|
268
|
+
ignored rather than rejected, so a typo in a denylist leaves the tool enabled. Prefer an allowlist.
|
|
254
269
|
- **`sandboxMode: 'read-only'` runs inside grok's `--sandbox read-only` profile**, which the OS
|
|
255
270
|
enforces (Seatbelt on macOS, Landlock on Linux) for the whole process tree: the session reads
|
|
256
271
|
anywhere and can write only to `~/.grok/` and the temp directory. Measured on 1.0.50 with
|
|
@@ -279,48 +294,83 @@ await manager.startSession({
|
|
|
279
294
|
});
|
|
280
295
|
```
|
|
281
296
|
|
|
282
|
-
|
|
297
|
+
> **Legacy: `engine: 'cursor'`.** Superseded in this lineup by Grok Build. The `cursor` engine
|
|
298
|
+
> still exists and still works for existing callers — it runs `cursor-agent` (`CURSOR_BIN`
|
|
299
|
+
> overrides it, never the bare `agent` name) and resumes a chat with `--resume <chatId>` — but it
|
|
300
|
+
> is no longer a documented option, is not version-tracked, and gets no new work. It left the
|
|
301
|
+
> tracked set because its stream does not say which model ran, so its cost cannot be attributed
|
|
302
|
+
> to a model. On the OpenAI-compatible endpoint and the ACP agent,
|
|
303
|
+
> `composer-*` and `auto` model names still route to it.
|
|
283
304
|
|
|
284
|
-
|
|
285
|
-
> The `cursor` engine still exists and still works — existing callers are not broken — but it is
|
|
286
|
-
> no longer a documented option, is not version-tracked, and gets no new work.
|
|
287
|
-
>
|
|
288
|
-
> Cursor itself is still maintained. It left the tracked set because it does not report which
|
|
289
|
-
> model ran (its `system` init event says `"model": "Auto"`), so costs cannot be attributed to a
|
|
290
|
-
> model, and because xAI's Grok installer also claims the bare `agent` binary name.
|
|
305
|
+
### OpenCode (`engine: 'opencode'`)
|
|
291
306
|
|
|
292
|
-
Wraps the
|
|
307
|
+
Wraps the [OpenCode](https://github.com/anomalyco/opencode) CLI with `run --format json`. Each `send()` spawns a new process. Tested with `opencode` **1.19.4**.
|
|
293
308
|
|
|
294
|
-
- Conversation continuity: the
|
|
295
|
-
-
|
|
296
|
-
-
|
|
297
|
-
-
|
|
298
|
-
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
- `--
|
|
304
|
-
-
|
|
305
|
-
-
|
|
306
|
-
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
+
- Conversation continuity: the session id from the event envelope is captured and passed back as `--session <id>` on later sends, so the model sees prior turns. `--continue` is deliberately not used: it means "the last session on this machine", which collides between concurrent sessions.
|
|
310
|
+
- NDJSON event stream with envelope `{ type, timestamp, sessionID, ... }`
|
|
311
|
+
- Event types: `text`, `reasoning`, `tool_use`, `step_start`, `step_finish`, `error`
|
|
312
|
+
- `text` and `tool_use` are **cumulative snapshots** keyed by `part.id` / `part.callID`; the wrapper diffs them to produce streaming deltas for `onText` callbacks and counts each tool invocation once
|
|
313
|
+
- Real token counts from `step_finish.part.tokens.{input,output,cache.{read,write}}`. **`input` is the uncached remainder only** — opencode's own `total` is `input + output + cache.read + cache.write` — so the cached part is billed on top of it, not carved out of it, and `contextPercent` is measured against the whole input side. On a resumed turn the uncached remainder is usually a small fraction of the cached part
|
|
314
|
+
- Cost is what opencode reports on each `step_finish` (`part.cost`), so a free model costs $0; the registry estimate applies only when no cost is reported. `opencode run --format json` does not name the model it ran
|
|
315
|
+
- Reasoning effort maps to `--variant`, opencode's provider-specific effort knob. opencode does not validate the value: a level the provider does not offer runs the turn at its default rather than failing
|
|
316
|
+
- `appendSystemPrompt`: opencode has no system-prompt flag, so it is placed at the top of the first message of each new conversation
|
|
317
|
+
- The message is written to the subprocess's stdin, which is then closed: opencode waits for EOF before it starts
|
|
318
|
+
- Provider-agnostic: opencode's `--model` expects `provider/model` form (e.g. `anthropic/claude-sonnet-4`). The wrapper passes `--model` through only when the value contains a `/`; otherwise opencode's own default applies
|
|
319
|
+
- `sandboxMode: 'read-only'` runs a generated `clawo-readonly` agent (`--agent clawo-readonly` plus an `OPENCODE_CONFIG_CONTENT` env var defining it):
|
|
320
|
+
- it denies `edit`, `bash`, `external_directory`, `webfetch` and `task` at the permission level, and also removes those tools via the agent's `tools` map
|
|
321
|
+
- denying `task` matters: otherwise the agent can hand a write to a subagent that runs under the default writable agent
|
|
322
|
+
- OpenCode's built-in `plan` agent is not used, because it permits both `bash` and `edit`
|
|
323
|
+
- if the `clawo-readonly` agent fails to load, the turn is refused rather than run with write access
|
|
324
|
+
- to test a change to this config, use adversarial prompts that include asking the agent to delegate; `opencode agent list` shows compiled rules that look the same for a safe and an unsafe agent
|
|
325
|
+
- Requires OpenCode 1.x: `npm install -g opencode-ai` or `brew install anomalyco/tap/opencode`. Auth via `opencode auth login` **or** any provider env var (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GEMINI_API_KEY`, etc.) — opencode picks up either path
|
|
326
|
+
- **OpenCode 2 is refused at session start.** Its `run --format json` keeps the event envelope but drops `step_finish` (no tokens or cost) and the `--variant` flag, and the read-only agent could not be verified on it (measured on the `0.0.0-beta-17823` build). The session reads `opencode --version` once and refuses a 2.x or `0.0.0-beta` build with that reason; if the version cannot be read, it starts as before. OpenCode 2 is what `opencode upgrade --major`, the `@opencode/cli` npm package and the `opencode-v2` Homebrew formula install.
|
|
327
|
+
- Binary: `opencode` (set `OPENCODE_BIN` env var to override). **Windows:** the npm `.cmd` shim launches through `src/engine-spawn.ts` (cross-spawn), as for codex above.
|
|
309
328
|
|
|
310
329
|
```typescript
|
|
311
330
|
await manager.startSession({
|
|
312
|
-
name: '
|
|
313
|
-
engine: '
|
|
314
|
-
model: 'sonnet-4',
|
|
331
|
+
name: 'opencode-task',
|
|
332
|
+
engine: 'opencode',
|
|
333
|
+
model: 'anthropic/claude-sonnet-4',
|
|
315
334
|
cwd: '/project',
|
|
316
335
|
});
|
|
317
336
|
```
|
|
318
337
|
|
|
338
|
+
> **Schema stability:** opencode releases nearly daily and the JSON event schema is not formally documented. The parser tolerates unknown event types and missing fields — but pin a tested `opencode` version in CI if you depend on field names.
|
|
339
|
+
|
|
340
|
+
### ACP agents (`engine: 'acp'`)
|
|
341
|
+
|
|
342
|
+
Drives any agent that serves the [Agent Client Protocol](https://agentclientprotocol.com) on stdio, with no per-agent code. Run through it so far: Kimi Code 2.3.1 (`kimi acp`), DeepSeek's dsh 0.2.1-rc.2 (`dsh --profile acp`) and OpenCode 1.19.4 (`opencode acp`). This is the client side of the protocol; [acp.md](./acp.md) is the other side, where the orchestrator is the agent.
|
|
343
|
+
|
|
344
|
+
- `acpAgent` names the agent: a bundled preset id (`kimi-code`, `dsh`; `clawo engines` lists them), or, from a local caller only, an inline `{ name, command, commandEnv?, args?, env?, contextWindow?, pricing? }`. Over HTTP, which includes the `clawo` CLI, only a preset id is accepted, as for `customEngine`.
|
|
345
|
+
- One agent process per session; each send is one `session/prompt` on one ACP session, so the conversation carries over between sends. `initialize` advertises no `fs` or `terminal` capability: the agent reads, writes and runs commands with its own tools.
|
|
346
|
+
- A turn succeeds when the agent ends it with `stopReason: "end_turn"`; any other stop reason (`refusal`, `max_tokens`, `cancelled`, …) fails it. A turn that runs past its timeout is cancelled with `session/cancel`. An agent that exits mid-turn fails the turn and the session.
|
|
347
|
+
- `session/request_permission` is answered at once from `permissionMode`: `bypassPermissions`, `acceptEdits`, `auto` and `dontAsk` pick an allow option, the others a reject option. Nothing waits on a person.
|
|
348
|
+
- Resume: the agent's session id is the resume id (`stats.acpSessionId`). A resumed session reattaches it with `session/resume`, or `session/load` when that is all the agent offers; an agent with neither fails the start rather than opening a new conversation.
|
|
349
|
+
- Usage and cost are whatever the agent reports (`PromptResponse.usage`, `usage_update`). With `pricing` set and no reported cost, the cost is estimated from the reported tokens. Kimi Code 2.3.1 and dsh 0.2.1-rc.2 report neither, so their sessions show 0 tokens and $0. OpenCode reports tokens.
|
|
350
|
+
- `sandboxMode: 'read-only'` is refused at start. The agent's tools run inside the agent process and this client sees only the permission requests the agent chooses to send, so nothing here can stop a write. `permissionMode: 'plan'` rejects every permission request the agent does send.
|
|
351
|
+
- The model and credentials are the agent's own configuration, set in its environment; the session's `model` and `effort` are not sent to the agent, and `compact` is left to it:
|
|
352
|
+
- Kimi Code: `KIMI_MODEL_NAME`, `KIMI_MODEL_API_KEY`, `KIMI_MODEL_BASE_URL`, `KIMI_MODEL_MAX_CONTEXT_SIZE`, `KIMI_MODEL_CAPABILITIES`, or a `kimi` login. `KIMI_BIN` overrides the command.
|
|
353
|
+
- dsh: `DEEPSEEK_API_KEY` (and `DEEPSEEK_BASE_URL`); its profile chooses the model. `DSH_BIN` overrides the command.
|
|
354
|
+
- ACP has no system-prompt channel, so `systemPrompt` and `appendSystemPrompt` go at the top of the first message of a new session (a council seat's charter travels this way); a resumed session already holds them.
|
|
355
|
+
- `launcher` applies as for every engine.
|
|
356
|
+
- Where an ACP agent runs besides `session_start`: each fan-out and council agent, a workflow `agent` node, `solve`'s implementer and scouts, and a fan-out's synthesis pass (`synthesisAcpAgent`) name it by preset id in `acpAgent`; `session_handoff` takes `acpAgent` as `session_start` does. On the CLI an agent in a list is written `acp:<preset>`. Autoloop roles and ultrareview reviewers refuse `acp`: a role cannot name the agent, and a reviewer must be read-only.
|
|
357
|
+
|
|
358
|
+
```bash
|
|
359
|
+
clawo fanout "How would you cut this test suite's run time?" -e claude,acp:kimi-code,acp:dsh --synthesize --wait
|
|
360
|
+
clawo session-start kimi -e acp --acp-agent kimi-code --permission-mode bypassPermissions -d ./project
|
|
361
|
+
clawo session-send kimi "Fix the failing test in tests/test_io.py"
|
|
362
|
+
```
|
|
363
|
+
|
|
319
364
|
### Community engine presets
|
|
320
365
|
|
|
321
366
|
`customEngine` accepts either an inline `CustomEngineConfig` or the id of a preset
|
|
322
|
-
bundled in `configs/engines
|
|
323
|
-
described once and shipped, rather than
|
|
367
|
+
bundled in `configs/engines/`, and `acpAgent` does the same for the `acp` engine. The
|
|
368
|
+
preset form exists so a third-party CLI is described once and shipped, rather than
|
|
369
|
+
retyped by every caller. A preset carries exactly one of an `engine` block (started
|
|
370
|
+
with `engine: 'custom'`) or an `acp` block (started with `engine: 'acp'`); naming one
|
|
371
|
+
with the other engine fails with a message that says which to use. Bundled today:
|
|
372
|
+
`zcode` (custom), `kimi-code` and `dsh` (ACP). The format is in
|
|
373
|
+
[configs/engines/README.md](../../configs/engines/README.md).
|
|
324
374
|
|
|
325
375
|
Engines fall into three tiers, decided by how they are verified. Bundled presets
|
|
326
376
|
are always `community`:
|
|
@@ -333,57 +383,40 @@ are always `community`:
|
|
|
333
383
|
|
|
334
384
|
A community preset must name its maintainer and the engine version and date it
|
|
335
385
|
was verified against (`provenance.maintainer`, `provenance.verifiedAgainst`,
|
|
336
|
-
`provenance.verifiedOn`)
|
|
386
|
+
`provenance.verifiedOn`); a link to the captured smoke-test transcript
|
|
387
|
+
(`provenance.smokeUrl`) is expected in review but not enforced by the schema check.
|
|
337
388
|
|
|
338
389
|
A preset never carries protocol translation. A CLI speaking its own wire format
|
|
339
390
|
needs an adapter binary in its author's package, with the preset pointing `bin`
|
|
340
|
-
at it — as
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
the
|
|
348
|
-
`clawo
|
|
391
|
+
at it — as the `zcode` preset does with `zcode-claw-adapter`. An agent that serves
|
|
392
|
+
ACP needs no adapter: its preset names the command.
|
|
393
|
+
|
|
394
|
+
Over HTTP — which includes the `clawo` CLI — a custom engine or an ACP agent may be
|
|
395
|
+
given **only** as a preset id. An inline config names a binary and its arguments and
|
|
396
|
+
is refused there; a preset id cannot introduce either, so it is the same class as
|
|
397
|
+
naming a built-in engine, and `engine: 'codex'` already spawns an executable over
|
|
398
|
+
that surface. Presets are therefore the supported way to reach either from the CLI:
|
|
399
|
+
`clawo engines` lists what is bundled, and
|
|
400
|
+
`clawo session-start <name> -e custom --custom-engine <id>` (or
|
|
401
|
+
`-e acp --acp-agent <id>` for an ACP preset) starts one.
|
|
349
402
|
|
|
350
403
|
Contribution steps and the smoke script: [CONTRIBUTING.md](../../CONTRIBUTING.md#contributing-an-engine).
|
|
351
404
|
|
|
352
|
-
###
|
|
353
|
-
|
|
354
|
-
Wraps the [sst/opencode](https://github.com/sst/opencode) CLI with `run --format json`. Each `send()` spawns a new process.
|
|
355
|
-
|
|
356
|
-
- Conversation continuity: the session id from the event envelope is captured and passed back as `--session <id>` on later sends, so the model sees prior turns. `--continue` is deliberately not used: it means "the last session on this machine", which collides between concurrent sessions.
|
|
357
|
-
- One-shot execution per message (no persistent subprocess)
|
|
358
|
-
- NDJSON event stream with envelope `{ type, timestamp, sessionID, ... }`
|
|
359
|
-
- Event types: `text`, `reasoning`, `tool_use`, `step_start`, `step_finish`, `error`
|
|
360
|
-
- `text` and `tool_use` are **cumulative snapshots** keyed by `part.id` / `part.callID`; the wrapper diffs them to produce streaming deltas for `onText` callbacks and counts each tool invocation once
|
|
361
|
-
- Real token counts from `step_finish.part.tokens.{input,output,cache.{read,write}}`. **`input` is the uncached remainder only** — opencode's own `total` is `input + output + cache.read + cache.write` — so the cached part is billed on top of it, not carved out of it, and `contextPercent` is measured against the whole input side. On a resumed turn the uncached remainder is usually a small fraction of the cached part
|
|
362
|
-
- Cost is what opencode reports on each `step_finish` (`part.cost`), so a free model costs $0; the registry estimate applies only when no cost is reported. `opencode run --format json` does not name the model it ran
|
|
363
|
-
- Reasoning effort maps to `--variant`, opencode's provider-specific effort knob. opencode does not validate the value: a level the provider does not offer runs the turn at its default rather than failing
|
|
364
|
-
- `appendSystemPrompt`: opencode has no system-prompt flag, so it is placed at the top of the first message of each new conversation
|
|
365
|
-
- The wrapper closes the subprocess's stdin immediately after spawn (opencode otherwise reads stdin and blocks on EOF, hanging the call)
|
|
366
|
-
- Provider-agnostic: opencode's `--model` expects `provider/model` form (e.g. `anthropic/claude-sonnet-4`). The wrapper passes `--model` through only when the value contains a `/`; otherwise opencode's own default applies
|
|
367
|
-
- `sandboxMode: 'read-only'` runs a generated `clawo-readonly` agent (`--agent clawo-readonly` plus an `OPENCODE_CONFIG_CONTENT` env var defining it):
|
|
368
|
-
- it denies `edit`, `bash`, `external_directory`, `webfetch` and `task` at the permission level, and also removes those tools via the agent's `tools` map
|
|
369
|
-
- denying `task` matters: otherwise the agent can hand a write to a subagent that runs under the default writable agent
|
|
370
|
-
- OpenCode's built-in `plan` agent is not used, because it permits both `bash` and `edit`
|
|
371
|
-
- if the `clawo-readonly` agent fails to load, the turn is refused rather than run with write access
|
|
372
|
-
- to test a change to this config, use adversarial prompts that include asking the agent to delegate; `opencode agent list` shows compiled rules that look the same for a safe and an unsafe agent
|
|
373
|
-
- Requires opencode installed: `brew install sst/tap/opencode` or `npm install -g opencode-ai`. Auth via `opencode auth login` **or** any provider env var (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GEMINI_API_KEY`, etc.) — opencode picks up either path
|
|
374
|
-
- **OpenCode 2 is refused at session start.** Its `run --format json` keeps the event envelope but drops `step_finish` (no tokens or cost) and the `--variant` flag, and the read-only agent could not be verified on it (measured on `0.0.0-beta-17823`, the build `opencode upgrade --major` installs as of October 2026). The session reads `opencode --version` once and refuses a 2.x or `0.0.0-beta` build with that reason; if the version cannot be read, it starts as before.
|
|
375
|
-
- Binary: `opencode` (set `OPENCODE_BIN` env var to override). The message is sent on stdin, never on the command line. **Windows:** the npm `.cmd` shim launches through `src/engine-spawn.ts` (cross-spawn), as for codex above.
|
|
376
|
-
|
|
377
|
-
```typescript
|
|
378
|
-
await manager.startSession({
|
|
379
|
-
name: 'opencode-task',
|
|
380
|
-
engine: 'opencode',
|
|
381
|
-
model: 'anthropic/claude-sonnet-4',
|
|
382
|
-
cwd: '/project',
|
|
383
|
-
});
|
|
384
|
-
```
|
|
405
|
+
### Running an engine under a launcher
|
|
385
406
|
|
|
386
|
-
|
|
407
|
+
`SessionConfig.launcher` is an argv prefix the engine binary runs under, for every
|
|
408
|
+
engine: `launcher: ['/opt/task/sandbox.sh', 'task', '0 1']` spawns
|
|
409
|
+
`/opt/task/sandbox.sh task '0 1' codex app-server …`. This is how a caller puts
|
|
410
|
+
the agent inside its own sandbox (bubblewrap, a container entry point, `taskset`)
|
|
411
|
+
without a custom engine. The launcher is the caller's own command, at the same
|
|
412
|
+
trust level as a contract's command check, which the HTTP API also accepts: set
|
|
413
|
+
it through the SessionManager API, the `session_start` tool, `/session/start` or a
|
|
414
|
+
workflow spec (the `goal` node's `launcher`, which `workflow_start` and
|
|
415
|
+
`clawo goal --launcher` also take). On Windows, a launcher that runs through
|
|
416
|
+
`cmd.exe` (a `.cmd` or `.bat`) is held to the same argv rule as an engine shim: an
|
|
417
|
+
argument with a newline or an unbalanced double quote is refused before spawning,
|
|
418
|
+
so an engine that takes its prompt on the command line needs a launcher that is a
|
|
419
|
+
real executable.
|
|
387
420
|
|
|
388
421
|
## ISession Interface
|
|
389
422
|
|
|
@@ -391,8 +424,11 @@ All engines implement `ISession`, making them interchangeable at the `SessionMan
|
|
|
391
424
|
|
|
392
425
|
```typescript
|
|
393
426
|
interface ISession {
|
|
394
|
-
//
|
|
427
|
+
// Identity
|
|
395
428
|
sessionId?: string;
|
|
429
|
+
readonly pid?: number;
|
|
430
|
+
|
|
431
|
+
// State
|
|
396
432
|
readonly isReady: boolean;
|
|
397
433
|
readonly isPaused: boolean;
|
|
398
434
|
readonly isBusy: boolean;
|
|
@@ -421,26 +457,19 @@ interface ISession {
|
|
|
421
457
|
|
|
422
458
|
// Events (EventEmitter)
|
|
423
459
|
on(event, listener): this;
|
|
460
|
+
once(event, listener): this;
|
|
424
461
|
emit(event, ...args): boolean;
|
|
462
|
+
removeListener(event, listener): this;
|
|
425
463
|
}
|
|
426
464
|
```
|
|
427
465
|
|
|
428
466
|
`SessionStats` requires `turnsSucceeded` as well as `turns`, so an engine that
|
|
429
467
|
implements this interface has to say which of its turns succeeded — the exit code
|
|
430
|
-
is not the answer on every engine. See
|
|
468
|
+
is not the answer on every engine. See [Stats & Monitoring](./sessions.md#stats--monitoring).
|
|
431
469
|
|
|
432
470
|
## Team Tools Across Engines
|
|
433
471
|
|
|
434
|
-
Team tools (`team_list`, `team_send`) operate on the same virtual-team layer for **every** engine: the "team" is the set of all active sessions managed by SessionManager.
|
|
435
|
-
|
|
436
|
-
| Engine | `team_list` | `team_send` |
|
|
437
|
-
| ------------------- | ------------------------------------------ | ------------------------------ |
|
|
438
|
-
| Claude | Lists other active SessionManager sessions | Routes via cross-session inbox |
|
|
439
|
-
| Codex / `codex-app` | Lists other active SessionManager sessions | Routes via cross-session inbox |
|
|
440
|
-
| Antigravity | Lists other active SessionManager sessions | Routes via cross-session inbox |
|
|
441
|
-
| Grok | Lists other active SessionManager sessions | Routes via cross-session inbox |
|
|
442
|
-
| OpenCode | Lists other active SessionManager sessions | Routes via cross-session inbox |
|
|
443
|
-
| Custom | Lists other active SessionManager sessions | Routes via cross-session inbox |
|
|
472
|
+
Team tools (`team_list`, `team_send`) operate on the same virtual-team layer for **every** engine: the "team" is the set of all active sessions managed by SessionManager, whatever engine each one runs. `team_list` lists the other active sessions with their engine and status; `team_send` routes a message through the cross-session inbox.
|
|
444
473
|
|
|
445
474
|
Messages are delivered via the inbox system — idle sessions receive immediately, busy sessions queue for later delivery.
|
|
446
475
|
|
|
@@ -466,20 +495,20 @@ await manager.startSession({
|
|
|
466
495
|
|
|
467
496
|
What happens behind the scenes:
|
|
468
497
|
|
|
469
|
-
1.
|
|
470
|
-
2. Starts a local proxy server (random port, auto-managed)
|
|
498
|
+
1. The session manager reads `~/.openclaw/openclaw.json` for the gateway port + auth (in plugin and `clawo serve` mode alike)
|
|
499
|
+
2. Starts a local proxy server on `127.0.0.1` (random port, auto-managed) the first time a Claude session names a non-Claude model, provided a gateway URL was found
|
|
471
500
|
3. Claude Code CLI sends Anthropic-format requests → proxy converts to OpenAI → gateway → any model
|
|
472
501
|
|
|
473
502
|
### Manual Config (optional)
|
|
474
503
|
|
|
475
504
|
Override with environment variables if needed:
|
|
476
505
|
|
|
477
|
-
| Variable | Default | Description
|
|
478
|
-
| ---------------- | -------------------------------- |
|
|
479
|
-
| `GATEWAY_URL` | Auto-detected from openclaw.json | Gateway endpoint (e.g. `http://127.0.0.1:18789/v1`)
|
|
480
|
-
| `GATEWAY_KEY` | Auto-detected from openclaw.json | Gateway auth password/token
|
|
481
|
-
| `GEMINI_API_KEY` | - | Direct Gemini API access
|
|
482
|
-
| `OPENAI_API_KEY` | - | Direct OpenAI API access
|
|
506
|
+
| Variable | Default | Description |
|
|
507
|
+
| ---------------- | -------------------------------- | --------------------------------------------------------- |
|
|
508
|
+
| `GATEWAY_URL` | Auto-detected from openclaw.json | Gateway endpoint (e.g. `http://127.0.0.1:18789/v1`) |
|
|
509
|
+
| `GATEWAY_KEY` | Auto-detected from openclaw.json | Gateway auth password/token |
|
|
510
|
+
| `GEMINI_API_KEY` | - | Direct Gemini API access, used when no gateway key is set |
|
|
511
|
+
| `OPENAI_API_KEY` | - | Direct OpenAI API access, used when no gateway key is set |
|
|
483
512
|
|
|
484
513
|
### Architecture
|
|
485
514
|
|
|
@@ -496,8 +525,12 @@ Integrate **any** coding agent CLI without writing engine-specific code. You pro
|
|
|
496
525
|
|
|
497
526
|
Two protocol modes:
|
|
498
527
|
|
|
499
|
-
- **Persistent** (`persistent: true`) — long-running subprocess with stream-json I/O over stdin/stdout (like Claude Code)
|
|
500
|
-
- **One-shot** (`persistent: false`, default) — new process spawned per `send()` (
|
|
528
|
+
- **Persistent** (`persistent: true`) — long-running subprocess with stream-json I/O over stdin/stdout (like Claude Code). It keeps the conversation; `args.resume` passes `resumeSessionId` when a session is restarted
|
|
529
|
+
- **One-shot** (`persistent: false`, default) — new process spawned per `send()`, with the message on the command line right after `args.print`. Nothing carries over between sends. On Windows, a CLI that runs through `cmd.exe` (an npm `.cmd` shim) cannot take a message containing a newline or an unbalanced double quote this way; use persistent mode
|
|
530
|
+
|
|
531
|
+
`appendSystemPrompt` goes through `args.appendSystemPrompt` when the config maps it. Without that flag it is put at the top of the first message of a conversation: every message in one-shot mode, the first one in persistent mode unless the session resumed through `args.resume`.
|
|
532
|
+
|
|
533
|
+
`sandboxMode: 'read-only'` needs both `args.permissionMode` and a `permissionModes` entry for the session's permission mode that maps to the CLI's read-only or plan value. Without them the CLI is never run writable: a persistent session refuses to start, and a one-shot session refuses every send.
|
|
501
534
|
|
|
502
535
|
### CustomEngineConfig
|
|
503
536
|
|
|
@@ -509,11 +542,17 @@ Two protocol modes:
|
|
|
509
542
|
| `persistent` | boolean | | `true` = persistent subprocess, `false` = one-shot (default) |
|
|
510
543
|
| `args` | object | yes | CLI flag mappings (see below) |
|
|
511
544
|
| `permissionModes` | object | | Maps OpenClaw mode names to CLI-specific values |
|
|
512
|
-
| `pricing` | object | | `{ input, output, cached? }` per 1M tokens
|
|
545
|
+
| `pricing` | object | | `{ input, output, cached? }` per 1M tokens (see below) |
|
|
513
546
|
| `contextWindow` | number | | Context window size (default: 200,000) |
|
|
514
547
|
| `env` | object | | Extra environment variables for the CLI process |
|
|
515
548
|
| `sanitizePatterns` | string[] | | Regex patterns to redact from stderr |
|
|
516
549
|
|
|
550
|
+
Cost is the reported tokens priced at the registry rate for the session's `model`,
|
|
551
|
+
or at its `pricingOverrides` entry when it has one. An unregistered model, or no
|
|
552
|
+
model, is priced at `pricing`, and at the `claude-sonnet-4-6` rate when the engine
|
|
553
|
+
sets none. With no model, a `pricingOverrides` entry for `claude-sonnet-4-6` still
|
|
554
|
+
takes precedence over `pricing`.
|
|
555
|
+
|
|
517
556
|
### args field
|
|
518
557
|
|
|
519
558
|
| Key | Example | Description |
|
|
@@ -523,14 +562,14 @@ Two protocol modes:
|
|
|
523
562
|
| `outputFormatValue` | `"stream-json"` | Value for stream-json output |
|
|
524
563
|
| `inputFormat` | `"--input-format"` | Input format flag (persistent only) |
|
|
525
564
|
| `inputFormatValue` | `"stream-json"` | Value for stream-json input |
|
|
526
|
-
| `skipPermissions` | `"-y"` |
|
|
565
|
+
| `skipPermissions` | `"-y"` | Passed when `dangerouslySkipPermissions` |
|
|
527
566
|
| `permissionMode` | `"--permission-mode"` | Permission mode flag |
|
|
528
567
|
| `model` | `"--model"` | Model selection flag |
|
|
529
568
|
| `systemPrompt` | `"--system-prompt"` | System prompt override flag |
|
|
530
569
|
| `appendSystemPrompt` | `"--append-system-prompt"` | Append system prompt flag |
|
|
531
570
|
| `maxTurns` | `"--max-turns"` | Max agent turns flag |
|
|
532
571
|
| `resume` | `"--resume"` | Session resume flag (persistent only) |
|
|
533
|
-
| `verbose` | `"--verbose"` | Verbose output flag
|
|
572
|
+
| `verbose` | `"--verbose"` | Verbose output flag (persistent only) |
|
|
534
573
|
| `replayUserMessages` | `"--replay-user-messages"` | Replay user messages (persistent only) |
|
|
535
574
|
| `includePartialMessages` | `"--include-partial-messages"` | Include partial messages (persistent only) |
|
|
536
575
|
| `effort` | `"--effort"` | Effort level flag |
|
|
@@ -602,10 +641,6 @@ await manager.startSession({
|
|
|
602
641
|
});
|
|
603
642
|
```
|
|
604
643
|
|
|
605
|
-
### Example: Google Antigravity CLI (`agy`)
|
|
606
|
-
|
|
607
|
-
Use the built-in [`engine: 'agy'`](#google-antigravity-engine-agy) instead of a custom config.
|
|
608
|
-
|
|
609
644
|
### Custom Engine in Council
|
|
610
645
|
|
|
611
646
|
Custom engines work in council by setting `engine: 'custom'` and `customEngine` on the agent persona:
|
|
@@ -633,9 +668,10 @@ To add a built-in engine (for CLIs that need custom protocol handling beyond wha
|
|
|
633
668
|
|
|
634
669
|
1. Create `src/persistent-<engine>-session.ts` implementing `ISession`
|
|
635
670
|
2. Add the engine name to `ENGINE_TYPES` (from which `EngineType` is derived) and its binary to `ENGINE_BINARY_NAMES` in `src/types.ts`
|
|
636
|
-
3. Add a case to `engineHasNativeConversation()` in `src/types.ts` and, if the engine resumes a conversation by id, to `nativeThreadIsLive()` in `src/openai-compat.ts`
|
|
671
|
+
3. Add a case to `engineHasNativeConversation()` in `src/types.ts` and, if the engine resumes a conversation by id, to `nativeThreadIsLive()` in `src/openai-compat.ts` and to `_sessionResumeId()` / `_storedResumeId()` in `src/session-manager.ts`, so a restart replays an id the engine can resume rather than a synthetic wrapper id
|
|
637
672
|
4. Add a case to `SessionManager._createSession()`
|
|
638
673
|
5. Add model pricing to `MODELS[]` in `src/models.ts`
|
|
674
|
+
6. For a core engine: add it to `ENGINES` in `scripts/sweep.ts` and pin its tested version in the engine table of `AGENTS.md`
|
|
639
675
|
|
|
640
676
|
The `ISession` interface is deliberately minimal — each engine handles its own subprocess bootstrapping, I/O protocol, and cleanup internally.
|
|
641
677
|
|