OpenClaw's `--json` result blob carries the actual LLM identifier in
`meta.agentMeta.model` (e.g. `deepseek-chat`, `claude-sonnet-4`),
alongside `provider` and the usage breakdown. The backend was reading
the surrounding `agentMeta.usage` and `agentMeta.sessionId` but skipping
the `model` field entirely, then attributing every run's tokens to
`opts.Model` — which for openclaw is the *agent name* passed via
`--agent`, not a real model identifier — falling all the way through to
"unknown" when no agent.model was configured.
Surface the runtime-reported model:
- `openclawEventResult` gains a `model` string.
- `buildOpenclawEventResult` reads `agentMeta.model` (trimmed; empty
string when absent for forward-compat with older runtimes / partial
outputs).
- `processOutput` propagates it through the result-blob branch.
- `Execute`'s usage map prefers `scanResult.model`, falling back to
`opts.Model` then `"unknown"` — preserving the prior behavior path
for any runtime that doesn't surface its own model yet.
Two unit tests cover both the populated and missing cases.
Refs: #1395
Adds a first-class `model` field on agents so users can pick the LLM model from the create / settings UI instead of editing `custom_env` / `custom_args`. Each provider's dropdown is populated from the live CLI when possible (`opencode models`, `pi --list-models`, `openclaw agents list --json`, `cursor-agent --list-models`, hermes ACP `session/new` → `SessionModelState`), with a static catalog for providers that don't enumerate.
Daemon resolves the runtime model as `agent.model → MULTICA_<PROVIDER>_MODEL → ""` — empty passes through so each backend's CLI picks its own default, avoiding static-guess drift.
Per-provider honouring:
- Claude / Codex / OpenCode / Cursor / Gemini / Pi / Copilot — CLI `--model` / thread payload.
- OpenClaw — `opts.Model` is mapped to `--agent <name>` (the CLI rejects `--model`).
- Hermes — `session/set_model` ACP RPC; stderr is sniffed for provider-level errors so HTTP 4xx from the configured LLM surfaces instead of "empty output"; explicit-model failures mark the task `failed`.
Supporting changes: migration 050 adds `agent.model`; daemon ↔ server heartbeat piggyback carries a model-discovery request; new REST endpoints under `/api/runtimes/{id}/models`; `multica agent create --model` / `update --model`; shared `ModelDropdown` in `packages/views/agents` (searchable, creatable, provider-grouped, default-badge, runtime-supported gate).
Fixes#1332.
Two regressions introduced in #910 (2026-04-14, "OpenClaw backend P0+P1
improvements") that together block all openclaw users:
1. `openclaw agent` does not accept `--model` or `--system-prompt`, so
any agent configured with a Model field crashed in ~700ms with
`exit status 1`. Remove both forwards, and add them to
openclawBlockedArgs so custom_args can't reintroduce the crash.
Model is bound at registration time via `openclaw agents
add/update --model`.
2. AgentInstructions were written to `{workDir}/AGENTS.md` by
execenv.InjectRuntimeConfig, but openclaw loads bootstrap files
from its own workspace dir — the file was never read, so every
agent's Instructions field was silently discarded. Populate
opts.SystemPrompt for the openclaw provider in runTask and
prepend it to the `--message` payload in the backend so the
model actually receives the instructions.
Other providers surface instructions through their native runtime
config file (CLAUDE.md / AGENTS.md / GEMINI.md) and are intentionally
left unchanged to avoid double injection.
Extract buildOpenclawArgs so arg construction is directly testable;
add unit tests covering the removed flags, the SystemPrompt prepend,
and custom_args filtering.
OpenClaw outputs its --json result as pretty-printed multi-line JSON to
stderr. The line-by-line scanner never found a valid JSON object on any
single line, causing the raw JSON to be returned as the chat response.
After exhausting line-by-line parsing, try parsing the accumulated
output as a whole before falling back to raw text.
Closes MUL-725
Combined P0 and P1 improvements to the OpenClaw agent backend, informed
by PaperClip's adapter architecture:
P0 — User experience:
- Streaming output — emit MessageText as NDJSON events arrive in real
time, instead of waiting for the final result blob
- Tool use support — parse and emit MessageToolUse/MessageToolResult
from streaming events, matching Claude and OpenCode backends
- Model & system prompt — pass --model and --system-prompt to the
OpenClaw CLI when configured
P1 — Robustness:
- Hardened JSON parsing — tryParseOpenclawResult requires lines to
start with '{', eliminating fragile brace-scanning that could
false-match JSON fragments in log lines
- Lifecycle event handling — new "lifecycle" event type with phase
tracking (error/failed/cancelled), plus structured error objects
(error.name, error.data.message) matching PaperClip's pattern
- Usage field name variants — parseOpenclawUsage supports multiple
naming conventions (input/inputTokens/input_tokens, cacheRead/
cachedInputTokens/cache_read_input_tokens, etc.) with incremental
accumulation across step_finish events
Backwards compatible with the legacy single JSON blob format.
31 tests covering all new functionality.
Closes MUL-726
processOutput() used strings.Index(raw, "{") to find the JSON start,
but error lines like `raw_params={"command":"..."}` contain braces that
get matched first, causing JSON parsing to fail and the entire raw
stderr (including internal metadata) to be returned as the agent comment.
Now tries each '{' position until one successfully unmarshals as a valid
openclawResult, skipping braces embedded in log/error lines.
* fix(cli): poll health endpoint instead of fixed sleep in daemon start
The daemon start command waited a fixed 2 seconds then checked the
health endpoint once. If the daemon took longer to initialize (auth,
workspace loading), the check failed and printed a misleading error
even though the daemon started successfully.
Replace the single check with a polling loop (500ms interval, 15s
timeout) so the CLI waits for the daemon to actually be ready.
* fix(agent): rewrite openclaw tests to match new backend API
The openclaw backend was rewritten in #715 to parse a single JSON blob
instead of streaming NDJSON events. The tests still referenced the old
types (openclawEvent) and methods (handleOCTextEvent, etc.), causing a
build failure in CI.
Rewrite all tests to exercise the new processOutput method and
openclawInt64 helper.
- Remove duplicate extractOCToolOutput, reuse extractToolOutput from opencode.go
- Rename extractEventText → openclawExtractText to avoid package-level name collisions
- Add clarifying comments for error status stickiness and result event behavior
- Remove redundant extractOCToolOutput tests (already covered by opencode tests)
Add OpenClaw as a fourth supported agent runtime alongside Claude Code,
Codex, and OpenCode. OpenClaw CLI (`openclaw agent -p ... --output-format
stream-json`) is integrated via the same Backend interface pattern.
Changes:
- Add openclawBackend in server/pkg/agent/openclaw.go with NDJSON
event stream parsing (text, thinking, tool_call, error, step, result)
- Register "openclaw" in the agent factory (agent.go)
- Add MULTICA_OPENCLAW_PATH / MULTICA_OPENCLAW_MODEL env var detection
in daemon config
- Include "openclaw" in AGENTS.md config injection alongside codex/opencode
- Add comprehensive unit tests for all event handlers and processEvents