@enderfga/claw-orchestrator 7.14.0 → 7.16.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 +162 -135
- package/configs/autoloop-reviewer-prompt.md +1 -1
- package/configs/engines/README.md +31 -11
- package/dist/bin/acp-server.js +7 -12
- package/dist/bin/acp-server.js.map +1 -1
- package/dist/bin/cli.js +84 -46
- 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 +149 -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 +72 -9
- 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/claude-goal-session.d.ts +147 -0
- package/dist/src/claude-goal-session.js +355 -0
- package/dist/src/claude-goal-session.js.map +1 -0
- 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 +27 -8
- package/dist/src/embedded-server.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 +135 -53
- package/dist/src/index.js.map +1 -1
- package/dist/src/kernel/agent-step.d.ts +6 -0
- package/dist/src/kernel/agent-step.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 +19 -5
- package/dist/src/kernel/nodes/goal.js +81 -34
- 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 +16 -11
- package/dist/src/kernel/templates/index.js +14 -8
- package/dist/src/kernel/templates/index.js.map +1 -1
- package/dist/src/kernel/types.d.ts +37 -13
- 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 +7 -0
- package/dist/src/persistent-acp-session.js +26 -2
- package/dist/src/persistent-acp-session.js.map +1 -1
- 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/persistent-session.d.ts +9 -0
- package/dist/src/persistent-session.js +23 -0
- package/dist/src/persistent-session.js.map +1 -1
- package/dist/src/run-ledger.d.ts +3 -3
- package/dist/src/session-manager.d.ts +45 -1
- package/dist/src/session-manager.js +199 -34
- package/dist/src/session-manager.js.map +1 -1
- package/dist/src/types.d.ts +34 -11
- 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 -61
- package/skills/references/autoloop.md +148 -38
- package/skills/references/claude-cli-tracking.md +9 -6
- package/skills/references/cli.md +139 -108
- 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 +161 -145
- 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 +217 -158
- 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 +275 -97
- 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,43 +294,49 @@ 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
|
+
|
|
319
340
|
### ACP agents (`engine: 'acp'`)
|
|
320
341
|
|
|
321
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.
|
|
@@ -326,12 +347,16 @@ Drives any agent that serves the [Agent Client Protocol](https://agentclientprot
|
|
|
326
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.
|
|
327
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.
|
|
328
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.
|
|
329
|
-
- The
|
|
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:
|
|
330
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.
|
|
331
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.
|
|
332
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.
|
|
333
357
|
|
|
334
358
|
```bash
|
|
359
|
+
clawo fanout "How would you cut this test suite's run time?" -e claude,acp:kimi-code,acp:dsh --synthesize --wait
|
|
335
360
|
clawo session-start kimi -e acp --acp-agent kimi-code --permission-mode bypassPermissions -d ./project
|
|
336
361
|
clawo session-send kimi "Fix the failing test in tests/test_io.py"
|
|
337
362
|
```
|
|
@@ -339,8 +364,13 @@ clawo session-send kimi "Fix the failing test in tests/test_io.py"
|
|
|
339
364
|
### Community engine presets
|
|
340
365
|
|
|
341
366
|
`customEngine` accepts either an inline `CustomEngineConfig` or the id of a preset
|
|
342
|
-
bundled in `configs/engines
|
|
343
|
-
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).
|
|
344
374
|
|
|
345
375
|
Engines fall into three tiers, decided by how they are verified. Bundled presets
|
|
346
376
|
are always `community`:
|
|
@@ -353,57 +383,40 @@ are always `community`:
|
|
|
353
383
|
|
|
354
384
|
A community preset must name its maintainer and the engine version and date it
|
|
355
385
|
was verified against (`provenance.maintainer`, `provenance.verifiedAgainst`,
|
|
356
|
-
`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.
|
|
357
388
|
|
|
358
389
|
A preset never carries protocol translation. A CLI speaking its own wire format
|
|
359
390
|
needs an adapter binary in its author's package, with the preset pointing `bin`
|
|
360
|
-
at it — as
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
the
|
|
368
|
-
`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.
|
|
369
402
|
|
|
370
403
|
Contribution steps and the smoke script: [CONTRIBUTING.md](../../CONTRIBUTING.md#contributing-an-engine).
|
|
371
404
|
|
|
372
|
-
###
|
|
373
|
-
|
|
374
|
-
Wraps the [sst/opencode](https://github.com/sst/opencode) CLI with `run --format json`. Each `send()` spawns a new process.
|
|
375
|
-
|
|
376
|
-
- 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.
|
|
377
|
-
- One-shot execution per message (no persistent subprocess)
|
|
378
|
-
- NDJSON event stream with envelope `{ type, timestamp, sessionID, ... }`
|
|
379
|
-
- Event types: `text`, `reasoning`, `tool_use`, `step_start`, `step_finish`, `error`
|
|
380
|
-
- `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
|
|
381
|
-
- 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
|
|
382
|
-
- 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
|
|
383
|
-
- 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
|
|
384
|
-
- `appendSystemPrompt`: opencode has no system-prompt flag, so it is placed at the top of the first message of each new conversation
|
|
385
|
-
- The wrapper closes the subprocess's stdin immediately after spawn (opencode otherwise reads stdin and blocks on EOF, hanging the call)
|
|
386
|
-
- 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
|
|
387
|
-
- `sandboxMode: 'read-only'` runs a generated `clawo-readonly` agent (`--agent clawo-readonly` plus an `OPENCODE_CONFIG_CONTENT` env var defining it):
|
|
388
|
-
- it denies `edit`, `bash`, `external_directory`, `webfetch` and `task` at the permission level, and also removes those tools via the agent's `tools` map
|
|
389
|
-
- denying `task` matters: otherwise the agent can hand a write to a subagent that runs under the default writable agent
|
|
390
|
-
- OpenCode's built-in `plan` agent is not used, because it permits both `bash` and `edit`
|
|
391
|
-
- if the `clawo-readonly` agent fails to load, the turn is refused rather than run with write access
|
|
392
|
-
- 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
|
|
393
|
-
- 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
|
|
394
|
-
- **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.
|
|
395
|
-
- 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.
|
|
396
|
-
|
|
397
|
-
```typescript
|
|
398
|
-
await manager.startSession({
|
|
399
|
-
name: 'opencode-task',
|
|
400
|
-
engine: 'opencode',
|
|
401
|
-
model: 'anthropic/claude-sonnet-4',
|
|
402
|
-
cwd: '/project',
|
|
403
|
-
});
|
|
404
|
-
```
|
|
405
|
+
### Running an engine under a launcher
|
|
405
406
|
|
|
406
|
-
|
|
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.
|
|
407
420
|
|
|
408
421
|
## ISession Interface
|
|
409
422
|
|
|
@@ -411,8 +424,11 @@ All engines implement `ISession`, making them interchangeable at the `SessionMan
|
|
|
411
424
|
|
|
412
425
|
```typescript
|
|
413
426
|
interface ISession {
|
|
414
|
-
//
|
|
427
|
+
// Identity
|
|
415
428
|
sessionId?: string;
|
|
429
|
+
readonly pid?: number;
|
|
430
|
+
|
|
431
|
+
// State
|
|
416
432
|
readonly isReady: boolean;
|
|
417
433
|
readonly isPaused: boolean;
|
|
418
434
|
readonly isBusy: boolean;
|
|
@@ -441,26 +457,19 @@ interface ISession {
|
|
|
441
457
|
|
|
442
458
|
// Events (EventEmitter)
|
|
443
459
|
on(event, listener): this;
|
|
460
|
+
once(event, listener): this;
|
|
444
461
|
emit(event, ...args): boolean;
|
|
462
|
+
removeListener(event, listener): this;
|
|
445
463
|
}
|
|
446
464
|
```
|
|
447
465
|
|
|
448
466
|
`SessionStats` requires `turnsSucceeded` as well as `turns`, so an engine that
|
|
449
467
|
implements this interface has to say which of its turns succeeded — the exit code
|
|
450
|
-
is not the answer on every engine. See
|
|
468
|
+
is not the answer on every engine. See [Stats & Monitoring](./sessions.md#stats--monitoring).
|
|
451
469
|
|
|
452
470
|
## Team Tools Across Engines
|
|
453
471
|
|
|
454
|
-
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.
|
|
455
|
-
|
|
456
|
-
| Engine | `team_list` | `team_send` |
|
|
457
|
-
| ------------------- | ------------------------------------------ | ------------------------------ |
|
|
458
|
-
| Claude | Lists other active SessionManager sessions | Routes via cross-session inbox |
|
|
459
|
-
| Codex / `codex-app` | Lists other active SessionManager sessions | Routes via cross-session inbox |
|
|
460
|
-
| Antigravity | Lists other active SessionManager sessions | Routes via cross-session inbox |
|
|
461
|
-
| Grok | Lists other active SessionManager sessions | Routes via cross-session inbox |
|
|
462
|
-
| OpenCode | Lists other active SessionManager sessions | Routes via cross-session inbox |
|
|
463
|
-
| 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.
|
|
464
473
|
|
|
465
474
|
Messages are delivered via the inbox system — idle sessions receive immediately, busy sessions queue for later delivery.
|
|
466
475
|
|
|
@@ -486,20 +495,20 @@ await manager.startSession({
|
|
|
486
495
|
|
|
487
496
|
What happens behind the scenes:
|
|
488
497
|
|
|
489
|
-
1.
|
|
490
|
-
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
|
|
491
500
|
3. Claude Code CLI sends Anthropic-format requests → proxy converts to OpenAI → gateway → any model
|
|
492
501
|
|
|
493
502
|
### Manual Config (optional)
|
|
494
503
|
|
|
495
504
|
Override with environment variables if needed:
|
|
496
505
|
|
|
497
|
-
| Variable | Default | Description
|
|
498
|
-
| ---------------- | -------------------------------- |
|
|
499
|
-
| `GATEWAY_URL` | Auto-detected from openclaw.json | Gateway endpoint (e.g. `http://127.0.0.1:18789/v1`)
|
|
500
|
-
| `GATEWAY_KEY` | Auto-detected from openclaw.json | Gateway auth password/token
|
|
501
|
-
| `GEMINI_API_KEY` | - | Direct Gemini API access
|
|
502
|
-
| `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 |
|
|
503
512
|
|
|
504
513
|
### Architecture
|
|
505
514
|
|
|
@@ -516,8 +525,12 @@ Integrate **any** coding agent CLI without writing engine-specific code. You pro
|
|
|
516
525
|
|
|
517
526
|
Two protocol modes:
|
|
518
527
|
|
|
519
|
-
- **Persistent** (`persistent: true`) — long-running subprocess with stream-json I/O over stdin/stdout (like Claude Code)
|
|
520
|
-
- **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.
|
|
521
534
|
|
|
522
535
|
### CustomEngineConfig
|
|
523
536
|
|
|
@@ -529,11 +542,17 @@ Two protocol modes:
|
|
|
529
542
|
| `persistent` | boolean | | `true` = persistent subprocess, `false` = one-shot (default) |
|
|
530
543
|
| `args` | object | yes | CLI flag mappings (see below) |
|
|
531
544
|
| `permissionModes` | object | | Maps OpenClaw mode names to CLI-specific values |
|
|
532
|
-
| `pricing` | object | | `{ input, output, cached? }` per 1M tokens
|
|
545
|
+
| `pricing` | object | | `{ input, output, cached? }` per 1M tokens (see below) |
|
|
533
546
|
| `contextWindow` | number | | Context window size (default: 200,000) |
|
|
534
547
|
| `env` | object | | Extra environment variables for the CLI process |
|
|
535
548
|
| `sanitizePatterns` | string[] | | Regex patterns to redact from stderr |
|
|
536
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
|
+
|
|
537
556
|
### args field
|
|
538
557
|
|
|
539
558
|
| Key | Example | Description |
|
|
@@ -543,14 +562,14 @@ Two protocol modes:
|
|
|
543
562
|
| `outputFormatValue` | `"stream-json"` | Value for stream-json output |
|
|
544
563
|
| `inputFormat` | `"--input-format"` | Input format flag (persistent only) |
|
|
545
564
|
| `inputFormatValue` | `"stream-json"` | Value for stream-json input |
|
|
546
|
-
| `skipPermissions` | `"-y"` |
|
|
565
|
+
| `skipPermissions` | `"-y"` | Passed when `dangerouslySkipPermissions` |
|
|
547
566
|
| `permissionMode` | `"--permission-mode"` | Permission mode flag |
|
|
548
567
|
| `model` | `"--model"` | Model selection flag |
|
|
549
568
|
| `systemPrompt` | `"--system-prompt"` | System prompt override flag |
|
|
550
569
|
| `appendSystemPrompt` | `"--append-system-prompt"` | Append system prompt flag |
|
|
551
570
|
| `maxTurns` | `"--max-turns"` | Max agent turns flag |
|
|
552
571
|
| `resume` | `"--resume"` | Session resume flag (persistent only) |
|
|
553
|
-
| `verbose` | `"--verbose"` | Verbose output flag
|
|
572
|
+
| `verbose` | `"--verbose"` | Verbose output flag (persistent only) |
|
|
554
573
|
| `replayUserMessages` | `"--replay-user-messages"` | Replay user messages (persistent only) |
|
|
555
574
|
| `includePartialMessages` | `"--include-partial-messages"` | Include partial messages (persistent only) |
|
|
556
575
|
| `effort` | `"--effort"` | Effort level flag |
|
|
@@ -622,10 +641,6 @@ await manager.startSession({
|
|
|
622
641
|
});
|
|
623
642
|
```
|
|
624
643
|
|
|
625
|
-
### Example: Google Antigravity CLI (`agy`)
|
|
626
|
-
|
|
627
|
-
Use the built-in [`engine: 'agy'`](#google-antigravity-engine-agy) instead of a custom config.
|
|
628
|
-
|
|
629
644
|
### Custom Engine in Council
|
|
630
645
|
|
|
631
646
|
Custom engines work in council by setting `engine: 'custom'` and `customEngine` on the agent persona:
|
|
@@ -653,9 +668,10 @@ To add a built-in engine (for CLIs that need custom protocol handling beyond wha
|
|
|
653
668
|
|
|
654
669
|
1. Create `src/persistent-<engine>-session.ts` implementing `ISession`
|
|
655
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`
|
|
656
|
-
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
|
|
657
672
|
4. Add a case to `SessionManager._createSession()`
|
|
658
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`
|
|
659
675
|
|
|
660
676
|
The `ISession` interface is deliberately minimal — each engine handles its own subprocess bootstrapping, I/O protocol, and cleanup internally.
|
|
661
677
|
|