* feat(openclaw): support connecting to existing OpenClaw gateway (#3260)
When the daemon host is a lightweight dev machine or CI coordinator, the
heavy agent work (LLM inference, code execution, tool use) often belongs
on a more powerful remote server already running an OpenClaw gateway.
Multica historically hard-coded `openclaw agent --local`, forcing every
turn to execute in-process on the daemon host.
This change adds an opt-in gateway routing mode controlled per-agent via
`runtime_config`:
{
"mode": "gateway",
"gateway": { "host": "...", "port": 18789, "token": "...", "tls": false }
}
- Backend: ExecOptions gains OpenclawMode + OpenclawGateway; buildOpenclawArgs
drops `--local` when mode == "gateway". Per-task openclaw-config.json
wrapper pins gateway.{host,port,auth.{mode,token},tls} so users do not
need to edit the daemon host's `~/.openclaw/openclaw.json` to point at
a different endpoint.
- Daemon: AgentData carries the raw runtime_config; decoding is fail-soft
(malformed JSON falls back to local mode rather than blocking dispatch).
- API: gateway.token is masked to "***" on every GET; PATCH replays the
sentinel back, and the update handler restores the persisted token so
the round-trip never destroys the secret. Defense-in-depth masking on
WS broadcasts, plus String/MarshalJSON masking on the in-memory struct
to block stray `%+v` / json.Marshal leaks.
- UI: openclaw-only "Routing" tab on the agent detail page with mode
selector + structured endpoint form. Token uses a "saved — submit a
new value to rotate" UX and matching backend preserve hook.
Empty `runtime_config` keeps the historical embedded behaviour, so
existing agents are unaffected.
* fix(openclaw): address #3664 review — drop dead gateway field, gate pin on mode
Per Bohan-J's review:
- Remove the dead ExecOptions.OpenclawGateway field (+ its String/MarshalJSON and
the daemon.go construction block). It carried the plaintext bearer token but was
never read — buildOpenclawArgs only consumes OpenclawMode and the live gateway
path runs through execenv.OpenclawGatewayPin — so this narrows the secret's
footprint.
- Gate the gateway pin on mode=="gateway" in decodeOpenclawRuntimeConfig: a
{"mode":"local","gateway":{...,"token"}} payload no longer writes the token into
the 0o600 per-task wrapper that --local makes openclaw ignore.
- Warn on an unrecognized non-empty mode (e.g. "gatway") instead of silently
falling back to local.
- Run preserveMaskedGatewayToken in CreateAgent too, so a literal "***" at create
time can't persist as a real bearer token.
- Document the gateway host:port trust boundary (SSRF note for shared daemon hosts).
Adds regression tests for the local-mode pin drop and the unknown-mode warning.