Compare commits

..

1 Commits

Author SHA1 Message Date
Kevin Lin
fc26444f6d fix: preserve runtime alias with compat wrappers 2026-05-07 15:11:29 -07:00
2278 changed files with 17304 additions and 73274 deletions

View File

@@ -1742,17 +1742,7 @@ jobs:
with:
install-bun: "false"
- name: Checkout ClawHub docs source
uses: actions/checkout@v6
with:
repository: openclaw/clawhub
path: clawhub-source
fetch-depth: 1
persist-credentials: false
- name: Check docs
env:
OPENCLAW_DOCS_SYNC_CLAWHUB_REPO: ${{ github.workspace }}/clawhub-source
run: pnpm check:docs
skills-python:

View File

@@ -22,15 +22,6 @@ jobs:
with:
fetch-depth: 0
- name: Checkout ClawHub docs source
uses: actions/checkout@v6
with:
repository: openclaw/clawhub
path: clawhub-source
fetch-depth: 1
persist-credentials: false
token: ${{ secrets.OPENCLAW_DOCS_SYNC_TOKEN || github.token }}
- name: Setup Node
uses: actions/setup-node@v6
with:
@@ -57,17 +48,12 @@ jobs:
- name: Sync docs into publish repo
run: |
clawhub_sha="$(git -C "$GITHUB_WORKSPACE/clawhub-source" rev-parse HEAD)"
node scripts/docs-sync-publish.mjs \
--target "$GITHUB_WORKSPACE/publish" \
--source-repo "$GITHUB_REPOSITORY" \
--source-sha "$GITHUB_SHA" \
--clawhub-repo "$GITHUB_WORKSPACE/clawhub-source" \
--clawhub-source-repo "openclaw/clawhub" \
--clawhub-source-sha "$clawhub_sha"
--source-sha "$GITHUB_SHA"
- name: Install docs MDX checker dependency
working-directory: publish
run: npm install --no-save --package-lock=false @mdx-js/mdx@3.1.1
- name: Check publish docs MDX

View File

@@ -98,5 +98,5 @@ jobs:
echo "- Run \`openclaw/releases-private/.github/workflows/openclaw-macos-validate.yml\` with tag \`${RELEASE_TAG}\` and wait for the private mac validation lane to pass."
echo "- Run \`openclaw/releases-private/.github/workflows/openclaw-macos-publish.yml\` with tag \`${RELEASE_TAG}\` and \`preflight_only=true\` for the full private mac preflight."
echo "- For the real publish path, run the same private mac publish workflow from \`main\` with the successful private preflight \`preflight_run_id\` so it promotes the prepared artifacts instead of rebuilding them."
echo "- For stable releases, the private publish workflow also publishes the signed \`appcast.xml\` to public \`main\`, or opens an appcast PR if direct push is blocked."
echo "- For stable releases, also download \`macos-appcast-${RELEASE_TAG}\` from the successful private run and commit \`appcast.xml\` back to \`main\` in \`openclaw/openclaw\`."
} >> "$GITHUB_STEP_SUMMARY"

View File

@@ -2135,8 +2135,8 @@ jobs:
# inside the already-isolated container to keep MCP cron/tool
# execution representative instead of failing on nested sandbox
# setup.
echo 'OPENCLAW_LIVE_CLI_BACKEND_ARGS=["exec","--json","--color","never","--sandbox","danger-full-access","-c","service_tier=\"priority\"","--skip-git-repo-check"]' >> "$GITHUB_ENV"
echo 'OPENCLAW_LIVE_CLI_BACKEND_RESUME_ARGS=["exec","resume","{sessionId}","-c","sandbox_mode=\"danger-full-access\"","-c","service_tier=\"priority\"","--skip-git-repo-check"]' >> "$GITHUB_ENV"
echo 'OPENCLAW_LIVE_CLI_BACKEND_ARGS=["exec","--json","--color","never","--sandbox","danger-full-access","-c","service_tier=\"fast\"","--skip-git-repo-check"]' >> "$GITHUB_ENV"
echo 'OPENCLAW_LIVE_CLI_BACKEND_RESUME_ARGS=["exec","resume","{sessionId}","-c","sandbox_mode=\"danger-full-access\"","-c","service_tier=\"fast\"","--skip-git-repo-check"]' >> "$GITHUB_ENV"
echo "OPENCLAW_LIVE_CLI_BACKEND_DEBUG=1" >> "$GITHUB_ENV"
echo "OPENCLAW_CLI_BACKEND_LOG_OUTPUT=1" >> "$GITHUB_ENV"
echo "OPENCLAW_TEST_CONSOLE=1" >> "$GITHUB_ENV"
@@ -2354,8 +2354,8 @@ jobs:
live-cli-backend-docker)
echo "OPENCLAW_LIVE_CLI_BACKEND_MODEL=codex-cli/gpt-5.4" >> "$GITHUB_ENV"
echo "OPENCLAW_LIVE_CLI_BACKEND_AUTH=api-key" >> "$GITHUB_ENV"
echo 'OPENCLAW_LIVE_CLI_BACKEND_ARGS=["exec","--json","--color","never","--sandbox","danger-full-access","-c","service_tier=\"priority\"","--skip-git-repo-check"]' >> "$GITHUB_ENV"
echo 'OPENCLAW_LIVE_CLI_BACKEND_RESUME_ARGS=["exec","resume","{sessionId}","-c","sandbox_mode=\"danger-full-access\"","-c","service_tier=\"priority\"","--skip-git-repo-check"]' >> "$GITHUB_ENV"
echo 'OPENCLAW_LIVE_CLI_BACKEND_ARGS=["exec","--json","--color","never","--sandbox","danger-full-access","-c","service_tier=\"fast\"","--skip-git-repo-check"]' >> "$GITHUB_ENV"
echo 'OPENCLAW_LIVE_CLI_BACKEND_RESUME_ARGS=["exec","resume","{sessionId}","-c","sandbox_mode=\"danger-full-access\"","-c","service_tier=\"fast\"","--skip-git-repo-check"]' >> "$GITHUB_ENV"
echo "OPENCLAW_LIVE_CLI_BACKEND_DEBUG=1" >> "$GITHUB_ENV"
echo "OPENCLAW_CLI_BACKEND_LOG_OUTPUT=1" >> "$GITHUB_ENV"
echo "OPENCLAW_TEST_CONSOLE=1" >> "$GITHUB_ENV"

View File

@@ -599,7 +599,7 @@ jobs:
published_upgrade_survivor_baselines: ${{ needs.resolve_target.outputs.run_release_soak == 'true' && 'last-stable-4 2026.4.23 2026.5.2 2026.4.15' || '' }}
published_upgrade_survivor_scenarios: ${{ needs.resolve_target.outputs.run_release_soak == 'true' && 'reported-issues' || '' }}
telegram_mode: mock-openai
telegram_scenarios: telegram-help-command,telegram-commands-command,telegram-tools-compact-command,telegram-whoami-command,telegram-status-command,telegram-other-bot-command-gating,telegram-context-command,telegram-mentioned-message-reply,telegram-reply-chain-exact-marker,telegram-stream-final-single-message,telegram-long-final-reuses-preview,telegram-mention-gating
telegram_scenarios: telegram-help-command,telegram-commands-command,telegram-tools-compact-command,telegram-whoami-command,telegram-context-command,telegram-current-session-status-tool,telegram-mention-gating
secrets:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
OPENAI_BASE_URL: ${{ secrets.OPENAI_BASE_URL }}

4
.gitignore vendored
View File

@@ -95,10 +95,6 @@ docs/internal/
tmp/
IDENTITY.md
USER.md
# Exception: oc-path real-world test fixtures need to be tracked even
# though the bare names match the local-untracked rule above.
!src/oc-path/tests/fixtures/real/IDENTITY.md
!src/oc-path/tests/fixtures/real/USER.md
*.tgz
*.tar.gz
*.zip

View File

@@ -6,33 +6,11 @@ Docs: https://docs.openclaw.ai
### Changes
- Telegram/Feishu: honor configured per-agent and global `reasoningDefault` values when deciding whether channel reasoning previews should stream or stay hidden, addressing the preview-default part of #73182. Thanks @anagnorisis2peripeteia.
- Docker: run the runtime image under `tini` so long-lived containers reap orphaned child processes and forward signals correctly. (#77885) Thanks @VintageAyu.
- Google/Gemini: normalize retired `google/gemini-3-pro-preview` and `google-gemini-cli/gemini-3-pro-preview` selections to `google/gemini-3.1-pro-preview` before they are written to model config.
- Google/Gemini: emit canonical `google/gemini-3.1-pro-preview` ids from configured provider catalog rows so model list and selection paths can test Gemini 3.1 instead of retired Gemini 3 Pro.
- Amazon Bedrock: support `serviceTier` parameter for Bedrock models, configurable via `agents.defaults.params.serviceTier` or per-model in `agents.defaults.models`. Valid values: `default`, `flex`, `priority`, `reserved`. (#64512) Thanks @mobilinkd.
- Control UI: read the Quick Settings exec policy badge from `tools.exec.security` instead of the non-schema `agents.defaults.exec.security` path, so configured `full`/`deny` values render accurately. Fixes #78311. Thanks @FriedBack.
- Control UI/usage: add transcript-backed historical lineage rollups for rotated logical sessions, with current-instance vs historical-lineage scope controls and long-range presets so usage history stays visible after restarts and updates. Fixes #50701. Thanks @dev-gideon-llc and @BunsDev.
- Agents/failover: harden state-aware lane suspension by persisting quota resume transitions, restoring configured lane concurrency, preserving non-quota failure reasons, and exporting model failover events through diagnostics OTLP. Thanks @BunsDev.
- Channels/streaming: make progress draft labels scroll away with other progress lines, render structured tool rows as compact emoji/title/details, show web-search queries from provider-native argument shapes, and skip empty Discord apply-patch starts until a patch summary exists. (#79146)
- Workspace/oc-path: add the `oc://` addressing substrate (`src/oc-path/`) — a universal, kind-dispatched path scheme for addressing leaves and nodes inside markdown, jsonc, jsonl, and yaml workspace files, with `parseOcPath`/`formatOcPath`, per-kind `parseXxx`/`emitXxx`, universal `resolveOcPath`/`setOcPath`/`findOcPaths` verbs, the `__OPENCLAW_REDACTED__` sentinel emit guard, and the new `openclaw path resolve|find|set|validate|emit` CLI for shell-level inspection and surgical edits. Implements #78051. (#78678) Thanks @giodl73-repo.
- Runtime/performance: avoid full-array sorting while auto-selecting providers, resolving supported thinking levels, picking node last-seen timestamps, and extracting Codex usage-limit messages. Thanks @shakkernerd.
- Plugins/doctor: avoid full-array sorting while selecting ClawHub search/archive results and bounded dreaming doctor entries. Thanks @shakkernerd.
- Agents/compaction: keep contributor diagnostics to a bounded top-three selection without sorting the full history. Thanks @shakkernerd.
- Sessions/UI: avoid full-array sorting while selecting ACPX leases, Google Meet calendar events, and latest chat sessions. Thanks @shakkernerd.
- Telegram: preserve the channel-specific 10-option poll cap in the unified outbound adapter so over-limit polls are rejected before send. (#78762) Thanks @obviyus.
- Telegram/streaming: continue over-limit draft previews in a new message instead of stopping when rendered preview text crosses Telegram's message limit. (#74508) Thanks @anagnorisis2peripeteia.
- Slack: route handled top-level channel turns in implicit-conversation channels to thread-scoped sessions when Slack reply threading is enabled, keeping the root turn and later thread replies on one OpenClaw session. (#78522) Thanks @zeroth-blip.
- Telegram: re-probe the primary fetch transport after repeated sticky fallback success so transient IPv4 or pinned-IP fallback promotion can recover without a gateway restart. Fixes #77088. (#77157) Thanks @MkDev11.
- Runtime/install: raise the supported Node 22 floor to `22.16+` so native SQLite query handling can rely on the `node:sqlite` statement metadata API while continuing to recommend Node 24. (#78921)
- Discord/voice: make duplicate same-guild auto-join entries resolve to the last configured channel so moving an agent between voice channels does not keep joining the stale channel.
- Discord/voice: add realtime `/vc` modes so Discord voice channels can run as STT/TTS, a realtime talk buffer with the OpenClaw agent brain, or a bidi realtime session with `openclaw_agent_consult`.
- Discord/voice: include a bounded one-line STT transcript preview in verbose voice logs so live voice debugging shows what speakers said before the agent reply.
- Codex app-server: pin the managed Codex harness and Codex CLI smoke package to `@openai/codex@0.129.0`, defer OpenClaw integration dynamic tools behind Codex tool search by default, and accept current Codex service-tier values so legacy `fast` settings survive the stable harness upgrade as `priority`.
- Codex app-server: default implicit local stdio app-server permissions to guardian when Codex system requirements disallow the YOLO approval, reviewer, or sandbox value, including hostname-scoped remote sandbox entries, avoiding turn-start failures on managed hosts that permit only reviewed approval or narrower sandboxes.
- Discord/voice: stream ElevenLabs TTS directly into Discord playback and send ElevenLabs latency optimization as the documented query parameter so spoken replies can start sooner.
- Discord/voice: keep TTS playback running when another user starts speaking, ignore new capture during playback to avoid feedback loops, and downgrade expected receive-stream aborts to verbose diagnostics.
- iMessage: expose native private-API message actions through `imsg rpc` for reactions, edits, unsends, replies, rich sends, attachments, and group management when `imsg status --json` reports the required bridge capabilities.
- Telegram: treat successful same-chat `message` tool outbound sends during an inbound telegram turn as delivered when deciding whether to emit the rewritten silent reply fallback (#78685). Thanks @neeravmakwana.
- Gateway/tasks: reconcile stale CLI run-context tasks whose live run context disappeared even when a child session row remains, and apply the default bounded reload deferral timeout to channel hot reloads so stale task records cannot block Discord/Slack/Telegram reloads forever.
- Gateway/sessions: keep session-store index writes atomic while skipping durable fsync inside the writer lock, reducing cron and channel-turn starvation on slow filesystems and addressing the session-store strand of #73655. Thanks @mmartoccia.
@@ -42,25 +20,19 @@ Docs: https://docs.openclaw.ai
- Discord/voice: make voice capture less choppy by extending the default post-speech silence grace to 2.5s, add `voice.captureSilenceGraceMs` for noisy Discord sessions, and tighten the spoken-output prompt around live STT fragments. Thanks @vincentkoc.
- Discord/streaming: default Discord replies to progress draft previews so tool/work activity appears in one edited Discord message unless `channels.discord.streaming.mode` is set to `off`.
- OpenAI: support `openai/chat-latest` as an explicit direct API-key model override for trying the moving ChatGPT Instant API alias without changing the stable default model.
- OpenAI/realtime: default realtime voice to `gpt-realtime-2`, use the GA Realtime WebSocket session shape for backend OpenAI bridges, and cover backend, WebRTC, Google Live, and Gateway relay paths in the live Talk smoke. (#79130)
- Update/Windows: spawn the post-core-update child process with `stdio:"pipe"` on Windows so PowerShell/CMD console handles are not inherited, preventing the terminal from hanging after `openclaw update` completes. Fixes #78445. (#78483) Thanks @Beandon13.
- Plugins/install: add `npm-pack:<path.tgz>` installs so local npm pack artifacts run through the same managed npm-root install, lockfile verification, dependency scan, and install-record path as registry npm plugins.
- Channels/plugins: show configured official external channels as missing-plugin status rows and send errors with exact install/doctor repair commands after raw package-manager upgrades leave Feishu or WhatsApp uninstalled. Fixes #78702 and #78593. Thanks @MarkMa84 and @mkupiainen.
- Codex app-server: disarm the short post-tool completion watchdog after current-turn activity, expose `appServer.turnCompletionIdleTimeoutMs`, and include raw assistant item context in idle-timeout diagnostics so status-only post-tool stalls stop failing as idle. Fixes #77984. Thanks @roseware-dev and @rubencu.
- Plugin skills/Windows: publish plugin-provided skill directories as junctions on Windows so standard users without Developer Mode can register plugin skills without symlink EPERM failures. Fixes #77958. (#77971) Thanks @hclsys and @jarro.
- Shell env/Windows: hide the login-shell environment probe child window so gateway startup and shell-env refreshes do not flash a console on Windows. Fixes #78159. (#78266) Thanks @BradGroux.
- MS Teams: surface blocked Bot Framework egress by logging JWKS fetch network failures and adding a Bot Connector send hint for transport-level reply failures. Fixes #77674. (#78081) Thanks @Beandon13.
- Gateway/sessions: fast-path already-qualified model refs while building session-list rows so `openclaw sessions` and Control UI session lists avoid heavyweight model resolution on large stores. (#77902) Thanks @ragesaq.
- Contributor PRs: remind external contributors to redact private information like IP addresses, API keys, phone numbers, and non-public endpoints from real behavior proof. Thanks @pashpashpash.
- Codex/approvals: in Codex approval modes, stop installing the pre-guardian native `PermissionRequest` hook by default so Codex's reviewer can approve safe commands before OpenClaw surfaces an approval, remember `allow-always` decisions for identical Codex native `PermissionRequest` payloads within the active session window, and make plugin approval requests validate/render their actual allowed decisions so Telegram and other native approval UIs cannot offer stale actions. Thanks @shakkernerd.
- Codex/plugins: enable migrated source-installed `openai-curated` Codex plugins in the same Codex harness thread with explicit `codexPlugins` config, cached app readiness, and fail-closed destructive-action policy. Thanks @kevinslin.
- Codex/plugins: enforce native plugin destructive-action policy with Codex app-level `destructive_enabled` config instead of OpenClaw-maintained per-tool deny lists, leave plugin app `open_world_enabled` on by default, and invalidate existing plugin app thread bindings so old generated app config is rebuilt. Thanks @kevinslin.
- PR triage: mark external pull requests with `proof: supplied` when Barnacle finds structured real behavior proof, keep stale negative proof labels in sync across CRLF-edited PR bodies, and let ClawSweeper own the stronger `proof: sufficient` judgement.
- Sessions CLI: show the selected agent runtime in the `openclaw sessions` table so terminal output matches the runtime visibility already present in JSON/status surfaces. Thanks @vincentkoc.
- ACPX/Codex: preserve trusted Codex project declarations when launching isolated Codex ACP sessions, avoiding interactive trust prompts in headless runs. Thanks @Stedyclaw.
- ACPX/Codex: reap stale OpenClaw-owned ACPX/Codex ACP process trees on startup and after ACP session close, preventing orphaned harness processes from slowing the Gateway. Thanks @91wan.
- ACP bridge: implement stable session list, resume, and close handlers so ACP clients can page Gateway sessions, rebind existing sessions without replay, and close bridge sessions cleanly. Thanks @amknight.
- ACP bridge: replay complete ledger-backed ACP sessions on load, including user prompts, tool updates, session metadata, and usage snapshots, while keeping older sessions on the existing transcript fallback. Thanks @amknight.
- ACP sessions: allow parent agents to inspect and message their own spawned cross-agent ACP sessions without enabling broad agent-to-agent visibility. Thanks @barronlroth.
- Talk/voice: unify realtime relay, transcription relay, managed-room handoff, Voice Call, Google Meet, VoiceClaw, and native clients around a shared Talk session controller and add the Gateway-managed `talk.session.*` RPC surface.
- Diagnostics/Talk: export bounded Talk lifecycle/audio metrics and session recovery metrics through OpenTelemetry and Prometheus without exposing transcripts, audio payloads, room ids, turn ids, or session ids.
@@ -87,11 +59,9 @@ Docs: https://docs.openclaw.ai
- Slack/performance: reduce message preparation, stream recipient lookup, and thread-context allocation overhead on Slack reply hot paths. Thanks @vincentkoc.
- Channels/streaming: cap progress-draft tool lines by default so edited progress boxes avoid jumpy reflow from long wrapped lines.
- Control UI/chat: add an agent-first filter to the chat session picker, keep chat controls/composer responsive across phone/tablet/desktop widths, keep desktop chat controls on one row, avoid duplicate avatar refreshes during initial chat load, and hide that row while scrolling down the transcript. Thanks @BunsDev.
- Control UI/chat: strip untrusted sender metadata from live streams and transcript display, preserve canvas preview anchors, and stop operator UI clients from injecting their internal client id as sender identity. Fixes #78739. Thanks @tmimmanuel, @guguangxin-eng, @hclsys, and @BunsDev.
- Control UI/chat: collapse consecutive duplicate text messages into one bubble with a count so repeated text-only messages stay compact without hiding nearby context.
- Control UI/chat and Sessions: label inherited thinking defaults separately from explicit overrides while preserving provider-supplied option labels. Fixes #77581. Thanks @BunsDev and @Beandon13.
- Agents/runtime: add prepared runtime foundation contracts for carrying provider, model, tool, TTS, and outbound runtime facts through later reply-path migrations. Thanks @mcaxtr.
- Control UI/WhatsApp: keep Show QR available for unlinked WhatsApp accounts while switching linked accounts to the explicit Relink action and showing Wait for scan only when a QR is active. Thanks @BunsDev.
- Agents/subagents: preserve every grouped child result when direct completion fallback has to bypass the requester-agent announce turn. Thanks @vincentkoc.
- TTS/telephony: honor provider voice/model overrides in telephony synthesis providers so Google Meet agent speech logs match the backend that actually produced the audio. Thanks @vincentkoc.
- Voice Call/realtime: bound the paced Twilio audio queue and close overloaded realtime streams before provider audio can pile up behind the websocket backpressure guard. Thanks @vincentkoc.
@@ -102,7 +72,7 @@ Docs: https://docs.openclaw.ai
- Gateway/performance: avoid resolving plugin auto-enable metadata twice in one runtime config pass, reducing repeated dashboard turn metadata scans. Thanks @shakkernerd.
- Auth/providers: pass `config` and `workspaceDir` lookup context through to provider-id resolution so workspace-scoped auth aliases resolve correctly when no explicit alias map is supplied. Thanks @shakkernerd.
- Gateway/performance: avoid importing `jiti` on native-loadable plugin startup paths, so compiled bundled plugin surfaces do not pay source-transform loader cost unless fallback loading is actually needed.
- Gateway/diagnostics: add startup phase spans, active work labels, stale terminal bridge markers, and opt-in sync-I/O tracing in `pnpm gateway:watch` so slow Gateway turns are easier to attribute from logs and stability diagnostics.
- Gateway/diagnostics: add startup phase spans, active work labels, stale terminal bridge markers, and default sync-I/O tracing in `pnpm gateway:watch` so slow Gateway turns are easier to attribute from logs and stability diagnostics.
- Plugins/loader: preserve real compiled plugin module evaluation errors on the native fast path instead of treating every thrown `.js` module as a source-transform fallback miss. Thanks @vincentkoc.
- QA/Mantis: add `pnpm openclaw qa mantis slack-desktop-smoke` to run Slack live QA inside a Crabbox VNC desktop, open Slack Web, and capture desktop screenshots beside the Slack QA artifacts.
- QA/Mantis: add an opt-in Discord thread attachment before/after scenario that creates a real thread, calls `message.thread-reply` with `filePath`, and captures baseline/candidate screenshot evidence.
@@ -156,8 +126,7 @@ Docs: https://docs.openclaw.ai
- Plugins/runtime state: add `registerIfAbsent` for atomic keyed-store dedupe claims that return whether a plugin successfully claimed a key without overwriting an existing live value. Thanks @amknight.
- Exec approvals: add a tree-sitter-backed shell command explainer for future approval and command-review surfaces. (#75004) Thanks @jesse-merhi.
- Control UI/performance: record browser long animation frame or long task entries in the debug event log when supported, making slow dashboard renders easier to attribute from the UI.
- Control UI/performance: keep chat, config, and channel refreshes responsive by decoupling slow history/schema/status work, reducing the client history window, and logging over-budget chat/config renders. Refs #77060, #45698, #47979, #44107. Thanks @BunsDev.
- Gateway/diagnostics: add startup phase spans, active work labels, stale terminal bridge markers, and opt-in sync-I/O tracing in `pnpm gateway:watch` so slow Gateway turns are easier to attribute from logs and stability diagnostics.
- Gateway/diagnostics: add startup phase spans, active work labels, stale terminal bridge markers, and default sync-I/O tracing in `pnpm gateway:watch` so slow Gateway turns are easier to attribute from logs and stability diagnostics.
- QA/Codex harness: add targeted live Docker/Testbox diagnostics, auth preflight checks, cache mount fixes, and app-server protocol checkout discovery so maintainer harness failures are easier to reproduce. Thanks @vincentkoc.
- QA/Mantis: add `pnpm openclaw qa mantis slack-desktop-smoke` to run Slack live QA inside a Crabbox VNC desktop, open Slack Web, and capture desktop screenshots beside the Slack QA artifacts.
- QA/Mantis: add visual desktop tasks with Crabbox MP4 recording, screenshot capture, and optional image-understanding assertions, and preserve video artifacts in Mantis before/after reports.
@@ -179,9 +148,6 @@ Docs: https://docs.openclaw.ai
- Plugins/hooks: add a `before_agent_run` pass/block gate that can stop a user prompt before model submission while preserving a redacted transcript entry for the user, and clarify that raw conversation hooks require `hooks.allowConversationAccess=true`. (#75035) Thanks @jesse-merhi.
- Config/Nix: keep startup-derived plugin enablement, gateway auth tokens, control UI origins, and owner-display secrets runtime-only instead of rewriting `openclaw.json`; in Nix mode, config writers, mutating `openclaw update`, plugin lifecycle mutators, and doctor repair/token-generation now refuse with agent-first nix-openclaw guidance. (#78047) Thanks @joshp123.
- Agents/context engine: invalidate cached assembled context views when source history shrinks or assembly fails, preventing stale pre-reset history from being reused. Fixes #77968. (#78163) Thanks @brokemac79 and @ChrisBot2026.
- Plugin SDK: add a generic `api.runtime.llm.complete` host completion helper with runtime-derived caller attribution, config-gated model/agent overrides, session-bound context-engine access, request-scoped config, audit metadata, and normalized usage attribution. (#64294) Thanks @DaevMithran.
- Control UI/exec approvals: highlight parsed shell command fragments that may deserve extra review in approval prompts. (#77153) Thanks @jesse-merhi.
- Codex app-server: nudge delegated/background work toward `sessions_spawn` when available so Codex uses OpenClaw task tracking and completion delivery by default. (#79518) Thanks @mbelinky.
### Breaking
@@ -189,59 +155,17 @@ Docs: https://docs.openclaw.ai
### Fixes
- Cron/agents: recognize same-target `edit``write` recovery in `isSameToolMutationAction`, so a successful `write` to a path clears an earlier failed `edit` on the same path. Stops cron from reporting fatal failures when an agent self-heals across `edit` and `write`, while preserving same-tool fingerprint matching, blocking different-target writes, and excluding tools (including `apply_patch`) whose real call args do not produce a stable `path` fingerprint segment. Fixes #79024. Thanks @RenzoMXD.
- Gateway/Tailscale: add opt-in `gateway.tailscale.preserveFunnel` so when `tailscale.mode = "serve"` and an externally configured Tailscale Funnel route already covers the gateway port, OpenClaw skips re-applying `tailscale serve` on startup and skips the `resetOnExit` teardown for that run, keeping operator-managed Funnel exposure alive across gateway restarts. Fixes #57241. Thanks @RenzoMXD.
- Agents/compaction: keep the recent tail after manual `/compact` when Pi returns an empty or no-op compaction summary, preventing blank checkpoints from replacing the live context.
- Native commands: handle slash commands before workspace and agent-reply bootstrap so Telegram `/status` and other command-only native replies do not wait behind full agent turn setup.
- Plugins/Nix: allow externally configured plugin roots under `/nix/store` to load in `OPENCLAW_NIX_MODE=1` while keeping normal external plugin hardlink rejection unchanged. Thanks @joshp123.
- fix(discord): gate user allowlist name resolution [AI]. (#79002) Thanks @pgondhi987.
- fix(msteams): gate startup user allowlist resolution [AI]. (#79003) Thanks @pgondhi987.
- Infra/fetch-timeout: pass `operation` and `url` context to `buildTimeoutAbortSignal` from the music-generate reference fetch and the Matrix guarded redirect transport, so the `fetch timeout reached; aborting operation` warning carries actionable structured fields instead of a bare line. Fixes #79195. Thanks @pandadev66.
- Harden macOS shell wrapper allowlist parsing [AI]. (#78518) Thanks @pgondhi987.
- macOS/config: reject stale or destructive app fallback config writes before direct replacement and keep rejected payloads as private audit artifacts, so `gateway.mode`, metadata, and auth are not silently clobbered. Fixes #64973 and #74890. Thanks @BunsDev.
- Gateway/macOS: include Apple Silicon Homebrew bin and sbin directories in generated LaunchAgent service PATHs so `openclaw gateway restart` keeps Homebrew Node installs reachable. Fixes #79232. Thanks @BunsDev.
- Doctor/OpenAI: stop pinning migrated `openai-codex/*` routes to the Codex runtime so mixed-provider agents keep automatic PI routing for MiniMax, Anthropic, and other non-OpenAI model switches.
- Gateway/macOS: `openclaw gateway stop` now uses `launchctl bootout` by default instead of unconditionally calling `launchctl disable`, so KeepAlive auto-recovery still works after unexpected crashes; use the new `--disable` flag to opt into the persistent-disable behavior when a manual stop should survive reboots. Fixes #77934. Thanks @bmoran1022.
- Gateway/macOS: `repairLaunchAgentBootstrap` no longer kickstarts an already-running LaunchAgent, preventing unnecessary service restarts and session disconnects when repair runs against a healthy gateway. Fixes #77428. Thanks @ramitrkar-hash.
- Gateway/macOS: `openclaw gateway stop --disable` now persists the LaunchAgent disable bit even after a previous bootout left the service not loaded, keeping the explicit stay-down path reliable. (#78412) Thanks @wdeveloper16.
- CLI/status: keep lean `openclaw status --json` off manifest-backed channel discovery so configured-channel checks do not repeatedly rescan plugin metadata. Fixes #79129.
- Control UI/chat: hide retired and non-public Google Gemini model IDs from chat model catalogs and route the bare `gemini-3-pro` alias to Gemini 3.1 Pro Preview instead of the shut-down Gemini 3 Pro Preview. Thanks @BunsDev.
- CLI/infer: canonicalize case-only catalog model refs in `infer model run --model` so mixed-case provider/model strings resolve to the canonical catalog entry instead of failing with `Unknown model`. (#78940) Thanks @ai-hpc.
- CLI/install: refuse state-mutating OpenClaw CLI runs as root by default, keep an explicit `OPENCLAW_ALLOW_ROOT=1` escape hatch for intentional root/container use, and update DigitalOcean setup guidance to run OpenClaw as a non-root user. Fixes #67478. Thanks @Jerry-Xin and @natechicago.
- Auto-reply/media: resolve `scp` from `PATH` when staging sandbox media so nonstandard OpenSSH installs can copy remote attachments.
- Agents/PI: route PI-native OpenAI-compatible default streams through OpenClaw boundary-aware transports so local-compatible model runs keep API-key injection and transport policy.
- Gateway/media: require authenticated owner or admin context for managed outgoing image bytes instead of trusting requester-session headers.
- Doctor/gateway: avoid duplicate Node runtime warnings when the daemon install plan already selected a supported Node runtime.
- Gateway/nodes: ignore malformed non-string capability entries from live nodes instead of throwing while listing the node catalog.
- Gateway/pairing: preserve deliberately narrowed role-token scopes when approving device scope upgrades instead of regranting the whole approved baseline.
- Telegram/ACP: keep chat-bound ACP replies durable by delivering final-only ACP output as final text instead of transient Telegram preview blocks. Thanks @shakkernerd.
- Telegram: hydrate replied-to messages as a persisted nearest-first reply chain so agents can see observed parent text, media refs, captions, senders, timestamps, and nested replies instead of guessing from a shallow reply id.
- Gateway/watch: leave `OPENCLAW_TRACE_SYNC_IO` disabled by default in `pnpm gateway:watch:raw` so watch mode avoids noisy Node sync-I/O stack traces unless explicitly requested.
- Codex app-server: close stdio stdin before force-killing the managed app-server, matching Codex single-client shutdown behavior and avoiding unsettled CLI exits after successful runs.
- CLI/Codex: dispose registered agent harnesses during short-lived CLI shutdown so successful Codex-backed `agent --local` runs do not leave app-server child processes alive.
- Agents/Codex: auto-enable the Codex harness plugin for one-shot OpenAI model overrides so `openclaw agent --local --model openai/...` does not fail with an unregistered `codex` harness.
- Gateway/live tests: avoid full model-registry enumeration for explicit provider-qualified live model filters, preventing `.profile` OpenAI gateway profile runs from hanging before provider dispatch.
- Gateway/status: surface CLI and gateway runtime versions, warn about stale PATH/global wrappers when they differ, and add stale-wrapper checks to the newer-config warning. Refs #79091. Thanks @RamaAditya49 and @sallyom.
- TUI/local runs: keep stable runtime plugin aliases present when legacy compatibility wrappers already exist in dist, so sending a message no longer fails with a missing `runtime-plugins.runtime.js` module.
- Providers: preserve non-OK `text/event-stream` response bodies so provider HTTP errors keep their JSON detail instead of collapsing to generic streaming failures. Fixes #78180.
- Gateway/auth: make explicit `trusted-proxy` mode fail closed instead of accepting local password fallback credentials after trusted-proxy identity checks fail. Fixes #78684.
- Active memory: treat Google Chat `spaces/...` conversation ids as scoped targets instead of runnable channel names so recall runs no longer fail bundled-plugin dirName validation. Fixes #78918.
- Active memory: make `/active-memory status` honor the configured agent allowlist instead of reporting on for agents where recall is disabled. Fixes #78986.
- Mistral: normalize structured OpenAI-compatible completions content blocks so thinking objects are not persisted as `[object Object]` visible reply text. Fixes #78846.
- Tools/session status: render the active heartbeat/run model for `session_status({"sessionKey":"current"})` instead of falling back to the persisted session default. Fixes #77493.
- Doctor/secrets: allow safe inherited exec SecretRef `passEnv` names such as `HOME` while still blocking dangerous runtime env hooks. Fixes #78216.
- Chat commands: make `/model default` reset the session model override instead of treating it as a literal model name. Fixes #78182.
- Cron: make rejected `payload.model` errors show the configured `agents.defaults.models` allowlist instead of echoing the rejected model twice. Fixes #79058.
- Agents/subagents: retry parent wake announces when the announce-summary model run fails with fallback cooldown exhaustion instead of dropping the wake on the first transient provider overload. Refs #78581.
- Providers/network: honor IPv4 CIDR and octet-wildcard `NO_PROXY` entries such as `100.64.0.0/10` and `100.64.*` before enabling trusted env-proxy mode for model-provider requests. Fixes #79030.
- Skills: cap skills watcher directory traversal at the same depth used by skill discovery so large non-skill trees under configured skill roots do not exhaust file descriptors on startup. Fixes #75501. Thanks @wzq-xzwj.
- Docs/Docker: document a local Compose override for Docker Desktop DNS failures in the shared-network `openclaw-cli` sidecar, keeping the default compose setup hardened while unblocking `openclaw plugins install` when users opt in. Fixes #79018. Thanks @Jason-Vaughan.
- Installer: when npm installs `openclaw` outside the parent shell PATH, print follow-up commands with the resolved binary path instead of telling users to run `openclaw` from a shell that will report `command not found`. Fixes #72382. Thanks @jbob762.
- Plugins/runtime: share MIME and JSON Schema helpers across bundled plugins while preserving canonical media MIME inference, browser URL wildcard semantics, migration home-path resolution, QA request-limit responses, and extensionless text file previews.
- Agents/memory flush: persist the pre-increment compaction counter after flush-triggered compaction so consecutive eligible compaction cycles run memoryFlush instead of alternating. Fixes #12590. Refs #12760, #26145, and #46513. Thanks @Kaspre, @lailoo, @drvoss, @Br1an67, and @dial481.
- Compute plugin callback authorization dynamically [AI]. (#78866) Thanks @pgondhi987.
- fix(active-memory): require admin scope for global toggles [AI]. (#78863) Thanks @pgondhi987.
- Honor owner enforcement for native commands [AI]. (#78864) Thanks @pgondhi987.
- Gateway/auth: allow `gateway.auth.mode: "none"` loopback backend RPC clients to skip device identity only for local non-browser backend connections, restoring subagent spawns and gateway tools without opening remote or browser-origin bypasses. Fixes #75780. Thanks @yozakura-ava.
- Tavily: resolve dedicated `tavily_search` and `tavily_extract` tool credentials from the active runtime config snapshot, so `exec` SecretRef-backed API keys do not reach the tools unresolved. (#78610) Thanks @VACInc.
- Gateway/sessions: clear cached skills snapshots during `/new` and `sessions.reset` so long-lived channel sessions rebuild the visible skill list after skills change. (#78873) Thanks @Evizero.
- fix(auto-reply): gate inline skill tool dispatch [AI]. (#78517) Thanks @pgondhi987.
@@ -252,7 +176,6 @@ Docs: https://docs.openclaw.ai
- Control UI/chat: wait for an in-flight model dropdown patch before sending the next chat message, so immediate sends use the selected session model instead of racing the previous override. Fixes #54240.
- Native chat: decode gateway-provided thinking metadata for the iOS/macOS picker so provider-specific levels such as `adaptive`, `xhigh`, and `max` appear without leaking unsupported default-model options. Thanks @BunsDev.
- Agents/compaction: cap summarization output reserve tokens to the selected model's `maxTokens` so 1M-context Anthropic compactions do not request more output than the API permits. Fixes #54383.
- Control UI/login: replace raw connection failures with structured, actionable login guidance for auth, pairing, insecure HTTP, origin, protocol, and transport failures. Thanks @BunsDev.
- Agents/tools: fail `exec host=node` before `system.run` when the selected node is known to be disconnected, with an actionable reconnect message instead of a raw node invoke failure. Thanks @BunsDev.
- Agents/models: accept legacy `anthropic-cli/*` model refs as Claude CLI runtime refs instead of failing model resolution with `Unknown model`. Thanks @BunsDev.
- Agents/tools: keep restrictive-profile tool-section warnings scoped to the configured sections whose tools are still missing from `alsoAllow`, so already re-allowed filesystem tools do not make exec-only fixes look broader than they are. Thanks @BunsDev.
@@ -292,7 +215,6 @@ Docs: https://docs.openclaw.ai
- Discord/groups: instruct group-chat agents to stay silent when a message is addressed to someone else, replying only when invited or correcting key facts. (#78615)
- Discord/groups: tell Discord-channel agents to wrap bare URLs as `<https://example.com>` so link previews do not expand into uninvited embeds. (#78614)
- Agents/fallback: fail fast on session write-lock timeouts instead of trying fallback models for local file contention. Fixes #66646. Thanks @sallyom.
- Browser/SSRF: stop closing user-owned Chrome tabs when a read-only operation (snapshot/screenshot/interactions) is rejected by the SSRF guard — only OpenClaw-initiated navigations now close on policy denial. Thanks @scotthuang.
- Telegram/Codex: generate DM topic labels with Codex-compatible simple-completion requests so auto-created private topics can be renamed instead of staying `New Chat`.
- Plugins/runtime fetch: drop third-party symbol metadata from plain request header dictionaries before passing them into native `fetch` or `Headers`, so SDK and guarded/proxy fetch paths do not reject otherwise valid plugin requests. Fixes #77846. Thanks @shakkernerd.
- Web fetch: bound guarded dispatcher cleanup after request timeouts so timed-out fetches return tool errors instead of leaving Gateway tool lanes active. (#78439) Thanks @obviyus.
@@ -316,7 +238,6 @@ Docs: https://docs.openclaw.ai
- Memory Wiki: skip empty and whitespace-only source pages when refreshing generated Related blocks, preventing blank pages from being rewritten into Related-only stubs. Fixes #78121. Thanks @amknight.
- LINE: reject `dmPolicy: "open"` configs without wildcard `allowFrom` so webhook DMs fail validation instead of being acknowledged and silently blocked before inbound processing. Fixes #78316.
- Telegram/Codex: keep message-tool-only progress drafts visible and render native Codex tool progress once per tool instead of duplicating item/tool draft lines. Fixes #75641. (#77949) Thanks @keshavbotagent.
- Telegram: keep duplicate message-tool-only Codex turns from posting generic silent-reply fallback text, so private finals stay private after inbound dedupe. Thanks @rubencu.
- Telegram/sessions: gap-fill delivered embedded final replies into the session JSONL even when the runner trace is missing, so Telegram answers after tool calls do not vanish from the durable transcript. Fixes #77814. (#78426) Thanks @obviyus, @ChushulSuri, and @DougButdorf.
- Providers/xAI: stop sending OpenAI-style reasoning effort controls to native Grok Responses models, so `xai/grok-4.3` no longer fails live Docker/Gateway runs with `Invalid reasoning effort`.
- Providers/xAI: clamp the bundled xAI thinking profile to `off` so live Gateway runs cannot send unsupported reasoning levels to native Grok Responses models.
@@ -352,7 +273,6 @@ Docs: https://docs.openclaw.ai
- Plugins/update: keep installed official npm and ClawHub plugins such as Codex, Discord, WhatsApp, and diagnostics plugins synced during host updates even when disabled or previously exact-pinned, while preserving third-party plugin pins. Thanks @vincentkoc.
- Doctor/status: warn when `OPENCLAW_GATEWAY_TOKEN` would shadow a different active `gateway.auth.token` source for local CLI commands, while avoiding false positives when config points at the same env token. Fixes #74271. Thanks @yelog.
- Gateway/HTTP: avoid loading managed outgoing-image media handlers for unrelated requests, so disabled OpenAI-compatible routes return 404 without waiting on lazy media sidecars. Thanks @vincentkoc.
- Plugins: dispatch cached descriptor-backed tools by the resolved runtime tool name for unnamed factories, fixing multi-tool plugins whose shared manifest contracts exposed sibling tools but failed at execution. Fixes #78671. Thanks @zanni098.
- Gateway/OpenAI-compatible: send the assistant role SSE chunk as soon as streaming chat-completion headers are accepted, so cold agent setup cannot leave `/v1/chat/completions` clients with a bodyless 200 response until their idle timeout fires.
- Agents/media: avoid direct generated-media completion fallback while the announce-agent run is still pending, so async video and music completions do not duplicate raw media messages. (#77754)
- WebChat/Codex media: stage Codex app-server generated local images into managed media before Gateway display, so Codex-home image paths no longer hit `LocalMediaAccessError` while keeping Codex home out of the display allowlist. Thanks @frankekn.
@@ -369,7 +289,7 @@ Docs: https://docs.openclaw.ai
- CLI/status: show the selected agent runtime/harness in `openclaw status` session rows so terminal status matches the `/status` runtime line. Thanks @vincentkoc.
- CLI/sessions: prune old unreferenced transcript, compaction checkpoint, and trajectory artifacts during normal `sessions cleanup`, so gateway restart or crash orphans do not accumulate indefinitely outside `sessions.json`. Fixes #77608. Thanks @slideshow-dingo.
- Doctor/Codex: repair legacy `openai-codex/*` routes to canonical `openai/*`, keep OpenAI agent turns on Codex by default, ignore stale whole-agent/session runtime pins, preserve explicit provider/model runtime policy, and migrate legacy runtime model refs to model-scoped runtime entries. Thanks @vincentkoc.
- Doctor/Codex: repair legacy `openai-codex/*` routes in primary models, fallbacks, heartbeat/subagent/compaction overrides, hooks, channel overrides, and stale session pins to canonical `openai/*`, selecting `agentRuntime.id: "codex"` only when the Codex plugin is installed, enabled, contributes the `codex` harness, and has usable OAuth; otherwise select `agentRuntime.id: "pi"`. Thanks @vincentkoc.
- Video generation: wait up to 20 minutes for slow fal/MiniMax queue-backed jobs, stop forwarding unsupported Google Veo generated-audio options, and normalize MiniMax `720P` requests to its supported `768P` resolution with the usual override warning/details instead of failing fallback.
- Video generation: accept provider-specific aspect-ratio and resolution hints at the tool boundary, normalize `720P` to MiniMax's supported `768P`, and stop sending Google `generateAudio` on Gemini video requests so provider fallback can recover from model-specific parameter differences. Thanks @vincentkoc.
- Channels/durable delivery: preserve channel-specific final reply semantics when using durable sends, including Telegram selected quotes and silent error replies plus WhatsApp message-sending cancellations.
@@ -648,15 +568,6 @@ Docs: https://docs.openclaw.ai
- WhatsApp: send captioned `MEDIA:` directive auto-replies once instead of emitting an empty media message before the captioned media reply. (#78770) Thanks @ai-hpc.
- Hooks/cron: log returned `/hooks/agent` isolated-run errors and failed cron jobs with cron diagnostic summaries, so rejected `payload.model` values are visible instead of looking like accepted-but-missing runs. Fixes #78597. (#78655) Thanks @kevinslin.
- Managed proxy/security: classify raw socket callsites and proxy runtime mutations in boundary checks so new direct egress or unmanaged proxy-state changes cannot land without explicit review. (#77126) Thanks @jesse-merhi.
- Channels/iMessage: surface the silent group-allowlist drop at default log level by emitting a one-time `warn` per account at monitor startup when `channels.imessage.groupPolicy: "allowlist"` is set without a `channels.imessage.groups` block, plus a one-time `warn` per `chat_id` when the runtime gate drops a specific group, naming the exact `channels.imessage.groups[...]` key to add to allow it. Fixes #78749. (#79190) Thanks @omarshahine.
- WhatsApp: stop Gateway-originated outbound echoes from advancing inbound activity in `openclaw channels status`, so outbound self-sends no longer look like handled inbound messages. Fixes #79056. (#79057) Thanks @ai-hpc and @bittoby.
- Gateway/nodes: preserve the live node registry session and invoke ownership when an older same-node WebSocket closes after reconnecting. (#78351) Thanks @samzong.
- Browser/downloads: route explicit and managed browser download output directories through `fs-safe` validation before staging final files, so symlinked output roots are rejected before writes. (#78780) Thanks @jesse-merhi.
- Agents/PI: skip the idle wait during aborted embedded-run cleanup, so stopped or timed-out runs clear pending tool state and release the session lock promptly. (#74919) Thanks @medns.
- Agents/current-time: split UTC into a separate `Reference UTC:` prompt line so local `Current time:` stays anchored to the user's timezone. (#42654) Thanks @chencheng-li.
- Agents/reasoning: keep embedded reasoning deltas raw for correct same-line streaming while preserving formatted Telegram, Feishu, Discord, and heartbeat delivery at the channel edge. (#78397) Thanks @medns.
- Agents/failover: rotate auth profiles before deferred cooldown marking on rate-limit failures, so file-lock contention cannot stall profile failover. Fixes #57281. (#57283) Thanks @jeremyknows.
- Gateway/sessions: when `session.dmScope: "main"` is configured, route a bare webchat `/new` against the agent's main session (`sessions.create` with `emitCommandHooks=true`) to an in-place reset instead of creating a parallel `dashboard:` child, matching `/new` behavior on Telegram/Discord. Fixes #77434. (#71170) Thanks @statxc.
## 2026.5.3-1
@@ -880,7 +791,6 @@ Docs: https://docs.openclaw.ai
- Agents/idle-timeout: add a cost-runaway breaker to the outer embedded-run retry loop that halts further attempts after 5 consecutive idle timeouts without completed model progress, so a wedged provider can no longer fan paid model calls out across the same run; completed text or tool-call progress resets the breaker, but partial tool-argument token dribbles do not. Fixes #76293. Thanks @ThePuma312.
- Heartbeats/Codex: align structured heartbeat prompts with actual `heartbeat_respond` tool availability, stop sending legacy `HEARTBEAT_OK` when the tool exists, and keep tool-disabled commitment check-ins on the legacy ack path. Thanks @pashpashpash and @vincentkoc.
- Agent runtimes: fail explicit plugin runtime selections honestly when the requested harness is unavailable instead of silently falling back to the embedded PI runtime. Thanks @pashpashpash.
- Telegram: log inbound gateway watch messages before dispatch so watch-mode diagnostics include incoming message summaries. Thanks @rubencu.
- Maintainer workflow: push prepared PR heads through GitHub's verified commit API by default and require an explicit override before git-protocol pushes can publish unsigned commits. Thanks @BunsDev.
- Feishu: resolve setup/status probes through the selected/default account so multi-account configs with account-scoped app credentials show as configured and probeable. Fixes #72930. Thanks @brokemac79.
- Gateway/responses: emit every client tool call from `/v1/responses` JSON and SSE responses when the agent invokes multiple client tools in a single turn, so multi-tool plans, graph orchestration calls, and similar batched flows no longer drop every call but the last. Fixes #52288. Thanks @CharZhou and @bonelli.

View File

@@ -86,9 +86,6 @@ Welcome to the lobster tank! 🦞
- **Mason Huang** - Stability, Security, Speed
- GitHub: [@hxy91819](https://github.com/hxy91819) · X: [@chenjingtalk](https://x.com/chenjingtalk)
- **Maurice Niu** - ClawHub, Security, Stability, Data integrity
- GitHub: [@momothemage](https://github.com/momothemage) · X: [@MomoPsicasso](https://x.com/MomoPsicasso)
## How to Contribute
1. **Bugs & small fixes** → Open a PR!

View File

@@ -160,7 +160,7 @@ RUN --mount=type=cache,id=openclaw-bookworm-apt-cache,target=/var/cache/apt,shar
--mount=type=cache,id=openclaw-bookworm-apt-lists,target=/var/lib/apt,sharing=locked \
apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
ca-certificates procps hostname curl git lsof openssl python3 tini && \
ca-certificates procps hostname curl git lsof openssl python3 && \
update-ca-certificates
RUN chown node:node /app
@@ -287,5 +287,4 @@ USER node
# For external access from host/ingress, override bind to "lan" and set auth.
HEALTHCHECK --interval=3m --timeout=10s --start-period=15s --retries=3 \
CMD node -e "fetch('http://127.0.0.1:18789/healthz').then((r)=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"
ENTRYPOINT ["tini", "-s", "--"]
CMD ["node", "openclaw.mjs", "gateway", "--allow-unconfigured"]

View File

@@ -2,53 +2,6 @@
<rss xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle" version="2.0">
<channel>
<title>OpenClaw</title>
<item>
<title>2026.5.7</title>
<pubDate>Thu, 07 May 2026 22:36:27 +0000</pubDate>
<link>https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml</link>
<sparkle:version>2026050790</sparkle:version>
<sparkle:shortVersionString>2026.5.7</sparkle:shortVersionString>
<sparkle:minimumSystemVersion>15.0</sparkle:minimumSystemVersion>
<description><![CDATA[<h2>OpenClaw 2026.5.7</h2>
<h3>Fixes</h3>
<ul>
<li>Release/plugin publishing: retry transient ClawHub CLI dependency install failures, keep preview-passing plugins publishable when one preview cell flakes, and verify every expected ClawHub package version after publish so maintenance releases are faster to recover and less likely to hide partial plugin publishes.</li>
<li>OpenAI: support <code>openai/chat-latest</code> as an explicit direct API-key model override for trying the moving ChatGPT Instant API alias without changing the stable default model.</li>
<li>Cron CLI: include computed <code>status</code> in <code>cron list --json</code> and <code>cron show --json</code> output so external tooling can read disabled/running/ok/error/skipped/idle state without reimplementing cron status derivation. (#78701) Thanks @aweiker.</li>
<li>Channels CLI: make <code>openclaw channels list</code> channel-only, add <code>--all</code> for bundled and catalog channels, render installed/configured/enabled state, and move model auth/usage details to <code>openclaw models auth list</code>, <code>openclaw status</code>, and <code>openclaw models list</code>. (#78456) Thanks @sliverp.</li>
<li>Native commands: honor owner enforcement for native command handlers. (#78864) Thanks @pgondhi987.</li>
<li>Active Memory: require admin scope for global memory toggles. (#78863) Thanks @pgondhi987.</li>
<li>Gateway/sessions: clear cached skills snapshots during <code>/new</code> and <code>sessions.reset</code> so long-lived channel sessions rebuild the visible skill list after skills change. (#78873) Thanks @Evizero.</li>
<li>Auto-reply: gate inline skill tool dispatch through before-tool-call authorization hooks. (#78517) Thanks @pgondhi987.</li>
<li>Tavily: resolve dedicated <code>tavily_search</code> and <code>tavily_extract</code> tool credentials from the active runtime config snapshot, so <code>exec</code> SecretRef-backed API keys do not reach the tools unresolved. (#78610) Thanks @VACInc.</li>
<li>Plugins/install: use the same absolute POSIX npm lifecycle shell for managed plugin install, rollback, repair, and uninstall npm operations as staged package updates, preventing restricted PATH shells from breaking cleanup. Thanks @vincentkoc.</li>
<li>Agents/context engine: invalidate cached assembled context views when source history shrinks or assembly fails, preventing stale pre-reset history from being reused. Fixes #77968. (#78163) Thanks @brokemac79 and @ChrisBot2026.</li>
<li>Discord/message: parse provider-prefixed targets like <code>discord:channel:<id></code> as channel sends instead of legacy Discord DM targets, so cross-channel agent <code>message(action="send")</code> calls no longer misroute channel IDs into misleading <code>Unknown Channel</code> failures. Fixes #78572.</li>
<li>Agents/compaction: clamp compaction summary reserve tokens to each model's output limit so high-context compaction no longer requests invalid <code>max_tokens</code> values. (#54392) Thanks @adzendo.</li>
<li>Commands/BTW: show the <code>/btw</code> missing-question usage placeholder with brackets so outbound channel sanitization keeps it visible. Fixes #62877. Thanks @RajvardhanPatil07.</li>
<li>Cron/doctor: repair persisted cron jobs whose <code>payload.model</code> was stored as <code>"default"</code>, <code>"null"</code>, blank, or JSON <code>null</code> by removing the bad override during <code>openclaw doctor --fix</code> while keeping cron runtime model validation strict. Fixes #78549. Thanks @bizzle12368239.</li>
<li>Telegram: honor <code>accessGroup:*</code> sender allowlists for DMs, groups, native commands, and callback authorization before applying Telegram's numeric sender-ID checks. Fixes #78660. Thanks @manugc.</li>
<li>Agent delivery: report <code>deliverySucceeded=false</code> when outbound delivery returns no adapter result, so claimed/empty delivery paths no longer masquerade as successful sends. Fixes #78532. Thanks @joeyfrasier.</li>
<li>Cron/isolated runs: fail implicit announce delivery before model execution when <code>delivery.channel=last</code> has no previous route, so recurring jobs do not spend tokens before hitting a permanent delivery-target error. Fixes #78608. Thanks @sallyom.</li>
<li>Gateway/sessions: persist a new generated transcript file when daily gateway-agent session rollover changes the session id, while preserving custom transcript paths. Fixes #78607. Thanks @nailujac, @zerone0x, and @sallyom.</li>
<li>Doctor/Codex OAuth: preserve working <code>openai-codex/*</code> PI routes during <code>doctor --fix</code> and recover 2026.5.5-rewritten <code>openai/*</code> GPT-5 routes when only Codex OAuth auth is available, so update repair does not break subscription-auth setups. Fixes #78407. Thanks @shakkernerd.</li>
<li>Telegram: keep the polling watchdog tied to <code>getUpdates</code> liveness so unrelated outbound Bot API calls cannot mask a wedged inbound poller. Fixes #78422. Thanks @ai-hpc.</li>
<li>Agents/subagents: have completed session-mode subagent registry rows honor <code>agents.defaults.subagents.archiveAfterMinutes</code> instead of a hardcoded 5-minute TTL, so registry-backed surfaces keep one retention knob across spawn modes. (#78263) Thanks @arniesaha.</li>
<li>Plugins/channel setup: forward <code>setChannelRuntime</code> from non-bundled external plugin setup entries so deferred external channel runtime initializers are installed before startup polling. Fixes #77779. (#77799) Thanks @openperf.</li>
<li>Telegram: treat successful same-chat <code>message</code> tool outbound sends during an inbound Telegram turn as delivered when deciding whether to emit the rewritten silent reply fallback. (#78685) Thanks @neeravmakwana.</li>
<li>Gateway/tasks: reconcile stale CLI run-context tasks whose live run context disappeared and bound channel hot-reload deferrals so stale task records cannot block Discord/Slack/Telegram reloads forever.</li>
<li>Discord/voice: audit Discord voice-channel permissions in <code>channels capabilities</code> and <code>channels status --probe</code>, including auto-join targets, so missing Connect/Speak/Read Message History permissions show up before <code>/vc join</code>.</li>
<li>Discord/voice: make voice capture less choppy by extending the default post-speech silence grace to 2.5s, add <code>voice.captureSilenceGraceMs</code> for noisy Discord sessions, and tighten the spoken-output prompt around live STT fragments. Thanks @vincentkoc.</li>
<li>WhatsApp: route proactive phone-number sends through Baileys LID forward mappings when available, so LID-addressed contacts receive agent messages instead of creating sender-only ghost chats. Fixes #67378. (#74925) Thanks @edenfunf.</li>
<li>WhatsApp: send captioned <code>MEDIA:</code> directive auto-replies once instead of emitting an empty media message before the captioned media reply. (#78770) Thanks @ai-hpc.</li>
<li>Codex/approvals: in Codex approval modes, stop installing the pre-guardian native <code>PermissionRequest</code> hook by default so Codex's reviewer can approve safe commands before OpenClaw surfaces an approval, remember <code>allow-always</code> decisions for identical Codex native <code>PermissionRequest</code> payloads within the active session window, and make plugin approval requests validate/render their actual allowed decisions so Telegram and other native approval UIs cannot offer stale actions. Thanks @shakkernerd.</li>
<li>Model providers: normalize APNG sniffed PNG uploads, preserve Gemini 3 tool-call thought-signature replay with fallback signatures, accept legacy <code>__env__:VAR</code> custom-provider keys, and repair snake_case tool-call transcript sanitization. Fixes #51881, #48915, #77566, and #42858.</li>
<li>Telegram/models: parse provider ids containing dots in <code>/models</code> callback buttons so <code>hf.co</code> model lists render as inline keyboard buttons. Fixes #38745.</li>
</ul>
<p><a href="https://github.com/openclaw/openclaw/blob/main/CHANGELOG.md">View full changelog</a></p>
]]></description>
<enclosure url="https://github.com/openclaw/openclaw/releases/download/v2026.5.7/OpenClaw-2026.5.7.zip" length="51130645" type="application/octet-stream" sparkle:edSignature="Zu+EzBGMRE1k7N4//L8HUxtUCPdO0ImrfDbgr2GrPMBrj7VGI1tOOl74gxNJoi/wfWvXz3fYVcBz2W/84ojuCw=="/>
</item>
<item>
<title>2026.5.2</title>
<pubDate>Sun, 03 May 2026 01:11:51 +0000</pubDate>
@@ -812,5 +765,297 @@
]]></description>
<enclosure url="https://github.com/openclaw/openclaw/releases/download/v2026.4.29/OpenClaw-2026.4.29.zip" length="50896802" type="application/octet-stream" sparkle:edSignature="YfQ25zMGgDv8XvHbdlL/s0SMJXyu763l5ppnfjiKOjSyxZY9sfoLaoXthcctFQDXA8isR1EEb/EEausu+XkFCA=="/>
</item>
<item>
<title>2026.4.27</title>
<pubDate>Wed, 29 Apr 2026 23:53:26 +0000</pubDate>
<link>https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml</link>
<sparkle:version>2026042790</sparkle:version>
<sparkle:shortVersionString>2026.4.27</sparkle:shortVersionString>
<sparkle:minimumSystemVersion>15.0</sparkle:minimumSystemVersion>
<description><![CDATA[<h2>OpenClaw 2026.4.27</h2>
<h3>Changes</h3>
<ul>
<li>Sandbox/Docker: add opt-in <code>sandbox.docker.gpus</code> passthrough for Docker sandbox containers so local GPU workloads can run inside sandboxed agents when the host Docker runtime supports <code>--gpus</code>. Fixes #57976; carries forward #58124. Thanks @cyan-ember.</li>
<li>iOS/Gateway: add an authenticated <code>node.presence.alive</code> protocol event and <code>node.list</code> last-seen fields so background iOS wakes can mark paired nodes recently alive without treating them as connected. Carries forward #63123. Thanks @ngutman.</li>
<li>Android: publish authenticated <code>node.presence.alive</code> events after node connect and background transitions so paired Android nodes retain durable last-seen metadata after disconnects. Carries forward #63123. Thanks @ngutman.</li>
<li>Gateway/chat: accept non-image attachments through <code>chat.send</code> by staging them as agent-readable media paths, while keeping unsupported RPC attachment paths explicit instead of silently dropping files. Fixes #48123. (#67572) Thanks @samzong.</li>
<li>Security/networking: add opt-in operator-managed outbound proxy routing (proxy.enabled + proxy.proxyUrl/OPENCLAW_PROXY_URL) with strict http:// forward-proxy validation, loopback-only Gateway bypass, and cleanup of proxy env/dispatcher state on exit. (#70044) Thanks @jesse-merhi and @joshavant.</li>
<li>Dependencies: refresh provider and tooling dependencies, including AWS SDK, PI runtime packages, AJV, Feishu SDK, Anthropic SDK, tokenjuice, and native TypeScript/oxlint tooling. Thanks @dependabot.</li>
<li>Matrix/QA: add live Matrix approval scenarios for exec metadata, chunked fallback, plugin approvals, deny reactions, thread targeting, and <code>target: "both"</code> delivery, with redacted artifacts preserving safe approval summaries. Thanks @gumadeiras.</li>
<li>Codex: add Computer Use setup for Codex-mode agents, including <code>/codex computer-use status/install</code>, marketplace discovery, optional auto-install, and fail-closed MCP server checks before Codex-mode turns start. Fixes #72094. (#71842) Thanks @pash-openai.</li>
<li>Apps: consume Peekaboo 3.0.0-beta4 and ElevenLabsKit 0.1.1, align Swabble on Commander 0.2.2, and refresh macOS/iOS SwiftPM resolutions against the released dependency graph. Thanks @Blaizzy.</li>
<li>Plugin SDK: expose shared channel route normalization, parser-driven target resolution, raw-target compact keys, parsed-target types, and route comparison helpers through <code>openclaw/plugin-sdk/channel-route</code>, switch native approval origin matching onto that route contract with optional delivery and match-only target normalization, and retire the internal channel-route shim behind dated compatibility aliases for legacy key/comparable-target helpers. Thanks @vincentkoc.</li>
<li>Docs/Codex: document how Codex Computer Use, direct <code>cua-driver mcp</code>, and OpenClaw.app's PeekabooBridge fit together so desktop-control setup choices are clearer. Thanks @pash-openai and @trycua.</li>
<li>Matrix/streaming: stream tool-progress updates into live Matrix preview edits by default when preview streaming is active, with <code>streaming.preview.toolProgress: false</code> to keep answer previews while hiding interim tool lines. Thanks @gumadeiras.</li>
<li>Plugins/models: wire manifest <code>modelCatalog.aliases</code> and <code>modelCatalog.suppressions</code> into model-catalog planning and built-in model suppression, with stale Spark and Qwen Coding Plan suppressions now declared in plugin manifests instead of runtime fallback hooks. Thanks @shakkernerd.</li>
<li>Plugin SDK/models: add a shared manifest-backed provider catalog builder and move Qianfan, Xiaomi, NVIDIA, Cerebras, Mistral, Moonshot, DeepSeek, Tencent TokenHub, and StepFun provider catalogs onto their plugin manifest <code>modelCatalog</code> rows. Thanks @shakkernerd.</li>
<li>Plugin SDK/models: move BytePlus and Volcano Engine standard and plan-provider catalogs into plugin manifest <code>modelCatalog</code> rows and remove the now-unused Volcengine-family shared catalog SDK subpath. Thanks @shakkernerd.</li>
<li>CLI/models: move Fireworks and Together AI fixed provider catalogs into plugin manifest <code>modelCatalog</code> rows so provider-filtered listing can use manifest-backed static rows. Thanks @shakkernerd.</li>
<li>Channels/Yuanbao: register the Tencent Yuanbao external channel plugin (<code>openclaw-plugin-yuanbao</code>) in the official channel catalog, contract suites, and community plugin docs, with a new <code>docs/channels/yuanbao.md</code> quick-start guide for WebSocket bot DMs and group chats. (#72756) Thanks @loongfay.</li>
<li>Channels/Yuanbao: add a channel docs entrance so the Tencent Yuanbao bot appears in the channel listing and sidebar navigation. (#73443) Thanks @loongfay.</li>
<li>Channels/QQBot: add full group chat support (history tracking, @-mention gating, activation modes, per-group config, FIFO message queue with deliver debounce), C2C <code>stream_messages</code> streaming with a <code>StreamingController</code> lifecycle manager, unified <code>sendMedia</code> with chunked upload for large files, and refactor the engine into pipeline stages, focused outbound submodules, builtin slash-command modules, and explicit DI ports via <code>createEngineAdapters()</code>. (#70624) Thanks @cxyhhhhh.</li>
<li>Plugins/startup: migrate bundled plugin manifests to explicit <code>activation.onStartup</code> declarations so Gateway startup imports only the bundled plugins that intentionally register startup-time runtime surfaces. Thanks @shakkernerd.</li>
<li>Plugins/startup: add an opt-in future-mode gate for disabling deprecated implicit startup sidecar loading while preserving explicit startup and narrower activation triggers. Thanks @shakkernerd.</li>
<li>Plugins/startup: add plugin compatibility warnings for deprecated implicit startup loading so authors can migrate to explicit <code>activation.onStartup</code> metadata. Thanks @shakkernerd.</li>
<li>Plugins/runtime: load bundled agent tool-result middleware from manifest contracts on demand so tokenjuice stays startup-lazy without losing Pi/Codex tool-output compaction. Thanks @shakkernerd.</li>
<li>Plugins/startup: add explicit <code>activation.onStartup</code> metadata so plugins can declare Gateway startup import behavior while the deprecated implicit sidecar fallback remains for legacy plugins. Thanks @shakkernerd.</li>
<li>Gateway/startup: reuse lookup-table plugin manifests when loading startup plugins so Gateway boot avoids rebuilding plugin discovery and manifest metadata. Thanks @shakkernerd.</li>
<li>CLI/models: declare fixed Qianfan, Xiaomi, NVIDIA, Cerebras, Mistral, Chutes, Kilo, OpenAI, and OpenCode Go model catalogs in refreshable plugin manifests, keep broad <code>models list --all</code> on raw registry and supplement rows without runtime normalization, and avoid duplicate supplement resolution. Thanks @shakkernerd.</li>
<li>Gateway/runtime: reuse the current plugin metadata snapshot for provider discovery so repeated model-provider discovery avoids rebuilding plugin manifest metadata. Thanks @shakkernerd.</li>
<li>Gateway/startup: pass the plugin metadata snapshot from config validation into plugin bootstrap so startup reuses one manifest product instead of rebuilding plugin metadata. Thanks @shakkernerd.</li>
<li>Plugin SDK/testing: move core-only channel contract fixtures under the channel contract test tree and retire the old <code>test/helpers/channels</code> bridge directory so plugin tests stay on focused SDK surfaces. Thanks @vincentkoc.</li>
<li>Plugin SDK/testing: expose native agent-runtime contract fixtures through <code>plugin-sdk/agent-runtime-test-contracts</code>, move sandbox config fixtures into the focused generic fixture subpath, and block extension tests from importing repo-only <code>test/helpers</code> bridges. Thanks @vincentkoc.</li>
<li>Plugin SDK/testing: expose generic module reload, bundled-path, Node builtin mock, channel pairing/envelope, HTTP server, temp-home, replay-policy, and live STT helpers through focused SDK test subpaths so extension tests no longer depend on repo-only helper bridges. Thanks @vincentkoc.</li>
<li>Plugin SDK: move maintained bundled channels off the deprecated <code>channel-config-schema-legacy</code> subpath, add an explicit bundled-channel schema SDK surface, and track both remaining legacy test/config compatibility barrels with dated removal windows. Thanks @vincentkoc.</li>
<li>Plugin SDK/testing: expose media provider capability assertions and provider HTTP mocks through focused SDK test subpaths, and retire the repo-only media-generation test helper bridge. Thanks @vincentkoc.</li>
<li>Plugin SDK/testing: promote bundled plugin/provider/channel contract helpers to focused SDK test subpaths and retire the repo-only <code>test/helpers/plugins</code> TypeScript bridge. Thanks @vincentkoc.</li>
<li>Plugin SDK/testing: expose generic channel action, setup, status, and directory contract helpers through <code>plugin-sdk/channel-test-helpers</code> so bundled extension tests no longer import repo-only channel helper bridges. Thanks @vincentkoc.</li>
<li>Plugin SDK/testing: add <code>plugin-sdk/channel-target-testing</code> for shared channel target-resolution cases, document channel reaction helpers on <code>plugin-sdk/channel-feedback</code>, and keep the old <code>plugin-sdk/test-utils</code> alias as compatibility-only. Thanks @vincentkoc.</li>
<li>Plugin SDK/testing: add a focused generic fixture subpath for CLI capture, sandbox, skill, agent-message, system-event, terminal, chunking, auth-token, and typed-case helpers. Thanks @vincentkoc.</li>
<li>Plugin SDK/testing: add focused plugin runtime and environment fixture subpaths so plugin tests can avoid the broad <code>plugin-sdk/testing</code> barrel for common setup helpers. Thanks @vincentkoc.</li>
<li>Plugin SDK/testing: add a focused <code>plugin-sdk/plugin-test-api</code> helper subpath and move bundled plugin registration tests off the repo-only plugin API bridge. Thanks @vincentkoc.</li>
<li>Plugin SDK: add generic host hooks for session state, next-turn context, trusted tool policy, UI descriptors, events, scheduler cleanup, and run-scoped plugin context. (#72287) Thanks @100yenadmin.</li>
<li>Plugin SDK/testing: expose provider catalog, wizard, registry, manifest, public-artifact, outbound, and TTS contract helpers through documented SDK testing seams so bundled plugin tests no longer import repo <code>src/**</code> internals. Thanks @vincentkoc.</li>
<li>Providers/DeepInfra: add a bundled DeepInfra provider with <code>DEEPINFRA_API_KEY</code> onboarding, dynamic OpenAI-compatible model discovery, image generation/editing, image/audio media understanding, TTS, text-to-video, memory embeddings, static catalog metadata, and provider-owned base URL policy. Carries forward #53805, #48088, #37576, #43896, #11533, and #2554. Thanks @ats3v.</li>
<li>Matrix: attach versioned structured approval metadata to pending approval messages so capable Matrix clients can render richer approval UI while body text and reaction fallback keep working. (#72432) Thanks @kakahu2015.</li>
</ul>
<h3>Fixes</h3>
<ul>
<li>Gateway/sessions: align <code>chat.history</code> and <code>sessions.list</code> thinking defaults with owning-agent and catalog-aware resolution so Control UI session defaults match backend runtime state. (#63418) Thanks @jpreagan.</li>
<li>Devices/pairing: recover array-shaped device and node pairing state files before persisting approvals, so UUID-keyed pending and paired entries no longer disappear after a malformed JSON store write. Fixes #63035. Thanks @sar618.</li>
<li>Gateway/auth: clear reused stale device tokens and stop reconnecting on device-token mismatch in the Control UI and Node gateway clients, avoiding rate-limit loops after scope-upgrade or token-rotation handoffs. Fixes #71609. Thanks @ricksayhi.</li>
<li>Gateway/approvals: treat duplicate same-decision approval resolves as idempotent during the resolved-entry grace window, including consumed <code>allow-once</code> approvals, while returning an explicit already-resolved error for conflicting repeats. Fixes #59162; refs #58479 and #65486. Thanks @wikithoughts, @sajazuniga7-coder, and @mjmai20682068-create.</li>
<li>Channels/Telegram: honor <code>approvals.exec/plugin.targets[].accountId</code> when routing native approvals across multi-bot Telegram accounts while preserving unscoped Telegram targets for any account. Fixes #69916. Thanks @joerod26.</li>
<li>Telegram/gateway: bound outbound Bot API calls and cache bundled plugin alias lookup so slow Telegram sends or WSL2 filesystem scans no longer wedge gateway replies. (#74210) Thanks @obviyus.</li>
<li>Agents/exec: omit the internal session-resume fallback preface from successful async exec completion messages sent directly back to chat. Fixes #67181. Thanks @raistlin88.</li>
<li>Agents/media: register detached <code>video_generate</code> and <code>music_generate</code> tool run contexts until terminal status, so Discord-backed provider jobs stay live in <code>/tasks</code> instead of becoming <code>lost</code> when the parent chat run context disappears. Thanks @vincentkoc.</li>
<li>Agents/media: prefer OpenAI image and video providers when the default model uses the OpenAI Codex auth alias, so auto media generation no longer falls through to Fal before GPT Image or Sora. Thanks @vincentkoc.</li>
<li>Tasks/media: infer agent ownership for session-scoped task records so <code>/tasks</code> agent-local fallback includes session-backed <code>video_generate</code> and other async media jobs even when the current chat session has no linked rows. Thanks @vincentkoc.</li>
<li>Agents/media: keep long-running <code>video_generate</code> and <code>music_generate</code> tasks fresh while provider jobs are still pending, so task maintenance does not mark active Discord media renders lost before completion. Thanks @vincentkoc.</li>
<li>CLI/status: treat scope-limited gateway probes as reachable-but-degraded in shared status scans, so <code>openclaw status --all</code> no longer reports a live gateway as unreachable after <code>missing scope: operator.read</code>. Fixes #49180; supersedes #47981. Thanks @openjay.</li>
<li>CLI/update: skip tracked plugins disabled in config during post-update plugin sync before npm, ClawHub, or marketplace update checks, preserving their install records without failing the update. Fixes #73880. Thanks @islandpreneur007.</li>
<li>Slack/Socket Mode: use a 15s Slack SDK pong timeout by default and add <code>channels.slack.socketMode.clientPingTimeout</code>, <code>serverPingTimeout</code>, and <code>pingPongLoggingEnabled</code> overrides so stale-websocket handling no longer depends on app-event health heuristics. Fixes #14248; refs #58519, #64009, and #63488. Thanks @shivasymbl and @freerk.</li>
<li>Slack/media: bound private file and forwarded attachment downloads with idle and total timeouts while preserving placeholder fallback, so stalled Slack <code>file_share</code> media no longer wedges inbound message handling. Fixes #61850. Thanks @bassboy2k.</li>
<li>Plugins/inspector: keep bundled plugin runtime capture quiet and config-tolerant for Codex, memory-lancedb, Feishu, Mattermost, QQBot, and Tlon so plugin-inspector JSON checks can validate the full bundled set. Thanks @vincentkoc.</li>
<li>Slack/auto-reply: keep fully consumed text reset triggers such as <code>new session</code> out of <code>BodyForAgent</code> after directive cleanup, so configured Slack reset phrases do not leak into the fresh model turn. Fixes #73137. Thanks @neeravmakwana.</li>
<li>Plugins/runtime deps: prune stale retained bundled runtime deps and keep doctor/secret channel contract scans on lightweight artifacts, so disabled bundled channels stop preserving old dependency trees or importing heavy plugin surfaces. Thanks @SymbolStar and @vincentkoc.</li>
<li>Plugins/runtime deps: cache unchanged bundled runtime mirror dist-file materialization decisions and close file-lock handles on owner-write failures, reducing repeated startup chunk scans and avoiding FileHandle-GC recovery stalls. Refs #73532. Thanks @oadiazp and @bstanbury.</li>
<li>Auto-reply: bound the post-run pending tool-result delivery drain with a progress-aware idle timeout, so a never-settling tool-result task no longer leaves the session active forever while slow healthy deliveries can keep draining. Fixes #53889; supersedes #64733 and #73434. Thanks @zijunl and @wujiaming88.</li>
<li>Gateway/startup: start chat channels without waiting for primary model prewarm, keeping model warmup bounded in the background so Slack and other channels come online promptly when provider discovery is slow. Supersedes #73420. Thanks @dorukardahan.</li>
<li>Gateway/install: carry env-backed config SecretRefs such as <code>channels.discord.token</code> into generated service environments when they are present only in the installing shell, while keeping gateway auth SecretRefs non-persisted. Fixes #67817; supersedes #73426. Thanks @wdimaculangan and @ztexydt-cqh.</li>
<li>Auto-reply/commands: stop bare <code>/reset</code> and <code>/new</code> after reset hooks acknowledge the command, so non-ACP channels no longer fall through into empty provider calls while <code>/reset <message></code> and <code>/new <message></code> still seed the next model turn. Fixes #73367 and #73412. Thanks @hoyanhan, @wenxu007, and @amdhelper.</li>
<li>Providers/DeepSeek: backfill DeepSeek V4 <code>reasoning_content</code> on plain assistant replay messages as well as tool-call turns, so thinking sessions with prior tool use no longer fail follow-up requests with missing reasoning content. Fixes #73417; refs #71372. Thanks @34262315716 and @Bartok9.</li>
<li>Agents/gateway tool: strip full config payloads from <code>config.patch</code> and <code>config.apply</code> tool responses while preserving direct RPC responses, so config-heavy sessions no longer replay large redacted configs into transcript history. Fixes #47610; supersedes #73439. Thanks @HanenVit and @juan-flores077.</li>
<li>Auto-reply: preserve voice-note media from silent turns while continuing to suppress text and non-voice media, so <code>NO_REPLY</code> TTS replies still deliver the requested audio bubble. (#73406) Thanks @zqchris.</li>
<li>Channels/Mattermost: stop enqueueing regular inbound posts as system events, so Mattermost user messages reach the model only as user-role inbound-envelope content instead of also appearing as <code>System: Mattermost message...</code> directives. Fixes #71795. Thanks @juan-flores077.</li>
<li>Agents/media: qualify bare <code>agents.defaults.imageModel</code> and <code>pdfModel</code> refs from unique configured image-capable providers, so Ollama vision models such as <code>moondream</code> and <code>qwen2.5vl:7b</code> do not fall through to the default provider. Fixes #38816; supersedes #73396. Thanks @alainasclaw and @vincentkoc.</li>
<li>Agents/Anthropic: send implicit Anthropic beta headers only to direct public Anthropic endpoints, including OAuth, so custom Anthropic-compatible providers no longer mis-handle unsupported beta flags unless explicitly configured. Refs #73346. Thanks @byBrodowski.</li>
<li>Skills: require explicit <code>skills.entries.coding-agent.enabled</code> before exposing the bundled coding-agent skill, so installs with Codex on PATH but no OpenAI auth do not silently offer Codex delegation. Fixes #73358. Thanks @LaFleurAdvertising and @Sanjays2402.</li>
<li>Plugins/startup: treat manifestless Claude bundles as valid installed-plugin registry entries instead of stale missing manifests, so workspace bundles no longer force repeated derived registry rebuilds or noisy <code>plugins.entries.workspace</code> warnings during Gateway startup. Fixes #73433. Thanks @AnneVoss.</li>
<li>Agents/subagents: preserve <code>sessions_yield</code> as a paused subagent state and ignore its wait text while freezing completion output, so parent sessions wait for the final post-compaction answer instead of receiving intermediate progress or <code>(no output)</code>. Fixes #73413. Thanks @Ask-sola.</li>
<li>Plugins/startup: precompute bundled runtime mirror fingerprints before taking the mirror lock and keep Docker bundled plugin runtime deps/mirrors in a Docker-managed volume instead of the Windows/WSL config bind mount, so cold starts avoid slow host-volume mirror writes. Fixes #73339. Thanks @1yihui.</li>
<li>Plugins/runtime deps: refresh bundled runtime mirrors without deleting active import trees, so config-triggered restarts do not see transient missing plugin files during registration. Thanks @shakkernerd.</li>
<li>Channels/LINE: persist inbound image, video, audio, and file downloads in <code>~/.openclaw/media/inbound/</code> instead of temporary files so agents can still read LINE media after <code>/tmp</code> cleanup. Fixes #73370. Thanks @hijirii and @wenxu007.</li>
<li>CLI/plugins: keep bundled plugin installs out of <code>plugins.load.paths</code> while preserving install records, so install/inspect/doctor loops no longer warn about the current bundled plugin directory. Thanks @vincentkoc.</li>
<li>CLI/plugins: scope <code>plugins inspect <id></code> runtime loading to the matched plugin so single-plugin inspection does not load every plugin before checking the target. Thanks @shakkernerd.</li>
<li>CLI/plugins: remove managed copied-path plugin directories during uninstall and plan uninstall from metadata instead of runtime-loading plugins, so plugin lifecycle commands avoid unnecessary bundled runtime-deps work. Thanks @shakkernerd.</li>
<li>Cron tool: infer the creating session's agentId for <code>cron.add</code> jobs when <code>agentId</code> is omitted or passed as undefined, keeping scheduled agentTurn jobs routed to the session agent; #40571 identified the guard bug and supplied the focused regression coverage. Thanks @ChanningYul.</li>
<li>Cron/Telegram: add <code>--thread-id</code> to <code>openclaw cron add</code> and <code>openclaw cron edit</code>, preserving Telegram forum topic delivery targets across scheduled announcements. Carries forward #51581, #60373, and #60890. Thanks @ChunHao-dev.</li>
<li>Cron/Telegram: preserve session-derived Telegram topic thread IDs when isolated cron delivery explicitly targets the parent chat, keeping bare chat targets in the active forum topic without leaking stale topics to other chats. Carries forward #64708. Thanks @addelh.</li>
<li>Memory/compaction: keep pre-compaction memory-flush prompts runtime-only so session transcripts and <code>chat.history</code> no longer expose them as normal user turns. Fixes #54408 and #58956; refs #43567. Thanks @markgong and @guoyuhang9.</li>
<li>Control UI/WebChat: keep large attachment payloads out of Lit state and optimistic chat messages, using object URL previews plus send-time payload serialization so PDF/image uploads no longer trigger <code>RangeError: Maximum call stack size exceeded</code>. Fixes #73360; refs #54378 and #63432. Thanks @hejunhui-73, @Ansub, and @christianhernandez3-afk.</li>
<li>Agents/Anthropic: cancel stalled Anthropic Messages SSE body reads when abort signals fire, so active-memory timeouts release transport resources instead of leaving hidden recall runs parked on <code>reader.read()</code>. Refs #72965 and #73120. Thanks @wdeveloper16.</li>
<li>Control UI/WebChat: keep pending run and typing state attached to the active client run, so unowned inject/announce/side-result finals no longer unlock unrelated active runs while completed owned runs still clear promptly. Fixes #57795; carries forward the narrow diagnosis from #57887. Thanks @haoyu-haoyu.</li>
<li>Sandbox/Docker: stop satisfying a missing default sandbox image by tagging plain Debian as <code>openclaw-sandbox:bookworm-slim</code>, preserving the Python tooling required by sandbox write/edit helpers and directing users to build the default image. Fixes #51185; refs #45108, #51099, #51609, and #57713. Thanks @dpalis, @Tin55FoilDev, @jbcohen2-coder, @macminihal-cyber, and @PraxoOnline.</li>
<li>Control UI/WebChat: confirm toolbar New Session button resets before dispatching <code>/new</code> while leaving typed <code>/new</code> and <code>/reset</code> commands immediate. Fixes #45800; refs #27065, #56611, #54499, and #27110. Thanks @aethnova, @kosta228-huli, @adambezemek, and @xss925175263 (xianshishan).</li>
<li>Agents/models: keep per-agent primary models strict when <code>fallbacks</code> is omitted, so probe-only custom providers are not tried as hidden fallback candidates unless the agent explicitly opts in. Fixes #73332. Thanks @haumanto.</li>
<li>Gateway/models: add <code>models.pricing.enabled</code> so offline or restricted-network installs can skip startup OpenRouter and LiteLLM pricing-catalog fetches while keeping explicit model costs working. Fixes #53639. Thanks @callebtc, @palewire, and @rjdjohnston.</li>
<li>Gateway/startup: warn when legacy <code>CLAWDBOT_*</code> or <code>MOLTBOT_*</code> environment variables are still present, pointing users to <code>OPENCLAW_*</code> names instead of failing silently. Fixes #53482; carries forward #53667. Thanks @lndyzwdxhs.</li>
<li>Onboarding: pin interactive and non-interactive health checks to the just-configured setup token/password so stale <code>OPENCLAW_GATEWAY_TOKEN</code> or <code>OPENCLAW_GATEWAY_PASSWORD</code> values do not produce false gateway-token-mismatch failures after setup. Fixes #72203. Thanks @galiniliev.</li>
<li>Doctor/state: require an interactive confirmation before archiving orphan transcript files, so <code>openclaw doctor --fix</code> no longer silently renames recoverable session history after upgrades regenerate <code>sessions.json</code>. Fixes #73106. Thanks @scottgl9.</li>
<li>Cron/Telegram: preserve explicit <code>:topic:</code> delivery targets over stale session-derived thread IDs when isolated cron announces to Telegram forum topics. Carries forward #59069; refs #49704 and #43808. Thanks @roytong9.</li>
<li>Build/runtime: write the runtime-postbuild stamp after <code>pnpm build</code> writes the build stamp, so the next CLI invocation does not re-sync runtime artifacts after a successful build. Fixes #73151. Thanks @bittoby.</li>
<li>Build/runtime: preserve staged bundled-plugin runtime dependency caches across source-checkout tsdown rebuilds, so local CLI and gateway-watch rebuilds no longer recreate large plugin dependency trees before starting. Refs #73205. Thanks @SymbolStar.</li>
<li>CLI/channels: list configured chat channel accounts from read-only setup metadata even when the standalone CLI has not loaded the runtime channel registry, so <code>openclaw channels list</code> shows Telegram accounts before auth providers. Fixes #73319 and #73322. Thanks @mlaihk.</li>
<li>CLI/model probes: keep <code>infer model run --gateway</code> raw by skipping prior session transcript, bootstrap context, context-engine assembly, tools, and bundled MCP servers, so local backends can be tested without full agent-context overhead. Fixes #73308. Thanks @ScientificProgrammer.</li>
<li>CLI/image describe: pass <code>--prompt</code> and <code>--timeout-ms</code> through <code>infer image describe</code> and <code>describe-many</code>, so custom vision instructions and slow local model budgets reach media-understanding providers such as Ollama, OpenAI, Google, and OpenRouter. Addresses #63700. Thanks @cedricjanssens.</li>
<li>Providers/Ollama: reject long non-linguistic Kimi/GLM symbol runs as provider failures instead of storing them as successful visible assistant replies, so fallback or error handling can recover from garbled cloud output. Fixes #64262; refs #67019. Thanks @Kloz813 and @xiaomenger123.</li>
<li>CLI/model probes: reject empty or whitespace-only <code>infer model run --prompt</code> values before calling local providers or the Gateway, so smoke checks do not spend provider calls on invalid turns. Fixes #73185. Thanks @iot2edge.</li>
<li>Gateway/media: route text-only <code>chat.send</code> image offloads through media-understanding fields so <code>agents.defaults.imageModel</code> can describe WebChat attachments instead of leaving only an opaque <code>media://inbound</code> marker. Fixes #72968. Thanks @vorajeeah.</li>
<li>Gateway/Windows: route no-listener restart handoffs through the Windows supervisor without leaving restart tokens in flight, so failed task scheduling can be retried and successful handoffs do not coalesce later restart requests. (#69056) Thanks @Thatgfsj.</li>
<li>Gateway/model pricing: skip plugin manifest discovery during background pricing refreshes when <code>plugins.enabled: false</code>, so disabled-plugin setups do not keep rebuilding plugin metadata from the Gateway hot path. Fixes #73291. Thanks @slideshow-dingo and @fishgills.</li>
<li>Ollama/thinking: validate <code>/think</code> commands against live Ollama catalog reasoning metadata and preserve explicit native <code>params.think</code>/<code>params.thinking</code>, so models whose <code>/api/show</code> capabilities include <code>thinking</code> expose <code>low</code>, <code>medium</code>, <code>high</code>, and <code>max</code> instead of being stuck on <code>off</code>. Fixes #73366. Thanks @cymise.</li>
<li>Gateway/sessions: remove automatic oversized <code>sessions.json</code> rotation backups, deprecate <code>session.maintenance.rotateBytes</code>, and teach <code>openclaw doctor --fix</code> to remove the ignored key so hot session writes no longer copy multi-MB stores. Refs #72338. Thanks @midhunmonachan and @DougButdorf.</li>
<li>Channels/Telegram: fail fast when Telegram rejects the startup <code>getMe</code> token probe with 401, so invalid or stale BotFather tokens are reported as token auth failures instead of misleading <code>deleteWebhook</code> cleanup failures. Fixes #47674. Thanks @samaedan-arch.</li>
<li>ACPX: keep generated Codex and Claude ACP wrapper startup paths working when remote or special state filesystems reject chmod, since OpenClaw invokes the wrappers through Node instead of executing them directly. Fixes #73333. Thanks @david-garcia-garcia.</li>
<li>CLI/onboarding: infer image input for common custom-provider vision model IDs, ask only for unknown models, and keep <code>--custom-image-input</code>/<code>--custom-text-input</code> overrides so vision-capable proxies do not get saved as text-only configs. Fixes #51869. Thanks @Antsoldier1974.</li>
<li>Models/OpenAI Codex: stop listing or resolving unsupported <code>openai-codex/gpt-5.4-mini</code> rows through Codex OAuth, keep stale discovery rows suppressed with a clear API-key-route hint, and leave direct <code>openai/gpt-5.4-mini</code> available. Fixes #73242. Thanks @0xCyda.</li>
<li>Plugin SDK: restore the root <code>stringEnum</code> and <code>optionalStringEnum</code> exports on both the published SDK entry and runtime root-alias bridge, so older external plugins can keep building and loading while migrating to focused SDK subpaths. Fixes #68279. Thanks @marzliak.</li>
<li>Plugin SDK: restore the root-alias bridge for <code>registerContextEngine</code> and expose missing legacy compat helpers <code>normalizeAccountId</code> and <code>resolvePreferredOpenClawTmpDir</code> so older external plugins such as <code>openclaw-weixin</code> can keep loading while migrating to focused SDK subpaths. Fixes #53497. Thanks @alanxchen85.</li>
<li>Auth profiles: make <code>openclaw doctor --fix</code> migrate legacy flat <code>auth-profiles.json</code> files such as <code>{ "ollama-windows": { "apiKey": "ollama-local" } }</code> to canonical provider default API-key profiles with a backup, so custom Ollama/OpenAI-compatible providers recover cleanly after upgrading. Fixes #59629; supersedes #59642. Thanks @Xsanders555 and @Linux2010.</li>
<li>Memory/Dreaming: retry Dream Diary once with the session default when a configured dreaming model is unavailable, while leaving subagent trust and allowlist errors visible instead of silently masking configuration problems. Refs #67409 and #69209. Thanks @Ghiggins18 and @everySympathy.</li>
<li>Feishu/inbound files: recover CJK filenames from plain <code>Content-Disposition: filename=</code> download headers when Feishu exposes UTF-8 bytes through Latin-1 header decoding, while leaving valid Latin-1 and JSON-derived names unchanged. (#48578, #50435, #59431) Thanks @alex-xuweilong, @lishuaigit, and @DoChaoing.</li>
<li>Channels/Telegram: normalize accidental full <code>/bot<TOKEN></code> Telegram <code>apiRoot</code> values at runtime and teach <code>openclaw doctor --fix</code> to remove the suffix, so startup control calls no longer 404 when direct Bot API curl commands work. Fixes #55387. Thanks @brendanmatthewjones-cmyk, @techfindubai-ux, and @Sivlerback-Chris.</li>
<li>Zalo Personal: persist refreshed <code>zca-js</code> session cookies after QR login, session restore, and successful API calls so gateway restarts restore the freshest local session. (#73277) Thanks @darkamenosa.</li>
<li>Logging/security: redact sensitive tokens (sk-\* keys, Bearer/Authorization values, etc.) at the subsystem console sink so <code>createSubsystemLogger().info/warn/error</code> output that bypasses the patched console-capture handler still applies the same redaction the file transport already does. Fixes #73284; refs #67953 and #64046. Thanks @edwin-rivera-dev.</li>
<li>Plugins/runtime deps: reuse enclosing versioned cache roots when bundled plugins resolve from nested staged paths, so plugin-runtime-deps no longer mints <code>openclaw-unknown-*</code> directories or loops on <code>ENOTEMPTY</code>. Fixes #72956. (#73205) Thanks @SymbolStar.</li>
<li>Agents/failover: classify CJK provider transport, quota, billing, auth, and overload error text so Chinese-language provider failures trigger fallback and user-facing transport copy instead of surfacing as unclassified raw errors. (#56242) Thanks @tomcatzh.</li>
<li>Agents/failover: seed non-claude-cli fallback prompts with Claude Code session context when a claude-cli attempt fails, so fallback models do not restart cold after billing or quota failover. (#72069) Thanks @stainlu.</li>
<li>Agents/CLI runner: transfer bundle-MCP tempDir cleanup from the per-turn runner finally to the Claude live-session lifecycle, so persistent Claude CLI sessions keep their <code>--mcp-config</code> directory until the live subprocess closes. Fixes #73244. Thanks @edwin-rivera-dev.</li>
<li>Gateway/nodes: allow Windows companion nodes to use safe declared commands such as canvas, camera list, location, device info, and screen snapshot by default while keeping dangerous media commands opt-in. (#71884) Thanks @shanselman.</li>
<li>Agents/cron: clarify agent-tool and CLI cron timezone guidance so supplied <code>tz</code> values use local wall-clock cron fields and omitted cron <code>tz</code> falls back to the Gateway host local timezone. Fixes #53669; carries forward #46177. (#73372) Thanks @chen-zhang-cs-code and @maranello-o.</li>
<li>Providers/Qwen: allow explicitly configured <code>qwen/qwen3.6-plus</code> to resolve on Qwen Coding Plan endpoints while keeping the built-in catalog from advertising it there. Fixes #63654; carries forward #63987. Thanks @jepson-liu.</li>
<li>Channels/Telegram: keep Bot API network fallbacks sticky after failed attempts and retry timed-out startup control calls once on the fallback route, so <code>deleteWebhook</code> IPv6 stalls no longer trigger slow multi-account retry storms. Fixes #73255. Thanks @ttomiczek and @sktbrd.</li>
<li>Gateway/agents: accept heartbeat, cron, and webhook as internal channel hints for agent runs so <code>sessions_spawn</code> works from non-delivery parent sessions while unknown channel hints still fail closed. Fixes #73237. Thanks @KeWang0622.</li>
<li>Gateway/models: merge explicit <code>models.providers.*.models</code> rows into the Gateway model catalog with normalized provider/model dedupe, and use normalized image-capability lookup so custom vision models keep native image attachments even when Pi discovery omits them or model ID casing differs. Fixes #64213 and #65165. Thanks @billonese and @202233a.</li>
<li>Gateway/reload: publish canonical post-write source config to in-process reloaders so simple config saves no longer create phantom plugin diffs or trigger unnecessary Gateway restarts. (#73267) Thanks @szsip239.</li>
<li>Gateway/Docker: keep config-triggered restarts in-process inside containers instead of spawning a detached child and exiting PID 1 cleanly, so Docker Swarm and other on-failure supervisors do not leave the service stuck at 0/1 replicas. Fixes #73178. Thanks @du-nguyen-IT007.</li>
<li>CLI/tasks: ship the task-registry control runtime in npm packages so <code>openclaw tasks cancel</code> can load ACP/subagent cancellation helpers from published builds. Fixes #68997. Thanks @1OAKDesign.</li>
<li>Channels/Telegram: preserve unsent generated media after partial reply streaming has already delivered the text, so <code>image_generate</code> outputs still reach Telegram as photos instead of being dropped from the final payload. Fixes #73253. Thanks @mlaihk.</li>
<li>Memory-core/dreaming: cap detached Dream Diary narrative subagents across cron sweeps so multi-workspace dreaming no longer fans out unbounded subagent sessions, lock contention, and cascading narrative timeouts. Fixes #73198. (#73287) Thanks @KeWang0622.</li>
<li>CLI/agents: close local one-shot Claude live stdio sessions and bundled MCP loopback resources after embedded <code>openclaw agent --local</code> runs, while keeping gateway-owned MCP loopback cleanup internal to the Gateway. Thanks @frankekn.</li>
<li>Export/session: keep inline export HTML scripts and vendor libraries injected after template formatting so generated session exports open with the app code, markdown renderer, and syntax highlighter present. Fixes #41862 and #49957; carries forward #41861 and #68947. Thanks @briannewman, @martenzi, and @armanddp.</li>
<li>Agents/ACPX: stage the patched Claude ACP adapter as an ACPX runtime dependency and route known Codex/Claude ACP commands through local wrappers, so Gateway runtime no longer depends on live <code>npx</code> adapter resolution. Fixes #73202. Thanks @joerod26.</li>
<li>Memory/compaction: let pre-compaction memory flush use an exact <code>agents.defaults.compaction.memoryFlush.model</code> override such as <code>ollama/qwen3:8b</code> without inheriting the active session fallback chain, so local housekeeping can avoid paid conversation models. Fixes #53772. Thanks @limen96.</li>
<li>macOS/update: stop managed Gateway services before package replacement and keep LaunchAgent service secrets out of world-readable plist metadata by loading them from owner-only env files. Fixes #72996. Thanks @Mathewb7.</li>
<li>Google Meet: keep observe-only Chrome joins and setup checks from requiring BlackHole or audio bridge commands, avoid granting or selecting the microphone in observe-only mode, and make <code>test_speech</code> report fresh realtime output-byte verification instead of only confirming a queued utterance. Refs #72478. Thanks @DougButdorf.</li>
<li>Gateway/hooks: route non-delivered hook completion and error summaries to the target agent's main session instead of the default agent session, preserving multi-agent hook isolation. Fixes #24693; carries forward #68667. Thanks @abersonFAC and @bluesky6868.</li>
<li>Control UI/models: request the configured Gateway model-list view so dashboards with only <code>models.providers.*.models</code> show those configured models first instead of flooding the picker with the full built-in catalog. Fixes #65405. Thanks @wbyanclaw.</li>
<li>CLI/models: keep default-model and allowlist pickers on explicit <code>models.providers.*.models</code> entries when <code>models.mode</code> is <code>replace</code> instead of loading the full built-in catalog. Fixes #64950. Thanks @mrozentsvayg.</li>
<li>Media/security: tighten media-understanding MIME sanitization so parameterized MIME values stay end-anchored and malformed whitespace or suffix payloads are rejected before file-context handling. Fixes #9795; carries forward #68225 with related review/test context from #61016/#68456. Thanks @ymaxgit, @bluesky6868, and @shamsulalam1114.</li>
<li>Discord: own the Carbon interaction listener and hand off Discord slash/component handling asynchronously, so compaction or long session locks no longer trip <code>InteractionEventListener</code> listener timeouts. Fixes #73204. Thanks @slideshow-dingo.</li>
<li>Compaction/diagnostics: keep unknown compaction failure classifications stable while logging sanitized detail for unclassified provider errors such as missing Ollama provider adapters. Thanks @gzsiang.</li>
<li>Models/fallbacks: record first-class <code>model.fallback_step</code> trajectory events with from/to models, failure detail, chain position, and final outcome so support exports preserve the primary model failure even when a later fallback also fails. Fixes #71744. Thanks @nikolaykazakovvs-ux.</li>
<li>Gateway/agents: block agent <code>exec</code> from launching interactive <code>openclaw channels login</code> flows and abort active agent runs after invalid-config recovery restores last-known-good config, preventing known channel-login and reload paths from wedging replies. Refs #72338. Thanks @midhunmonachan.</li>
<li>Gateway/diagnostics: emit payload-free liveness warnings with event-loop delay, event-loop utilization, CPU-core ratio, active-session counts, and OTEL warning metrics/spans so live-but-stalled Gateways capture CPU-spin context in stability bundles and telemetry. Refs #72338. Thanks @midhunmonachan and @DougButdorf.</li>
<li>Gateway/startup: keep value-option foreground starts on the gateway fast path and skip proxy bootstrap unless proxy env is configured, reducing normal gateway startup RSS and avoiding full CLI graph loading. Thanks @vincentkoc.</li>
<li>Heartbeat/models: show heartbeat model bleed guidance on context-overflow resets when the last runtime model matches configured <code>heartbeat.model</code>, so smaller local heartbeat models point users to <code>isolatedSession</code> or <code>lightContext</code> instead of only compaction-buffer tuning. Fixes #67314. Thanks @Knightmare6890.</li>
<li>Subagents/models: persist <code>sessions_spawn.model</code> and configured subagent models as child-session model overrides before the first turn, so spawned subagents actually run on the requested provider/model instead of reverting to the target agent default. Fixes #73180. Thanks @danielzinhu99.</li>
<li>Channels/Telegram: keep webhook-mode local listeners alive and retry Telegram <code>setWebhook</code> registration after recoverable startup network failures, so transient Bot API timeouts no longer leave reverse proxies pointing at a closed listener. Fixes #71834. Thanks @jinon86.</li>
<li>Agents/ACPX: bundle the Codex ACP adapter and launch it from the isolated <code>CODEX_HOME</code> wrapper before falling back to npm, so Codex ACP startup no longer depends on live <code>npx</code> resolution or the stale <code>@zed-industries/codex-acp@^0.11.1</code> range. Fixes #72037; refs #73202. Thanks @jasonftl, @sazora, and @joerod26.</li>
<li>Agents/ACPX: register the embedded ACP backend at Gateway startup through a lightweight ACP backend SDK path and without importing the heavy ACPX runtime until an ACP session or explicit startup probe needs it, reducing baseline Gateway RSS. Thanks @vincentkoc.</li>
<li>CLI/update: keep restart health polling when the restarted Gateway is reachable but has not reported its version yet, so macOS service restarts do not fail early with <code>actual unavailable</code>. Thanks @ProspectOre.</li>
<li>Backup: skip installed plugin <code>extensions/*/node_modules</code> dependency trees while keeping plugin manifests and source files in archives, so local backups avoid rebuildable npm payload bloat. Fixes #64144. Thanks @BrilliantWang.</li>
<li>Cron/models: fail isolated cron runs closed when an explicit <code>payload.model</code> is not allowed or cannot be resolved, so scheduled jobs do not silently fall back to an unrelated agent default or paid route before configured provider proxies such as LiteLLM can run. Fixes #73146. Thanks @oneandrewwang.</li>
<li>Memory/QMD: back off repeated chat-turn QMD open failures while still letting memory status and CLI probes recheck immediately, so a broken sidecar dependency cannot trigger active-memory or cron retry storms. Fixes #73188 and #73176. Thanks @leonlushgit and @w3i-William.</li>
<li>Talk Mode: resolve <code>messages.tts.providers.<id>.apiKey</code> through the active runtime snapshot for <code>talk.config</code>, so Talk overlays can discover SecretRef-backed speech providers without falling back to local speech. Fixes #73109. (#73111) Thanks @omarshahine.</li>
<li>Memory/Ollama: resolve <code>memorySearch.provider</code> custom provider ids through their configured <code>models.providers.<id>.api</code> owner, so multi-GPU Ollama setups can dedicate embeddings to providers such as <code>ollama-5080</code> without losing the Ollama adapter or local auth semantics. Fixes #73150. Thanks @oneandrewwang.</li>
<li>CLI/memory: skip eager context-window warmup for <code>openclaw memory</code> commands so memory search does not race unrelated model metadata discovery. Fixes #73123. Thanks @oalansilva and @neeravmakwana.</li>
<li>CLI/Telegram: route Telegram <code>message send</code> and poll actions through the running Gateway when available, so packaged installs use the staged <code>grammy</code> runtime deps and CLI sends return instead of hanging after the Telegram channel is active. Fixes #73140. Thanks @oalansilva.</li>
<li>Plugins/runtime deps: prepare staged bundled plugin dependencies before loading packaged public surfaces, so OpenClaw's Telegram runtime/test facade loads resolve <code>grammy</code> from the managed runtime-deps stage without copying dependencies into the global package root. Refs #73140. Thanks @oalansilva.</li>
<li>Agents/exec: emit <code>(no output)</code> for silent exec update and node-host result blocks so Anthropic-compatible providers no longer reject empty tool-result text after quiet commands. Fixes #73117. Thanks @pfrederiksen and @Sanjays2402.</li>
<li>Cron/providers: preflight local Ollama and OpenAI-compatible provider endpoints before isolated cron agent turns, record unreachable local providers as skipped runs, and cache dead-endpoint probes so many jobs do not hammer the same stopped local server. Fixes #58584. Thanks @jpeghead.</li>
<li>Gateway/config: let config reload continue in degraded mode when invalidity is scoped to plugin entries, so incompatible plugin configs can be skipped and the Gateway restart can still pick up the rest of the config after rollbacks. Fixes #73131. Thanks @Adam-Researchh.</li>
<li>Doctor/channels: suppress disabled bundled-plugin blocker warnings when a trusted external plugin owns the configured channel, so Lark/Feishu installs no longer get Feishu repair noise after switching to <code>openclaw-lark</code>. Fixes #56794. Thanks @wuji-tech-dev.</li>
<li>CLI/status: show skipped fast-path memory checks as <code>not checked</code> and report active custom memory plugin runtime status from <code>status --json --all</code> without requiring built-in <code>agents.defaults.memorySearch</code>, so plugins such as memory-lancedb-pro and memory-cms no longer look unavailable when their own runtime is healthy. Fixes #56968. Thanks @Tony-ooo and @aderius.</li>
<li>Gateway/channels: record and log unexpected clean channel monitor exits so channels that return without throwing no longer appear stopped with no error. Fixes #73099. Thanks @balaji1968-kingler.</li>
<li>Discord/group chats: keep group/channel replies private by default unless the agent explicitly uses the message tool, so always-on rooms can lurk without leaking automatic final, block, preview, or status-reaction output; <code>messages.groupChat.visibleReplies: "automatic"</code> restores legacy auto-posting. (#73046) Thanks @scoootscooob.</li>
<li>Plugins/package: force nested bundled-plugin runtime dependency installs out of inherited npm dry-run mode during prepack and package smoke checks, so packed installs materialize required plugin modules instead of reporting missing bundled files. Refs #73128. Thanks @Adam-Researchh.</li>
<li>Discord: skip reaction events before REST channel fetch when notifications are off, guild reactions are disabled, or allowlist mode cannot match without channel overrides, reducing reconnect bursts that caused slow listener warnings. Fixes #73133. Thanks @isaacsummers.</li>
<li>Channels/Telegram: centralize polling update tracking so accepted offsets remain durable across restarts, same-process handler failures can still retry, and slow offset writes cannot overwrite newer accepted watermarks. Refs #73115. Thanks @vdruts.</li>
<li>Agents/models: classify empty, reasoning-only, and planning-only terminal agent runs before accepting a model fallback candidate, so invalid or incompatible models can advance to the next configured fallback instead of returning a 30-second terminal failure. Fixes #73115. Thanks @vdruts.</li>
<li>Memory/LanceDB: let embedding config use provider-backed auth profiles, environment credentials, or provider config without a separate plugin <code>embedding.apiKey</code>, so OAuth-capable embedding providers can power auto-recall/capture. Fixes #68950. Thanks @malshaalan-ai.</li>
<li>CLI/parents: invoking <code>openclaw <parent></code> (memory, channels, plugins, approvals, devices, cron, mcp) without a subcommand now prints the parent's help and exits <code>0</code>, matching <code><parent> --help</code> and the existing <code>agents</code> / <code>sessions</code> defaults so shell <code>&&</code> chains and pnpm wrappers no longer surface a misleading <code>ELIFECYCLE Command failed with exit code 1.</code> line. Fixes #73077. Thanks @hclsys.</li>
<li>Plugins/hooks: time out never-settling <code>agent_end</code> observation hooks after 30 seconds and log the plugin failure, so hung embedding endpoints no longer leave memory capture silently pending forever. Fixes #65544. Thanks @ghoc0099.</li>
<li>Gateway/config: serve runtime config schemas from the current plugin metadata snapshot and generated bundled channel schema metadata instead of rebuilding plugin channel config modules on every <code>config.get</code>/<code>config.schema</code>, preventing idle plugin-discovery CPU churn after upgrades. Fixes #73088. Thanks @sleitor and @geovansb.</li>
<li>Memory/LanceDB: call OpenAI-compatible embedding endpoints through the raw SDK transport without sending <code>encoding_format</code>, then normalize float-array or base64 responses so providers such as ZhiPu and DashScope no longer fail recall with wrong vector dimensions or rejected parameters. Fixes #63655. Thanks @kinthaiofficial.</li>
<li>Plugins/install: run dependency installs with npm error-level logging instead of silent mode so failed plugin or hook installs surface actionable npm errors such as EUNSUPPORTEDPROTOCOL instead of <code>npm install failed:</code> with no detail. (#73093) Thanks @sanctrl.</li>
<li>Memory/LanceDB: bound memory recall embedding queries with a new <code>recallMaxChars</code> setting, prefer the latest user message over channel prompt metadata during auto-recall, and document the knob so small Ollama embedding models avoid context-length failures. Fixes #56780. Thanks @rungmc357 and @zak-collaborator.</li>
<li>CLI/skills: resolve workspace-backed skills commands from <code>--agent</code>, then the current agent workspace, before falling back to the default agent, so multi-agent ClawHub installs, updates, and status checks stay scoped to the active workspace. Fixes #56161; carries forward #72726. Thanks @langbowang and @luyao618.</li>
<li>Plugin SDK: fall back from partial bundled plugin directory overrides to package source public surfaces while preserving <code>OPENCLAW_DISABLE_BUNDLED_PLUGINS</code> as a hard disable. (#72817) Thanks @serkonyc.</li>
<li>Agents/ACPX: stop forwarding Codex ACP timeout config controls that Codex rejects while preserving OpenClaw's run-timeout watchdog for ACP subagents. Fixes #73052. Thanks @pfrederiksen and @richa65.</li>
<li>Memory Core: stream fallback vector search scoring with a bounded top-K result set so large indexes do not materialize every chunk embedding when sqlite-vec is unavailable. (#73069) Thanks @parkertoddbrooks.</li>
<li>Memory Core: stream embedding-cache seeding during safe reindex so large local caches do not materialize every row into the V8 heap before the atomic rebuild. (#73067) Thanks @parkertoddbrooks.</li>
<li>Memory/Ollama: add <code>memorySearch.remote.nonBatchConcurrency</code> for inline embedding indexing, default Ollama non-batch indexing to one request at a time, and keep batch concurrency separate from non-batch concurrency so local embedding backfills avoid timeout storms on smaller hosts. Carries forward #57733. Thanks @itilys.</li>
<li>macOS app: update Peekaboo, ElevenLabsKit, and MLX TTS helper dependencies, make canvas file watching and config/exec-approval state writes reliable under concurrent app/test activity, and keep the app plus helper builds warning-free. Thanks @Blaizzy.</li>
<li>iOS app: refresh SwiftPM/XcodeGen source hygiene, make app, extension, watch, and curated shared Swift files pass the prebuild SwiftFormat and SwiftLint checks, move relay registration off deprecated StoreKit receipt APIs, and keep simulator builds and logic tests warning-free. Thanks @ngutman.</li>
<li>Agents/models: keep <code>models.json</code> readiness and provider-hook caches warm across repeated agent and subagent model resolution while preserving external <code>models.json</code> invalidation, reducing repeated provider-plugin loads on slower ARM64 hosts. Fixes #73075. Thanks @jochen.</li>
<li>Docs/tools: clarify that <code>tools.profile: "messaging"</code> is intentionally narrow and that <code>tools.profile: "full"</code> is the unrestricted baseline for broader command/control access. Carries forward #39954. Thanks @posigit.</li>
<li>Control UI/Agents: redact tool-call args, partial/final results, derived exec output, and configured custom secret patterns before streaming tool events to the Control UI, so tool output cannot expose provider or channel credentials. Fixes #72283. (#72319) Thanks @volcano303 and @BunsDev.</li>
<li>Agents/sessions: keep <code>sessions_history</code> recall redaction enabled even when general log redaction is disabled, and clarify that safety-boundary UI/tool/diagnostic payloads still redact independently of <code>logging.redactSensitive</code>. Carries forward #72319. Thanks @volcano303 and @BunsDev.</li>
<li>Providers/Codex: pass agent and workspace directories into provider stream wrappers so Codex native <code>web_search</code> activation can evaluate the correct auth context, and smoke-test the built status-message runtime by resolving the emitted bundle name. Carries forward #67843; refs #65909. Thanks @neilofneils404.</li>
<li>Cron/models: keep <code>payload.model</code> as a per-job primary that can use configured fallbacks, while still letting <code>payload.fallbacks: []</code> make cron runs strict and avoid hidden agent-primary retries. Refs #73023. Thanks @pavelyortho-cyber.</li>
<li>Models/fallbacks: treat user-selected session models as exact choices, so <code>/model ollama/...</code> and model-picker switches fail visibly when the selected provider is unreachable instead of answering from an unrelated configured fallback. Fixes #73023. Thanks @pavelyortho-cyber.</li>
<li>Codex harness: keep ChatGPT subscription app-server runs from inheriting <code>CODEX_API_KEY</code> or <code>OPENAI_API_KEY</code>, and fall back to <code>CODEX_API_KEY</code> / <code>OPENAI_API_KEY</code> app-server login only when no Codex account is available. Fixes #73057. Thanks @holgergruenhagen and @pashpashpash.</li>
<li>CLI/model probes: fail local <code>infer model run</code> probes when the provider returns no text output, so unreachable local providers and empty completions no longer look like successful smoke tests. Refs #73023. Thanks @pavelyortho-cyber.</li>
<li>CLI/Ollama: run local <code>infer model run</code> through the lean provider completion path and skip global model discovery for one-shot local probes, so Ollama smoke tests no longer pay full chat-agent/tool startup cost or hang before the native <code>/api/chat</code> request. Fixes #72851. Thanks @TotalRes2020.</li>
<li>Doctor/gateway services: ignore launchd/systemd companion services that only reference the gateway as a dependency, suppress inactive Linux extra-service warnings, and avoid rewriting a running systemd gateway command/entrypoint during doctor repair. Carries forward #39118. Thanks @therk.</li>
<li>Daemon/service: only emit hard-coded version-manager paths such as <code>~/.volta/bin</code>, <code>~/.asdf/shims</code>, <code>~/.bun/bin</code>, and fnm/pnpm fallbacks into gateway and node service PATHs when the directories exist, so <code>openclaw doctor</code> no longer flags <code>gateway.path.non-minimal</code> against a PATH the daemon just wrote. Env-driven roots and stable user-bin dirs remain unconditional. Fixes #71944; carries forward #71964. Thanks @Sanjays2402.</li>
<li>CLI/startup: disable Node's module compile cache automatically for live source-checkout launchers so in-place <code>pnpm build</code> updates are visible to the next <code>openclaw</code> CLI invocation. Fixes #73037. Thanks @LouisGameDev.</li>
<li>Agents/group chat: keep silent-allowed empty and reasoning-only turns on the <code>NO_REPLY</code> path without injecting visible-answer retry prompts, and clarify the group prompt so agents use the exact silent token instead of prose. Thanks @vincentkoc.</li>
<li>Agents/group chat: move <code>NO_REPLY</code> mechanics into channel-aware direct/group prompts and suppress the duplicate generic silent-reply section for auto-reply runs, so always-on group agents get one consistent stay-silent instruction. Thanks @vincentkoc.</li>
<li>Providers/OpenAI: preserve encrypted empty-summary Responses reasoning items in WebSocket replay and request <code>reasoning.encrypted_content</code> on reasoning turns so GPT-5.4/GPT-5.5 sessions do not lose required <code>rs_*</code> state beside <code>msg_*</code> items. Fixes #73053. Thanks @odb36777.</li>
<li>Gateway/startup: treat <code>plugins.enabled=false</code> as an early plugin fast path, skipping plugin auto-enable discovery, gateway plugin lookup/runtime-dependency staging, and stale-plugin cleanup warnings while preserving channel blocker warnings. (#73041) Thanks @WuKongAI-CMU.</li>
<li>Channels/commands: make generated <code>/dock-*</code> commands switch the active session reply route through <code>session.identityLinks</code> instead of falling through to normal chat. Fixes #69206; carries forward #73033. Thanks @clawbones and @michaelatamuk.</li>
<li>Providers/Cloudflare AI Gateway: strip assistant prefill turns from Anthropic Messages payloads when thinking is enabled, so Claude requests through Cloudflare AI Gateway no longer fail Anthropic conversation-ending validation. Fixes #72905; carries forward #73005. Thanks @AaronFaby and @sahilsatralkar.</li>
<li>Gateway/startup: keep primary-model startup prewarm on scoped metadata preparation, let native approval bootstraps retry outside channel startup, and skip the global hook runner when no <code>gateway_start</code> hook is registered, so clean post-ready sidecar work stays off the critical path. Refs #72846. Thanks @RayWoo, @livekm0309, and @mrz1836.</li>
<li>Gateway/channels: start bundled channel accounts with a lightweight <code>runtimeContexts</code> surface instead of importing the full reply/routing/session channel runtime before <code>startAccount</code>, so Discord, Telegram, Slack, Matrix, and QQBot startup no longer block on unrelated channel helper graphs. Refs #72846 and #72960. Thanks @mrz1836, @RayWoo, and @rollingshmily.</li>
<li>Gateway/supervisor: exit cleanly when a supervised restart finds an existing healthy gateway and bound retries when the existing gateway stays unhealthy, so stale lock contention cannot loop indefinitely. Refs #72846. Thanks @azgardtek.</li>
<li>Gateway/startup: scope primary-model provider discovery during channel prewarm to the configured provider owner and add split startup trace timings, so boot avoids staging unrelated bundled provider dependencies while setup discovery remains broad. Fixes #73002. Thanks @Schnup03.</li>
<li>Plugins/runtime deps: declare retained staged bundled plugin dependencies in the npm staging manifest while installing only newly missing packages, so Gateway restarts avoid reinstalling the full retained dependency set when one runtime dependency is absent. Fixes #73055. Thanks @GCorp2026.</li>
<li>CLI/status: keep default <code>openclaw status</code> off the heavyweight security audit, plugin compatibility, and memory-vector probes while still showing configured Telegram channels through setup metadata, so routine health checks stay fast and no longer render an empty Channels table. Fixes #72993. Thanks @comick1.</li>
<li>Channels/Telegram: send a best-effort native typing cue immediately after an inbound message is accepted, so slow pre-dispatch turns show Telegram liveness before queueing, compaction, model, or tool work starts. Fixes #63759. Thanks @alessandropcostabr.</li>
<li>Channels/Telegram: stop native approval startup auth failures from retrying every second, while still waiting through retryable Gateway auth handoffs, so Telegram approval setup problems no longer create a reconnect/log loop during channel startup. Refs #72846 and #72867. Thanks @kiranvk-2011 and @porly1985.</li>
<li>Channels/Microsoft Teams: unwrap staged CommonJS JWT runtime dependencies before Bot Connector token validation so inbound Teams messages no longer 401 after the bundled runtime-deps move. Fixes #73026. Thanks @kbrown10000.</li>
<li>Gateway/auth: allow local direct callers in trusted-proxy mode to use the configured gateway password as an internal fallback while keeping token fallback rejected. Fixes #17761. Thanks @dashed, @vincentkoc, and @jetd1.</li>
<li>Gateway/auth: add explicit <code>trustedProxy.allowLoopback</code> support for same-host loopback reverse proxies while keeping loopback trusted-proxy auth fail-closed by default and preserving required-header and allowlist checks. Fixes #59167; carries forward #63379. Thanks @Matir, @jeremyakers, and @mrosmarin.</li>
<li>Channels/sessions: prevent guarded inbound session recording from creating route-only phantom sessions while still allowing last-route updates for sessions that already exist. Carries forward #73009. Thanks @jzakirov.</li>
<li>Cron: accept <code>delivery.threadId</code> in Gateway cron add/update schemas so scheduled announce delivery can target Telegram forum topics and other threaded channel destinations through the documented delivery path. Fixes #73017. Thanks @coachsootz.</li>
<li>Plugins/runtime deps: stage bundled plugin dependencies imported by mirrored root dist chunks, so packaged memory and status commands do not miss <code>chokidar</code> or similar root-chunk dependencies after update. Fixes #72882 and #72970; carries forward #72992. Thanks @shrimpy8, @colin-chang, and @Schnup03.</li>
<li>Plugins/runtime deps: reuse unchanged bundled plugin runtime mirrors instead of rebuilding plugin trees on every load, cutting avoidable writes and restart/reconnect I/O on slow storage. Fixes #72933. Thanks @jasonftl.</li>
<li>Agents/runtime context: deliver hidden runtime context through prompt-local system context while keeping the transcript-only custom entry out of provider user turns, and strip stale copied runtime-context prefaces from user-facing replies. Fixes #72386; carries forward #72969. Thanks @jhsmith409.</li>
<li>Channels/Telegram: skip the optional webhook-info API call during polling-mode status checks and startup bot-label probes so long-polling setups avoid an unnecessary Telegram round trip. Carries forward #72990. Thanks @danielgruneberg.</li>
<li>CLI/message: resolve targeted <code>openclaw message</code> channels to their owning plugin before loading the registry, and fall back to configured channel plugins when the channel must be inferred, so scripted sends avoid full bundled plugin registry scans without assuming channel ids match plugin ids. Fixes #73006. Thanks @jasonftl.</li>
<li>Plugins/startup: parse strict JSON plugin manifests with native JSON first and keep JSON5 as the compatibility fallback, reducing manifest registry CPU during Gateway boot and CLI startup. Fixes #73011. Thanks @jasonftl.</li>
<li>CLI/models: keep route-first <code>models status --json</code> stdout reserved for the JSON payload by routing auth-profile and startup diagnostics to stderr. Fixes #72962. Thanks @vishutdhar.</li>
<li>Gateway/runtime: keep dirty-tree status calls from rebuilding live <code>dist</code>, clear stale task and restart state across in-process restarts, retry transient Discord lazy imports, and let channel startup continue after slow model warmup so browser, Discord, and voice-call sidecars come online. Thanks @vincentkoc.</li>
<li>Security/CodeQL: replace file SecretRef id gateway schema regex validation with segment-aligned predicates and set empty permissions on release summary/backfill jobs so the narrowed CodeQL profile stays clean. Thanks @vincentkoc.</li>
<li>Sessions: ignore future-dated session activity timestamps during reset freshness checks and cap future <code>updatedAt</code> values at the merge boundary so clock-skewed messages cannot keep stale sessions alive forever. Fixes #72989. Thanks @martingarramon.</li>
<li>Sessions: apply search, activity filters, and limits before gateway row enrichment so bounded session lists avoid scanning discarded transcripts. Carries forward #72978. Thanks @yeager.</li>
<li>Sessions: remove trajectory runtime and pointer sidecars when session maintenance prunes, caps, or disk-evicts their owning session, while preserving sidecars still referenced by live rows. Fixes #73000. Thanks @jared-rebel.</li>
<li>Plugins/CLI: allow managed plugin installs when the active extensions root is a symlink to a real state directory, while keeping nested target symlinks blocked and suppressing misleading hook-pack fallback errors for install-boundary failures. Fixes #72946. Thanks @mayank6136.</li>
<li>Providers/Ollama: mark discovered Ollama catalog models as supporting streaming usage metadata so token accounting stays enabled for local models. (#72976) Thanks @sdeyang.</li>
<li>Media understanding: reject malformed MIME values with trailing junk while preserving standard parameter tails before enrichment uses them. (#72914) Thanks @volcano303.</li>
<li>WebChat: keep bare <code>/new</code> and <code>/reset</code> prompts from producing empty transcript text by inserting the hidden session marker when the visible tail is blank. (#72863) Thanks @mahopan.</li>
<li>CLI/update: explain completion-cache refresh timeouts with manual refresh guidance instead of surfacing a raw low-level timeout. Fixes #72842. (#72850) Thanks @iot2edge.</li>
<li>Memory-core/dreaming: give narrative generation a 60-second timeout so slower local or remote models can finish instead of timing out at 15 seconds. Fixes #72837. (#72852) Thanks @RayWoo.</li>
<li>Plugins/hooks: inject each plugin's resolved config into internal hook event context without mutating the shared event object. (#72888) Thanks @jalapeno777.</li>
<li>Agents/ACP: pass the resolved ACP agent directory into media understanding so per-agent media caches and config are used for ACP-dispatched image turns. (#72832) Thanks @luyao618.</li>
<li>Gateway/Bonjour: truncate mDNS service names and host labels to the 63-byte DNS label limit at valid UTF-8 boundaries. (#72809) Thanks @luyao618.</li>
<li>Feishu: treat groups explicitly configured under channels.feishu.groups as admitted even when groupAllowFrom is empty, while preserving groupPolicy: "disabled" as a hard group block and keeping groups.\* wildcard defaults non-admitting. Fixes #67687. (#72789) Thanks @MoerAI.</li>
<li>Gateway/startup: keep hot Gateway boot paths on leaf config imports and add max-RSS reporting to the gateway startup bench so low-memory startup regressions are visible before release. Thanks @vincentkoc.</li>
<li>WebChat: read <code>chat.history</code> from active transcript branches, drop stale streamed assistant tails once final history catches up, and coalesce duplicate in-flight Control UI submits, so rewritten prompts, completed replies, and rapid send events no longer render or process twice. Fixes #72975, #72963, and #72974. Thanks @dmagdici, @lhtpluto, and @Benjamin5281999.</li>
<li>WebChat/TTS: persist automatic final-mode TTS audio as a supplemental audio-only transcript update instead of adding a second assistant message with the same visible text. Fixes #72830. Thanks @lhtpluto.</li>
<li>Agents/LSP: terminate bundled stdio LSP process trees during runtime disposal and Gateway shutdown, so nested children such as <code>tsserver</code> do not survive stop or restart. Fixes #72357. Thanks @ai-hpc and @bittoby.</li>
<li>Diagnostics/OTEL: capture privacy-safe model-call request payload bytes, streamed response bytes, first-response latency, and total duration in diagnostic events, plugin hooks, stability snapshots, and OTEL model-call spans/metrics without logging raw model content. Fixes #33832. Thanks @wwh830.</li>
<li>Logging: write validated diagnostic trace context as top-level <code>traceId</code>, <code>spanId</code>, <code>parentSpanId</code>, and <code>traceFlags</code> fields in file-log JSONL records so traced requests and model calls are easier to correlate in log processors. Refs #40353. Thanks @liangruochong44-ui.</li>
<li>Logging/sessions: apply configured redaction patterns to persisted session transcript text and accept escaped character classes in safe custom redaction regexes, so transcript JSONL no longer keeps matching sensitive text in the clear. Fixes #42982. Thanks @panpan0000.</li>
<li>Providers/Ollama: honor <code>/api/show</code> capabilities when registering local models so non-tool Ollama models no longer receive the agent tool surface, and keep native Ollama thinking opt-in instead of enabling it by default. Fixes #64710 and duplicate #65343. Thanks @yuan-b, @netherby, @xilopaint, and @Diyforfun2026.</li>
<li>Control UI/Agents: remount the Overview model controls when switching agents so the primary-model picker cannot retain stale per-agent selection. Fixes #39392; carries forward #39401, notes the duplicate #39495 approach, and keeps #46275/#54724 broader stabilization out of scope. Thanks @daijunyi002, @SergioChan, @aworki, and @wsyjh8.</li>
<li>Auto-reply: poison inbound message dedupe after replay-unsafe provider/runtime failures so retries stay safe before visible progress but cannot duplicate messages after block output, tool side effects, or session progress. Fixes #69303; keeps #58549 and #64606 as duplicate validation. Thanks @martingarramon, @NikolaFC, and @zeroth-blip.</li>
<li>Agents/model fallback: jump directly to a known later live-session model redirect instead of walking unrelated fallback candidates, while preserving the already-landed live-session/fallback loop guard. Fixes #57471; related loop family already closed via #58496. Thanks @yuxiaoyang2007-prog.</li>
<li>Gateway/Bonjour: keep @homebridge/ciao cancellation handlers registered across advertiser restarts so late probing cancellations cannot crash Linux and other mDNS-churned gateways. Thanks @vincentkoc.</li>
<li>Plugins/startup: load the default <code>memory-core</code> slot during Gateway startup when permitted so active-memory recall can call <code>memory_search</code> and <code>memory_get</code> without requiring an explicit <code>plugins.slots.memory</code> entry, while preserving <code>plugins.slots.memory: "none"</code>. Thanks @vincentkoc.</li>
<li>Gateway/plugins: resolve <code>gateway_start</code> cron hooks from live Gateway runtime state before the legacy deps fallback, so memory-core dreaming cron reconciliation keeps working on installs where <code>deps.cron</code> is not populated during service startup. Fixes #72835. Thanks @RayWoo.</li>
<li>Plugins/CLI: prefer native require for compiled bundled plugin JavaScript before jiti so read-only config, status, device, and node commands avoid unnecessary transform overhead on slow hosts. Fixes #62842. Thanks @Effet.</li>
<li>Plugins/compat: inventory doctor-side deprecation migrations separately from runtime plugin compatibility so release sweeps preserve needed repairs while enforcing dated removal windows. Thanks @vincentkoc.</li>
<li>Plugins/compat: add missing dated compatibility records for legacy extension-api, memory registration, provider hook/type aliases, runtime aliases, channel SDK helpers, and approval/test utility shims. Thanks @vincentkoc.</li>
<li>Plugins/CLI: refresh the persisted registry after managed plugin files are removed so ClawHub uninstall cannot leave stale <code>plugins list</code> entries. Thanks @vincentkoc.</li>
<li>Plugins/CLI: make plugin install and uninstall config writes conflict-aware, clear stale denylist entries on explicit reinstall/removal, and delete managed plugin files only after config/index commit succeeds. Thanks @vincentkoc.</li>
<li>Plugins: fail <code>plugins update</code> when tracked plugin or hook updates error, keep bundled runtime-dependency repair behind restrictive allowlists, and reject package installs with unloadable extension entries. Thanks @vincentkoc.</li>
<li>WebChat/Control UI: support non-video file attachments in chat uploads while preserving the existing image attachment path and MIME-sniff fallback for generic image uploads. (#70947) Thanks @IAMSamuelRodda.</li>
<li>Skills/memory: restore Chokidar v5 hot reloads by watching concrete skill and memory roots with filters, including SKILL.md removals and deleted skill folders without broad workspace recursion. Fixes #27404, #33585, and #41606. Thanks @shelvenzhou, @08820048, and @rocke2020.</li>
<li>Gateway/chat: keep duplicate attachment-backed <code>chat.send</code> retries with the same idempotency key on the documented in-flight path so aborts still target the real active run. Fixes #70139. Thanks @Feelw00.</li>
<li>Gateway/chat: preserve repeated boundary characters while merging assistant chat stream deltas, including repeated digits, CJK characters, and markdown/table tokens. Fixes #63769; carries forward #63994 and #65457. Thanks @yon950905 and @mohuaxiao.</li>
<li>Plugins: share package entrypoint resolution between install and discovery, reject mismatched <code>runtimeExtensions</code>, and cache bundled runtime-dependency manifest reads during scans. Thanks @vincentkoc.</li>
<li>WhatsApp/Web: keep quiet but healthy linked-device sessions connected by basing the watchdog on WhatsApp Web transport activity, while retaining a longer app-silence cap so frame activity cannot mask a stuck session forever. Fixes #70678; carries forward the focused #71466 approach and keeps #63939 as related configurable-timeout follow-up. Thanks @vincentkoc and @oromeis.</li>
<li>Discord/gateway: count failed health-monitor restart attempts toward cooldown and hourly caps, and evict stale account lifecycle state during channel reloads so repeated Discord gateway recovery cannot loop on old status. Fixes #38596. (#40413) Thanks @jellyAI-dev and @vashquez.</li>
<li>TTS/BlueBubbles: pre-transcode synthesized MP3 audio to opus-in-CAF (mono, 24 kHz — validated against macOS 15.x Messages.app's native voice-memo CAF descriptor) on macOS hosts before handing the file to BlueBubbles, so iMessage renders the result as a native voice-memo bubble with proper duration and waveform UI instead of a plain file attachment. Adds an opt-in <code>tts.voice.preferAudioFileFormat</code> channel capability and a magic-byte sniff for the CAF container so the host-local-media validator (which uses <code>file-type</code> and didn't recognize CAF natively) can verify the pre-transcoded buffer. Channels that don't opt in are unaffected. (#72586) Fixes #72506. Thanks @omarshahine.</li>
<li>Feishu: retry WebSocket startup failures with monitor-owned backoff while preserving SDK-local heartbeat defaults, so persistent-connection startup failures no longer leave the monitor hung. Fixes #68766; related #42354 and #55532. Thanks @alex-xuweilong, @120106835, @sirfengyu, and @tianhaocui.</li>
</ul>
<p><a href="https://github.com/openclaw/openclaw/blob/main/CHANGELOG.md">View full changelog</a></p>
]]></description>
<enclosure url="https://github.com/openclaw/openclaw/releases/download/v2026.4.27/OpenClaw-2026.4.27.zip" length="50595360" type="application/octet-stream" sparkle:edSignature="X8DQNQNWVcvtpYLkhZcsKNpnA78ycyzgGlZaG0XBY1GIph3oZNUIpAszGGocJVqTK7+F89Au5ZPb60mOqJQ6DQ=="/>
</item>
</channel>
</rss>

View File

@@ -8,8 +8,6 @@ import SwiftUI
@MainActor
@Observable
final class AppState {
private static let logger = Logger(subsystem: "ai.openclaw", category: "app-state")
private let isPreview: Bool
private var isInitializing = true
private var isApplyingRemoteTokenConfig = false
@@ -698,10 +696,7 @@ final class AppState {
remoteToken: self.remoteToken,
remoteTokenDirty: self.remoteTokenDirty))
guard synced.changed else { return }
guard OpenClawConfigFile.saveDict(synced.root) else {
Self.logger.warning("gateway config sync rejected to protect persisted gateway auth/mode")
return
}
OpenClawConfigFile.saveDict(synced.root)
}
func triggerVoiceEars(ttl: TimeInterval? = 5) {

View File

@@ -8,7 +8,6 @@ enum ConfigStore {
var saveLocal: (@MainActor @Sendable ([String: Any]) -> Void)?
var loadRemote: (@MainActor @Sendable () async -> [String: Any])?
var saveRemote: (@MainActor @Sendable ([String: Any]) async throws -> Void)?
var saveGateway: (@MainActor @Sendable ([String: Any]) async throws -> Void)?
}
private actor OverrideStore {
@@ -67,19 +66,10 @@ enum ConfigStore {
do {
try await self.saveToGateway(root)
} catch {
guard self.shouldFallbackToLocalWrite(afterGatewaySaveError: error) else {
self.lastHash = nil
throw error
}
guard OpenClawConfigFile.saveDict(
OpenClawConfigFile.saveDict(
root,
preserveExistingKeys: true,
allowGatewayAuthMutation: allowGatewayAuthMutation)
else {
throw NSError(domain: "ConfigStore", code: 2, userInfo: [
NSLocalizedDescriptionKey: "Local config write rejected to protect gateway auth/mode.",
])
}
}
}
}
@@ -99,30 +89,8 @@ enum ConfigStore {
}
}
private static func shouldFallbackToLocalWrite(afterGatewaySaveError error: Error) -> Bool {
let nsError = error as NSError
let message = "\(nsError.domain) \(nsError.localizedDescription)".lowercased()
let blockedFragments = [
"invalid_request",
"invalid request",
"invalid config",
"config changed since last load",
"base hash",
"basehash",
"unauthorized",
"token mismatch",
"auth",
]
return !blockedFragments.contains { message.contains($0) }
}
@MainActor
private static func saveToGateway(_ root: [String: Any]) async throws {
let overrides = await self.overrideStore.overrides
if let saveGateway = overrides.saveGateway {
try await saveGateway(root)
return
}
if self.lastHash == nil {
_ = await self.loadFromGateway()
}

View File

@@ -779,10 +779,7 @@ struct DebugSettings: View {
session["store"] = trimmed.isEmpty ? SessionLoader.defaultStorePath : trimmed
root["session"] = session
guard OpenClawConfigFile.saveDict(root) else {
self.sessionStoreSaveError = "Config write rejected to protect gateway auth/mode."
return
}
OpenClawConfigFile.saveDict(root)
self.sessionStoreSaveError = nil
}

View File

@@ -43,8 +43,7 @@ enum ExecApprovalEvaluator {
let allowAlwaysPatterns = ExecCommandResolution.resolveAllowAlwaysPatterns(
command: command,
cwd: cwd,
env: env,
rawCommand: allowlistRawCommand)
env: env)
let allowlistMatches = security == .allowlist
? ExecAllowlistMatcher.matchAll(entries: approvals.allowlist, resolutions: allowlistResolutions)
: []

View File

@@ -27,7 +27,7 @@ struct ExecCommandResolution {
{
// Allowlist resolution must follow actual argv execution for wrappers.
// `rawCommand` is caller-supplied display text and may be canonicalized.
let shell = ExecShellWrapperParser.extractForAllowlist(command: command, rawCommand: rawCommand)
let shell = ExecShellWrapperParser.extract(command: command, rawCommand: nil)
if shell.isWrapper {
// Fail closed when env modifiers precede a shell wrapper. This mirrors
// system-run binding behavior where such invocations must stay bound to
@@ -68,8 +68,7 @@ struct ExecCommandResolution {
static func resolveAllowAlwaysPatterns(
command: [String],
cwd: String?,
env: [String: String]?,
rawCommand: String? = nil) -> [String]
env: [String: String]?) -> [String]
{
var patterns: [String] = []
var seen = Set<String>()
@@ -77,7 +76,6 @@ struct ExecCommandResolution {
command: command,
cwd: cwd,
env: env,
rawCommand: rawCommand,
depth: 0,
patterns: &patterns,
seen: &seen)
@@ -154,7 +152,6 @@ struct ExecCommandResolution {
command: [String],
cwd: String?,
env: [String: String]?,
rawCommand: String?,
depth: Int,
patterns: inout [String],
seen: inout Set<String>)
@@ -165,19 +162,13 @@ struct ExecCommandResolution {
if let token0 = command.first?.trimmingCharacters(in: .whitespacesAndNewlines),
ExecCommandToken.basenameLower(token0) == "env",
let envUnwrapped = ExecEnvInvocationUnwrapper.unwrapWithMetadata(command),
!envUnwrapped.command.isEmpty
let envUnwrapped = ExecEnvInvocationUnwrapper.unwrap(command),
!envUnwrapped.isEmpty
{
if envUnwrapped.usesModifiers,
self.isAllowlistShellWrapper(command: envUnwrapped.command, rawCommand: rawCommand)
{
return
}
self.collectAllowAlwaysPatterns(
command: envUnwrapped.command,
command: envUnwrapped,
cwd: cwd,
env: env,
rawCommand: rawCommand,
depth: depth + 1,
patterns: &patterns,
seen: &seen)
@@ -189,14 +180,13 @@ struct ExecCommandResolution {
command: shellMultiplexer,
cwd: cwd,
env: env,
rawCommand: rawCommand,
depth: depth + 1,
patterns: &patterns,
seen: &seen)
return
}
let shell = ExecShellWrapperParser.extractForAllowlist(command: command, rawCommand: rawCommand)
let shell = ExecShellWrapperParser.extract(command: command, rawCommand: nil)
if shell.isWrapper {
guard let shellCommand = shell.command,
let segments = self.splitShellCommandChain(shellCommand)
@@ -212,7 +202,6 @@ struct ExecCommandResolution {
command: tokens,
cwd: cwd,
env: env,
rawCommand: nil,
depth: depth + 1,
patterns: &patterns,
seen: &seen)
@@ -229,10 +218,6 @@ struct ExecCommandResolution {
patterns.append(pattern)
}
private static func isAllowlistShellWrapper(command: [String], rawCommand: String?) -> Bool {
ExecShellWrapperParser.extractForAllowlist(command: command, rawCommand: rawCommand).isWrapper
}
private static func unwrapShellMultiplexerInvocation(_ argv: [String]) -> [String]? {
guard let token0 = argv.first?.trimmingCharacters(in: .whitespacesAndNewlines), !token0.isEmpty else {
return nil

View File

@@ -1,278 +0,0 @@
import Foundation
enum ExecInlineCommandParser {
struct Match {
let tokenIndex: Int
let inlineCommand: String?
let valueTokenOffset: Int
init(tokenIndex: Int, inlineCommand: String?, valueTokenOffset: Int = 1) {
self.tokenIndex = tokenIndex
self.inlineCommand = inlineCommand
self.valueTokenOffset = valueTokenOffset
}
}
private struct CombinedCommandFlag {
let attachedCommand: String?
let separateValueCount: Int
}
private static let posixShellOptionsWithSeparateValues = Set([
"--init-file",
"--rcfile",
"-O",
"-o",
"+O",
"+o",
])
static func hasPosixInteractiveStartupBeforeInlineCommand(
_ argv: [String],
flags: Set<String>) -> Bool
{
var idx = 1
var sawInteractiveMode = false
while idx < argv.count {
let token = argv[idx].trimmingCharacters(in: .whitespacesAndNewlines)
if token.isEmpty {
idx += 1
continue
}
if token == "--" {
return false
}
if self.isPosixInteractiveModeOption(token) {
sawInteractiveMode = true
}
if flags.contains(token) || self.isCombinedCommandFlag(token) {
return sawInteractiveMode
}
if !token.hasPrefix("-"), !token.hasPrefix("+") {
return false
}
let combinedValueCount = self.combinedSeparateValueOptionCount(token)
if combinedValueCount > 0 {
idx += 1 + combinedValueCount
continue
}
if self.consumesSeparateValue(token) {
idx += 2
continue
}
idx += 1
}
return false
}
static func hasPosixLoginStartupBeforeInlineCommand(
_ argv: [String],
flags: Set<String>) -> Bool
{
var idx = 1
var sawLoginMode = false
while idx < argv.count {
let token = argv[idx].trimmingCharacters(in: .whitespacesAndNewlines)
if token.isEmpty {
idx += 1
continue
}
if token == "--" {
return false
}
if token == "--login" || self.isPosixShortOption(token, containing: "l") {
sawLoginMode = true
}
if flags.contains(token) || self.isCombinedCommandFlag(token) {
return sawLoginMode
}
if !token.hasPrefix("-"), !token.hasPrefix("+") {
return false
}
let combinedValueCount = self.combinedSeparateValueOptionCount(token)
if combinedValueCount > 0 {
idx += 1 + combinedValueCount
continue
}
if self.consumesSeparateValue(token) {
idx += 2
continue
}
idx += 1
}
return false
}
static func hasFishInitCommandOption(_ argv: [String]) -> Bool {
var idx = 1
while idx < argv.count {
let token = argv[idx].trimmingCharacters(in: .whitespacesAndNewlines)
if token.isEmpty {
idx += 1
continue
}
if token == "--" {
return false
}
if token == "-C" || token == "--init-command" {
return true
}
if token.hasPrefix("-C"), token != "-C" {
return true
}
if token.hasPrefix("--init-command=") {
return true
}
if !token.hasPrefix("-"), !token.hasPrefix("+") {
return false
}
idx += 1
}
return false
}
static func hasFishAttachedCommandOption(_ argv: [String]) -> Bool {
var idx = 1
while idx < argv.count {
let token = argv[idx].trimmingCharacters(in: .whitespacesAndNewlines)
if token.isEmpty {
idx += 1
continue
}
if token == "--" {
return false
}
if token.hasPrefix("-c"), token != "-c" {
return true
}
if !token.hasPrefix("-"), !token.hasPrefix("+") {
return false
}
idx += 1
}
return false
}
static func findMatch(
_ argv: [String],
flags: Set<String>,
allowCombinedC: Bool) -> Match?
{
var idx = 1
while idx < argv.count {
let token = argv[idx].trimmingCharacters(in: .whitespacesAndNewlines)
if token.isEmpty {
idx += 1
continue
}
if token == "--" {
break
}
let comparableToken = allowCombinedC ? token : token.lowercased()
if flags.contains(comparableToken) {
return Match(tokenIndex: idx, inlineCommand: nil)
}
if allowCombinedC, let combined = self.parseCombinedCommandFlag(token) {
if let attachedCommand = combined.attachedCommand {
return Match(tokenIndex: idx, inlineCommand: attachedCommand, valueTokenOffset: 0)
}
return Match(
tokenIndex: idx,
inlineCommand: nil,
valueTokenOffset: 1 + combined.separateValueCount)
}
if allowCombinedC, !token.hasPrefix("-"), !token.hasPrefix("+") {
break
}
let combinedValueCount = allowCombinedC ? self.combinedSeparateValueOptionCount(token) : 0
if combinedValueCount > 0 {
idx += 1 + combinedValueCount
continue
}
if allowCombinedC, self.consumesSeparateValue(token) {
idx += 2
continue
}
idx += 1
}
return nil
}
static func extractInlineCommand(
_ argv: [String],
flags: Set<String>,
allowCombinedC: Bool) -> String?
{
guard let match = self.findMatch(argv, flags: flags, allowCombinedC: allowCombinedC) else {
return nil
}
if let inlineCommand = match.inlineCommand {
return inlineCommand
}
let nextIndex = match.tokenIndex + match.valueTokenOffset
let payload = nextIndex < argv.count
? argv[nextIndex].trimmingCharacters(in: .whitespacesAndNewlines)
: ""
return payload.isEmpty ? nil : payload
}
private static func isCombinedCommandFlag(_ token: String) -> Bool {
self.parseCombinedCommandFlag(token) != nil
}
private static func parseCombinedCommandFlag(_ token: String) -> CombinedCommandFlag? {
let chars = Array(token)
guard chars.count >= 2, chars[0] == "-", chars[1] != "-" else {
return nil
}
let optionChars = Array(chars.dropFirst())
guard let commandFlagIndex = optionChars.firstIndex(of: "c") else {
return nil
}
if optionChars.contains("-") {
return nil
}
let suffix = String(optionChars.dropFirst(commandFlagIndex + 1))
if !suffix.isEmpty,
suffix.range(of: #"[^A-Za-z]"#, options: .regularExpression) != nil
{
return CombinedCommandFlag(attachedCommand: suffix, separateValueCount: 0)
}
let separateValueCount = optionChars.reduce(0) { count, char in
count + ((char == "o" || char == "O") ? 1 : 0)
}
return CombinedCommandFlag(attachedCommand: nil, separateValueCount: separateValueCount)
}
private static func combinedSeparateValueOptionCount(_ token: String) -> Int {
let chars = Array(token)
guard chars.count >= 2, chars[0] == "-" || chars[0] == "+", chars[1] != "-" else {
return 0
}
if chars.dropFirst().contains("-") {
return 0
}
return chars.dropFirst().reduce(0) { count, char in
count + ((char == "o" || char == "O") ? 1 : 0)
}
}
private static func consumesSeparateValue(_ token: String) -> Bool {
self.posixShellOptionsWithSeparateValues.contains(token)
}
private static func isPosixInteractiveModeOption(_ token: String) -> Bool {
token == "--interactive" || self.isPosixShortOption(token, containing: "i")
}
private static func isPosixShortOption(_ token: String, containing option: Character) -> Bool {
let chars = Array(token)
guard chars.count >= 2, chars[0] == "-", chars[1] != "-" else {
return false
}
if chars.dropFirst().contains("-") {
return false
}
return chars.dropFirst().contains(option)
}
}

View File

@@ -6,10 +6,9 @@ enum ExecShellWrapperParser {
let command: String?
static let notWrapper = ParsedShellWrapper(isWrapper: false, command: nil)
static let blockedWrapper = ParsedShellWrapper(isWrapper: true, command: nil)
}
private enum Kind: Equatable {
private enum Kind {
case posix
case cmd
case powershell
@@ -28,34 +27,14 @@ enum ExecShellWrapperParser {
WrapperSpec(kind: .cmd, names: ["cmd.exe", "cmd"]),
WrapperSpec(kind: .powershell, names: ["powershell", "powershell.exe", "pwsh", "pwsh.exe"]),
]
private static let loginStartupShellNames = Set(["ash", "bash", "dash", "fish", "ksh", "sh", "zsh"])
static func extract(command: [String], rawCommand: String?) -> ParsedShellWrapper {
let trimmedRaw = rawCommand?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
let preferredRaw = trimmedRaw.isEmpty ? nil : trimmedRaw
return self.extract(
command: command,
preferredRaw: preferredRaw,
failClosedOnStartupWrappers: false,
depth: 0)
return self.extract(command: command, preferredRaw: preferredRaw, depth: 0)
}
static func extractForAllowlist(command: [String], rawCommand: String?) -> ParsedShellWrapper {
let trimmedRaw = rawCommand?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
let preferredRaw = trimmedRaw.isEmpty ? nil : trimmedRaw
return self.extract(
command: command,
preferredRaw: preferredRaw,
failClosedOnStartupWrappers: true,
depth: 0)
}
private static func extract(
command: [String],
preferredRaw: String?,
failClosedOnStartupWrappers: Bool,
depth: Int) -> ParsedShellWrapper
{
private static func extract(command: [String], preferredRaw: String?, depth: Int) -> ParsedShellWrapper {
guard depth < ExecEnvInvocationUnwrapper.maxWrapperDepth else {
return .notWrapper
}
@@ -68,96 +47,19 @@ enum ExecShellWrapperParser {
guard let unwrapped = ExecEnvInvocationUnwrapper.unwrap(command) else {
return .notWrapper
}
return self.extract(
command: unwrapped,
preferredRaw: preferredRaw,
failClosedOnStartupWrappers: failClosedOnStartupWrappers,
depth: depth + 1)
return self.extract(command: unwrapped, preferredRaw: preferredRaw, depth: depth + 1)
}
guard let spec = self.wrapperSpecs.first(where: { $0.names.contains(base0) }) else {
return .notWrapper
}
if spec.kind == .posix,
base0 == "fish",
ExecInlineCommandParser.hasFishAttachedCommandOption(command)
{
return .blockedWrapper
}
let includeLegacyLoginInlineForm = failClosedOnStartupWrappers &&
!self.legacyLoginInlinePayloadMatchesRaw(
command: command,
spec: spec,
base0: base0,
preferredRaw: preferredRaw)
if self.startupWrapperRequiresFullArgv(
command: command,
spec: spec,
base0: base0,
includeLegacyLoginInlineForm: includeLegacyLoginInlineForm)
{
return .blockedWrapper
}
guard let payload = self.extractPayload(command: command, spec: spec) else {
return .notWrapper
}
let normalized = failClosedOnStartupWrappers ? payload : preferredRaw ?? payload
let normalized = preferredRaw ?? payload
return ParsedShellWrapper(isWrapper: true, command: normalized)
}
private static func startupWrapperRequiresFullArgv(
command: [String],
spec: WrapperSpec,
base0: String,
includeLegacyLoginInlineForm: Bool) -> Bool
{
guard spec.kind == .posix else {
return false
}
if base0 == "fish",
ExecInlineCommandParser.hasFishInitCommandOption(command)
{
return true
}
if self.loginStartupShellNames.contains(base0),
ExecInlineCommandParser.hasPosixLoginStartupBeforeInlineCommand(
command,
flags: self.posixInlineFlags)
{
return includeLegacyLoginInlineForm || !self.isLegacyShLoginInlineForm(command, base0: base0)
}
return ExecInlineCommandParser.hasPosixInteractiveStartupBeforeInlineCommand(
command,
flags: self.posixInlineFlags)
}
private static func isLegacyLoginInlineForm(_ command: [String]) -> Bool {
guard command.count > 1 else {
return false
}
return command[1].trimmingCharacters(in: .whitespacesAndNewlines) == "-lc"
}
private static func isLegacyShLoginInlineForm(_ command: [String], base0: String) -> Bool {
base0 == "sh" && self.isLegacyLoginInlineForm(command)
}
private static func legacyLoginInlinePayloadMatchesRaw(
command: [String],
spec: WrapperSpec,
base0: String,
preferredRaw: String?) -> Bool
{
guard let preferredRaw,
base0 == "sh",
self.isLegacyLoginInlineForm(command),
let payload = self.extractPayload(command: command, spec: spec)
else {
return false
}
return payload == preferredRaw.trimmingCharacters(in: .whitespacesAndNewlines)
}
private static func extractPayload(command: [String], spec: WrapperSpec) -> String? {
switch spec.kind {
case .posix:
@@ -170,10 +72,12 @@ enum ExecShellWrapperParser {
}
private static func extractPosixInlineCommand(_ command: [String]) -> String? {
ExecInlineCommandParser.extractInlineCommand(
command,
flags: self.posixInlineFlags,
allowCombinedC: true)
let flag = command.count > 1 ? command[1].trimmingCharacters(in: .whitespacesAndNewlines) : ""
guard self.posixInlineFlags.contains(flag.lowercased()) else {
return nil
}
let payload = command.count > 2 ? command[2].trimmingCharacters(in: .whitespacesAndNewlines) : ""
return payload.isEmpty ? nil : payload
}
private static func extractCmdInlineCommand(_ command: [String]) -> String? {
@@ -193,10 +97,10 @@ enum ExecShellWrapperParser {
if token.isEmpty { continue }
if token == "--" { break }
if self.powershellInlineFlags.contains(token) {
return ExecInlineCommandParser.extractInlineCommand(
command,
flags: self.powershellInlineFlags,
allowCombinedC: false)
let payload = idx + 1 < command.count
? command[idx + 1].trimmingCharacters(in: .whitespacesAndNewlines)
: ""
return payload.isEmpty ? nil : payload
}
}
return nil

View File

@@ -326,12 +326,40 @@ enum ExecSystemRunCommandValidator {
return current
}
private struct InlineCommandTokenMatch {
var tokenIndex: Int
var inlineCommand: String?
}
private static func findInlineCommandTokenMatch(
_ argv: [String],
flags: Set<String>,
allowCombinedC: Bool) -> ExecInlineCommandParser.Match?
allowCombinedC: Bool) -> InlineCommandTokenMatch?
{
ExecInlineCommandParser.findMatch(argv, flags: flags, allowCombinedC: allowCombinedC)
var idx = 1
while idx < argv.count {
let token = argv[idx].trimmingCharacters(in: .whitespacesAndNewlines)
if token.isEmpty {
idx += 1
continue
}
let lower = token.lowercased()
if lower == "--" {
break
}
if flags.contains(lower) {
return InlineCommandTokenMatch(tokenIndex: idx, inlineCommand: nil)
}
if allowCombinedC, let inlineOffset = self.combinedCommandInlineOffset(token) {
let inline = String(token.dropFirst(inlineOffset))
.trimmingCharacters(in: .whitespacesAndNewlines)
return InlineCommandTokenMatch(
tokenIndex: idx,
inlineCommand: inline.isEmpty ? nil : inline)
}
idx += 1
}
return nil
}
private static func resolveInlineCommandTokenIndex(
@@ -345,10 +373,24 @@ enum ExecSystemRunCommandValidator {
if match.inlineCommand != nil {
return match.tokenIndex
}
let nextIndex = match.tokenIndex + match.valueTokenOffset
let nextIndex = match.tokenIndex + 1
return nextIndex < argv.count ? nextIndex : nil
}
private static func combinedCommandInlineOffset(_ token: String) -> Int? {
let chars = Array(token.lowercased())
guard chars.count >= 2, chars[0] == "-", chars[1] != "-" else {
return nil
}
if chars.dropFirst().contains("-") {
return nil
}
guard let commandIndex = chars.firstIndex(of: "c"), commandIndex > 0 else {
return nil
}
return commandIndex + 1
}
private static func extractShellInlinePayload(
_ argv: [String],
normalizedWrapper: String) -> String?
@@ -379,7 +421,7 @@ enum ExecSystemRunCommandValidator {
if let inlineCommand = match.inlineCommand {
return inlineCommand
}
let nextIndex = match.tokenIndex + match.valueTokenOffset
let nextIndex = match.tokenIndex + 1
return self.trimmedNonEmpty(nextIndex < argv.count ? argv[nextIndex] : nil)
}

View File

@@ -52,16 +52,14 @@ enum OpenClawConfigFile {
}
}
@discardableResult
static func saveDict(
_ dict: [String: Any],
preserveExistingKeys: Bool = false,
allowGatewayAuthMutation: Bool = false)
-> Bool
{
self.withFileLock {
// Nix mode disables config writes in production, but tests rely on saving temp configs.
if ProcessInfo.processInfo.isNixMode, !ProcessInfo.processInfo.isRunningTests { return false }
if ProcessInfo.processInfo.isNixMode, !ProcessInfo.processInfo.isRunningTests { return }
let url = self.url()
let previousData = try? Data(contentsOf: url)
let previousRoot = previousData.flatMap { self.parseConfigData($0) }
@@ -83,7 +81,12 @@ enum OpenClawConfigFile {
do {
let data = try JSONSerialization.data(withJSONObject: output, options: [.prettyPrinted, .sortedKeys])
try FileManager().createDirectory(
at: url.deletingLastPathComponent(),
withIntermediateDirectories: true)
try data.write(to: url, options: [.atomic])
let nextBytes = data.count
let nextAttributes = try? FileManager().attributesOfItem(atPath: url.path)
let gatewayModeAfter = self.gatewayMode(output)
var suspicious = self.configWriteSuspiciousReasons(
existsBefore: previousData != nil,
@@ -95,44 +98,6 @@ enum OpenClawConfigFile {
if preservedGatewayAuth {
suspicious.append("gateway-auth-preserved")
}
let blocking = self.configWriteBlockingReasons(suspicious)
if !blocking.isEmpty {
let rejectedPath = self.persistRejectedConfigWrite(data: data, configURL: url)
self.logger.warning("config write rejected (\(blocking.joined(separator: ", "))) at \(url.path)")
self.appendConfigWriteAudit([
"result": "rejected",
"configPath": url.path,
"existsBefore": previousData != nil,
"previousBytes": previousBytes ?? NSNull(),
"nextBytes": nextBytes,
"previousDev": self.fileSystemNumber(previousAttributes?[.systemNumber]) ?? NSNull(),
"nextDev": NSNull(),
"previousIno": self.fileSystemNumber(previousAttributes?[.systemFileNumber]) ?? NSNull(),
"nextIno": NSNull(),
"previousMode": self.posixMode(previousAttributes?[.posixPermissions]) ?? NSNull(),
"nextMode": NSNull(),
"previousNlink": self.fileAttributeInt(previousAttributes?[.referenceCount]) ?? NSNull(),
"nextNlink": NSNull(),
"previousUid": self.fileAttributeInt(previousAttributes?[.ownerAccountID]) ?? NSNull(),
"nextUid": NSNull(),
"previousGid": self.fileAttributeInt(previousAttributes?[.groupOwnerAccountID]) ?? NSNull(),
"nextGid": NSNull(),
"hasMetaBefore": hadMetaBefore,
"hasMetaAfter": self.hasMeta(output),
"gatewayModeBefore": gatewayModeBefore ?? NSNull(),
"gatewayModeAfter": gatewayModeAfter ?? NSNull(),
"preservedGatewayAuth": preservedGatewayAuth,
"suspicious": suspicious,
"blocking": blocking,
"rejectedPath": rejectedPath ?? NSNull(),
])
return false
}
try FileManager().createDirectory(
at: url.deletingLastPathComponent(),
withIntermediateDirectories: true)
try data.write(to: url, options: [.atomic])
let nextAttributes = try? FileManager().attributesOfItem(atPath: url.path)
if !suspicious.isEmpty {
self.logger.warning("config write anomaly (\(suspicious.joined(separator: ", "))) at \(url.path)")
}
@@ -158,11 +123,9 @@ enum OpenClawConfigFile {
"hasMetaAfter": self.hasMeta(output),
"gatewayModeBefore": gatewayModeBefore ?? NSNull(),
"gatewayModeAfter": gatewayModeAfter ?? NSNull(),
"preservedGatewayAuth": preservedGatewayAuth,
"suspicious": suspicious,
])
self.observeConfigRead(data: data, root: output, configURL: url, valid: true)
return true
} catch {
self.logger.error("config save failed: \(error.localizedDescription)")
self.appendConfigWriteAudit([
@@ -175,11 +138,9 @@ enum OpenClawConfigFile {
"hasMetaAfter": self.hasMeta(output),
"gatewayModeBefore": gatewayModeBefore ?? NSNull(),
"gatewayModeAfter": self.gatewayMode(output) ?? NSNull(),
"preservedGatewayAuth": preservedGatewayAuth,
"suspicious": preservedGatewayAuth ? ["gateway-auth-preserved"] : [],
"error": error.localizedDescription,
])
return false
}
}
}
@@ -455,12 +416,6 @@ enum OpenClawConfigFile {
return reasons
}
private static func configWriteBlockingReasons(_ suspicious: [String]) -> [String] {
suspicious.filter { reason in
reason.hasPrefix("size-drop:") || reason == "gateway-mode-removed"
}
}
private static func configAuditLogURL() -> URL {
self.stateDirURL()
.appendingPathComponent("logs", isDirectory: true)
@@ -639,26 +594,6 @@ enum OpenClawConfigFile {
}
}
private static func persistRejectedConfigWrite(data: Data, configURL: URL) -> String? {
let timestamp = ISO8601DateFormatter().string(from: Date())
let url = configURL.deletingLastPathComponent()
.appendingPathComponent("\(configURL.lastPathComponent).rejected.\(self.configTimestampToken(timestamp))")
let fileManager = FileManager()
let privatePermissions: NSNumber = 0o600
if fileManager.fileExists(atPath: url.path) {
try? fileManager.setAttributes([.posixPermissions: privatePermissions], ofItemAtPath: url.path)
return url.path
}
guard fileManager.createFile(
atPath: url.path,
contents: data,
attributes: [.posixPermissions: privatePermissions])
else {
return nil
}
return url.path
}
private static func observeConfigRead(data: Data, root: [String: Any]?, configURL: URL, valid: Bool) {
let observedAt = ISO8601DateFormatter().string(from: Date())
let current = self.configFingerprint(data: data, root: root, configURL: configURL, observedAt: observedAt)

View File

@@ -259,37 +259,4 @@ struct AppStateRemoteConfigTests {
remoteTokenDirty: true))
#expect((cleared["token"] as? String) == nil)
}
@Test
func `synced gateway root preserves gateway auth across mode changes`() {
let initialRoot: [String: Any] = [
"gateway": [
"mode": "remote",
"auth": [
"mode": "token",
"token": "test-token", // pragma: allowlist secret
],
"remote": [
"transport": "direct",
"url": "wss://old-gateway.example",
],
],
]
let localRoot = AppState._testSyncedGatewayRoot(
currentRoot: initialRoot,
draft: .init(
connectionMode: .local,
remoteTransport: .ssh,
remoteTarget: "",
remoteIdentity: "",
remoteUrl: "",
remoteToken: "",
remoteTokenDirty: false))
let localGateway = localRoot["gateway"] as? [String: Any]
let auth = localGateway?["auth"] as? [String: Any]
#expect(localGateway?["mode"] as? String == "local")
#expect(auth?["mode"] as? String == "token")
#expect(auth?["token"] as? String == "test-token") // pragma: allowlist secret
}
}

View File

@@ -1,4 +1,3 @@
import Foundation
import Testing
@testable import OpenClaw
@@ -66,76 +65,4 @@ struct ConfigStoreTests {
#expect(localHit)
#expect(!remoteHit)
}
@Test func `local save does not fall back to direct write after stale gateway rejection`() async throws {
let stateDir = FileManager().temporaryDirectory
.appendingPathComponent("openclaw-state-\(UUID().uuidString)", isDirectory: true)
let configPath = stateDir.appendingPathComponent("openclaw.json")
defer { try? FileManager().removeItem(at: stateDir) }
try await TestIsolation.withEnvValues([
"OPENCLAW_STATE_DIR": stateDir.path,
"OPENCLAW_CONFIG_PATH": configPath.path,
]) {
OpenClawConfigFile.saveDict([
"gateway": [
"mode": "local",
"auth": [
"mode": "token",
"token": "test-token", // pragma: allowlist secret
],
],
])
let before = try String(contentsOf: configPath, encoding: .utf8)
await ConfigStore._testSetOverrides(.init(
isRemoteMode: { false },
saveGateway: { _ in
throw NSError(domain: "Gateway", code: 0, userInfo: [
NSLocalizedDescriptionKey: "config changed since last load; re-run config.get and retry",
])
}))
var didThrow = false
do {
try await ConfigStore.save(["browser": ["enabled": false]])
} catch {
didThrow = true
}
await ConfigStore._testClearOverrides()
#expect(didThrow)
let after = try String(contentsOf: configPath, encoding: .utf8)
#expect(after == before)
}
}
@Test func `local save can fall back to protected direct write when gateway is unavailable`() async throws {
let stateDir = FileManager().temporaryDirectory
.appendingPathComponent("openclaw-state-\(UUID().uuidString)", isDirectory: true)
let configPath = stateDir.appendingPathComponent("openclaw.json")
defer { try? FileManager().removeItem(at: stateDir) }
try await TestIsolation.withEnvValues([
"OPENCLAW_STATE_DIR": stateDir.path,
"OPENCLAW_CONFIG_PATH": configPath.path,
]) {
await ConfigStore._testSetOverrides(.init(
isRemoteMode: { false },
saveGateway: { _ in
throw NSError(domain: "Gateway", code: 0, userInfo: [
NSLocalizedDescriptionKey: "gateway not configured",
])
}))
try await ConfigStore.save([
"gateway": ["mode": "local"],
"browser": ["enabled": false],
])
await ConfigStore._testClearOverrides()
let data = try Data(contentsOf: configPath)
let root = try JSONSerialization.jsonObject(with: data) as? [String: Any]
#expect(((root?["browser"] as? [String: Any])?["enabled"] as? Bool) == false)
#expect((root?["meta"] as? [String: Any]) != nil)
}
}
}

View File

@@ -111,7 +111,7 @@ struct ExecAllowlistTests {
}
@Test func `resolve for allowlist splits shell chains`() {
let command = ["/bin/sh", "-c", "echo allowlisted && /usr/bin/touch /tmp/openclaw-allowlist-test"]
let command = ["/bin/sh", "-lc", "echo allowlisted && /usr/bin/touch /tmp/openclaw-allowlist-test"]
let resolutions = ExecCommandResolution.resolveForAllowlist(
command: command,
rawCommand: "echo allowlisted && /usr/bin/touch /tmp/openclaw-allowlist-test",
@@ -122,109 +122,9 @@ struct ExecAllowlistTests {
#expect(resolutions[1].executableName == "touch")
}
@Test func `resolve for allowlist splits posix combined c flag payloads`() {
for command in [
["/bin/bash", "-xc", "/usr/bin/printf safe_marker"],
["/bin/bash", "-ec", "/usr/bin/printf safe_marker"],
["/bin/bash", "-euxc", "/usr/bin/printf safe_marker"],
["/bin/bash", "-cx", "/usr/bin/printf safe_marker"],
["/bin/bash", "-O", "extglob", "-xc", "/usr/bin/printf safe_marker"],
["/bin/bash", "-co", "vi", "/usr/bin/printf safe_marker"],
["/bin/bash", "-oc", "vi", "/usr/bin/printf safe_marker"],
["/bin/bash", "-cO", "extglob", "/usr/bin/printf safe_marker"],
["/bin/bash", "-xo", "vi", "-c", "/usr/bin/printf safe_marker"],
["/bin/bash", "-xO", "extglob", "-c", "/usr/bin/printf safe_marker"],
["/bin/bash", "+xo", "vi", "-c", "/usr/bin/printf safe_marker"],
["/bin/bash", "--rcfile", "/tmp/rc", "-c", "/usr/bin/printf safe_marker"],
["/bin/bash", "--init-file=/tmp/rc", "-c", "/usr/bin/printf safe_marker"],
] {
let resolutions = ExecCommandResolution.resolveForAllowlist(
command: command,
rawCommand: nil,
cwd: nil,
env: ["PATH": "/usr/bin:/bin"])
#expect(resolutions.count == 1)
#expect(resolutions[0].resolvedPath == "/usr/bin/printf")
#expect(resolutions[0].executableName == "printf")
}
}
@Test func `resolve for allowlist treats c after posix shell operand as direct exec`() {
for command in [
["/bin/bash", "./script.sh", "-c", "/usr/bin/printf safe_marker"],
["/bin/bash", "-x", "-C", "echo ok", "-c", "/usr/bin/printf safe_marker"],
] {
let resolutions = ExecCommandResolution.resolveForAllowlist(
command: command,
rawCommand: nil,
cwd: "/tmp",
env: ["PATH": "/usr/bin:/bin"])
#expect(resolutions.count == 1)
#expect(resolutions[0].resolvedPath == "/bin/bash")
#expect(resolutions[0].executableName == "bash")
}
}
@Test func `resolve for allowlist fails closed for interactive posix shell wrappers`() {
for command in [
["/bin/bash", "-i", "-c", "/usr/bin/printf safe_marker"],
["/bin/bash", "-ic", "/usr/bin/printf safe_marker"],
["/bin/bash", "--rcfile", "/tmp/payload.sh", "-i", "-c", "/usr/bin/printf safe_marker"],
["/usr/bin/fish", "--interactive", "-c", "/usr/bin/printf safe_marker"],
] {
let resolutions = ExecCommandResolution.resolveForAllowlist(
command: command,
rawCommand: nil,
cwd: nil,
env: ["PATH": "/usr/bin:/bin"])
#expect(resolutions.isEmpty)
}
}
@Test func `resolve for allowlist fails closed for login shell wrappers`() {
for command in [
["/bin/bash", "-l", "-c", "/usr/bin/printf safe_marker"],
["/bin/bash", "--login", "-c", "/usr/bin/printf safe_marker"],
["/bin/bash", "-xlc", "/usr/bin/printf safe_marker"],
["/bin/dash", "-lc", "/usr/bin/printf safe_marker"],
["ash", "-lc", "/usr/bin/printf safe_marker"],
["/usr/bin/fish", "-l", "-c", "/usr/bin/printf safe_marker"],
["/usr/bin/fish", "--login", "-c", "/usr/bin/printf safe_marker"],
["/bin/sh", "-lc", "/usr/bin/printf safe_marker"],
["/bin/sh", "-x", "-lc", "/usr/bin/printf safe_marker"],
["/usr/bin/env", "/bin/sh", "-lc", "/usr/bin/printf safe_marker"],
] {
let resolutions = ExecCommandResolution.resolveForAllowlist(
command: command,
rawCommand: nil,
cwd: nil,
env: ["PATH": "/usr/bin:/bin"])
#expect(resolutions.isEmpty)
}
}
@Test func `resolve for allowlist fails closed for fish init command wrappers`() {
for command in [
["/usr/bin/fish", "--init-command=/tmp/payload.fish", "-c", "/usr/bin/printf safe_marker"],
["/usr/bin/fish", "--init-command", "/tmp/payload.fish", "-c", "/usr/bin/printf safe_marker"],
["/usr/bin/fish", "-C", "/tmp/payload.fish", "-c", "/usr/bin/printf safe_marker"],
["/usr/bin/fish", "-C/tmp/payload.fish", "-c", "/usr/bin/printf safe_marker"],
["/usr/bin/fish", "--init-command", "-c; /tmp/payload.fish", "/usr/bin/printf safe_marker"],
["/usr/bin/fish", "-C", "-c", "/usr/bin/printf safe_marker"],
["/usr/bin/fish", "-c/tmp/payload.fish", "/usr/bin/printf safe_marker"],
] {
let resolutions = ExecCommandResolution.resolveForAllowlist(
command: command,
rawCommand: nil,
cwd: nil,
env: ["PATH": "/usr/bin:/bin"])
#expect(resolutions.isEmpty)
}
}
@Test func `resolve for allowlist uses wrapper argv payload even with canonical raw command`() {
let command = ["/bin/sh", "-c", "echo allowlisted && /usr/bin/touch /tmp/openclaw-allowlist-test"]
let canonicalRaw = "/bin/sh -c \"echo allowlisted && /usr/bin/touch /tmp/openclaw-allowlist-test\""
let command = ["/bin/sh", "-lc", "echo allowlisted && /usr/bin/touch /tmp/openclaw-allowlist-test"]
let canonicalRaw = "/bin/sh -lc \"echo allowlisted && /usr/bin/touch /tmp/openclaw-allowlist-test\""
let resolutions = ExecCommandResolution.resolveForAllowlist(
command: command,
rawCommand: canonicalRaw,
@@ -235,25 +135,6 @@ struct ExecAllowlistTests {
#expect(resolutions[1].executableName == "touch")
}
@Test func `resolve for allowlist preserves generated sh lc raw payload binding`() {
let command = ["/bin/sh", "-lc", "/usr/bin/printf safe_marker"]
let resolutions = ExecCommandResolution.resolveForAllowlist(
command: command,
rawCommand: "/usr/bin/printf safe_marker",
cwd: nil,
env: ["PATH": "/usr/bin:/bin"])
#expect(resolutions.count == 1)
#expect(resolutions[0].resolvedPath == "/usr/bin/printf")
#expect(resolutions[0].executableName == "printf")
let rawlessResolutions = ExecCommandResolution.resolveForAllowlist(
command: command,
rawCommand: nil,
cwd: nil,
env: ["PATH": "/usr/bin:/bin"])
#expect(rawlessResolutions.isEmpty)
}
@Test func `resolve for allowlist fails closed for env modified shell wrappers`() {
let command = ["/usr/bin/env", "BASH_ENV=/tmp/payload.sh", "bash", "-lc", "echo allowlisted"]
let canonicalRaw = "/usr/bin/env BASH_ENV=/tmp/payload.sh bash -lc \"echo allowlisted\""
@@ -277,7 +158,7 @@ struct ExecAllowlistTests {
}
@Test func `resolve for allowlist keeps quoted operators in single segment`() {
let command = ["/bin/sh", "-c", "echo \"a && b\""]
let command = ["/bin/sh", "-lc", "echo \"a && b\""]
let resolutions = ExecCommandResolution.resolveForAllowlist(
command: command,
rawCommand: "echo \"a && b\"",
@@ -288,7 +169,7 @@ struct ExecAllowlistTests {
}
@Test func `resolve for allowlist fails closed on command substitution`() {
let command = ["/bin/sh", "-c", "echo $(/usr/bin/touch /tmp/openclaw-allowlist-test-subst)"]
let command = ["/bin/sh", "-lc", "echo $(/usr/bin/touch /tmp/openclaw-allowlist-test-subst)"]
let resolutions = ExecCommandResolution.resolveForAllowlist(
command: command,
rawCommand: "echo $(/usr/bin/touch /tmp/openclaw-allowlist-test-subst)",
@@ -298,7 +179,7 @@ struct ExecAllowlistTests {
}
@Test func `resolve for allowlist fails closed on quoted command substitution`() {
let command = ["/bin/sh", "-c", "echo \"ok $(/usr/bin/touch /tmp/openclaw-allowlist-test-quoted-subst)\""]
let command = ["/bin/sh", "-lc", "echo \"ok $(/usr/bin/touch /tmp/openclaw-allowlist-test-quoted-subst)\""]
let resolutions = ExecCommandResolution.resolveForAllowlist(
command: command,
rawCommand: "echo \"ok $(/usr/bin/touch /tmp/openclaw-allowlist-test-quoted-subst)\"",
@@ -308,7 +189,7 @@ struct ExecAllowlistTests {
}
@Test func `resolve for allowlist fails closed on line-continued command substitution`() {
let command = ["/bin/sh", "-c", "echo $\\\n(/usr/bin/touch /tmp/openclaw-allowlist-test-line-cont-subst)"]
let command = ["/bin/sh", "-lc", "echo $\\\n(/usr/bin/touch /tmp/openclaw-allowlist-test-line-cont-subst)"]
let resolutions = ExecCommandResolution.resolveForAllowlist(
command: command,
rawCommand: "echo $\\\n(/usr/bin/touch /tmp/openclaw-allowlist-test-line-cont-subst)",
@@ -320,7 +201,7 @@ struct ExecAllowlistTests {
@Test func `resolve for allowlist fails closed on chained line-continued command substitution`() {
let command = [
"/bin/sh",
"-c",
"-lc",
"echo ok && $\\\n(/usr/bin/touch /tmp/openclaw-allowlist-test-chained-line-cont-subst)",
]
let resolutions = ExecCommandResolution.resolveForAllowlist(
@@ -332,7 +213,7 @@ struct ExecAllowlistTests {
}
@Test func `resolve for allowlist fails closed on quoted backticks`() {
let command = ["/bin/sh", "-c", "echo \"ok `/usr/bin/id`\""]
let command = ["/bin/sh", "-lc", "echo \"ok `/usr/bin/id`\""]
let resolutions = ExecCommandResolution.resolveForAllowlist(
command: command,
rawCommand: "echo \"ok `/usr/bin/id`\"",
@@ -345,7 +226,7 @@ struct ExecAllowlistTests {
let fixtures = try Self.loadShellParserParityCases()
for fixture in fixtures {
let resolutions = ExecCommandResolution.resolveForAllowlist(
command: ["/bin/sh", "-c", fixture.command],
command: ["/bin/sh", "-lc", fixture.command],
rawCommand: fixture.command,
cwd: nil,
env: ["PATH": "/usr/bin:/bin"])
@@ -395,7 +276,7 @@ struct ExecAllowlistTests {
let command = [
"/usr/bin/env",
"/bin/sh",
"-c",
"-lc",
"echo allowlisted && /usr/bin/touch /tmp/openclaw-allowlist-test",
]
let resolutions = ExecCommandResolution.resolveForAllowlist(
@@ -409,7 +290,7 @@ struct ExecAllowlistTests {
}
@Test func `resolve for allowlist unwraps env dispatch wrappers inside shell segments`() {
let command = ["/bin/sh", "-c", "env /usr/bin/touch /tmp/openclaw-allowlist-test"]
let command = ["/bin/sh", "-lc", "env /usr/bin/touch /tmp/openclaw-allowlist-test"]
let resolutions = ExecCommandResolution.resolveForAllowlist(
command: command,
rawCommand: "env /usr/bin/touch /tmp/openclaw-allowlist-test",
@@ -421,7 +302,7 @@ struct ExecAllowlistTests {
}
@Test func `resolve for allowlist preserves env assignments inside shell segments`() {
let command = ["/bin/sh", "-c", "env FOO=bar /usr/bin/touch /tmp/openclaw-allowlist-test"]
let command = ["/bin/sh", "-lc", "env FOO=bar /usr/bin/touch /tmp/openclaw-allowlist-test"]
let resolutions = ExecCommandResolution.resolveForAllowlist(
command: command,
rawCommand: "env FOO=bar /usr/bin/touch /tmp/openclaw-allowlist-test",
@@ -445,8 +326,8 @@ struct ExecAllowlistTests {
}
@Test func `approval evaluator resolves shell payload from canonical wrapper text`() async {
let command = ["/bin/sh", "-c", "/usr/bin/printf ok"]
let rawCommand = "/bin/sh -c \"/usr/bin/printf ok\""
let command = ["/bin/sh", "-lc", "/usr/bin/printf ok"]
let rawCommand = "/bin/sh -lc \"/usr/bin/printf ok\""
let evaluation = await ExecApprovalEvaluator.evaluate(
command: command,
rawCommand: rawCommand,
@@ -469,32 +350,6 @@ struct ExecAllowlistTests {
#expect(patterns == ["/usr/bin/printf"])
}
@Test func `allow always patterns fail closed for env modified shell wrappers`() {
let patterns = ExecCommandResolution.resolveAllowAlwaysPatterns(
command: [
"/usr/bin/env",
"BASH_ENV=/tmp/payload.sh",
"/bin/sh",
"-lc",
"/usr/bin/printf ok",
],
cwd: nil,
env: ["PATH": "/usr/bin:/bin"],
rawCommand: "/usr/bin/printf ok")
#expect(patterns.isEmpty)
}
@Test func `allow always patterns preserve generated sh lc raw payload binding`() {
let patterns = ExecCommandResolution.resolveAllowAlwaysPatterns(
command: ["/bin/sh", "-lc", "/usr/bin/printf safe_marker"],
cwd: nil,
env: ["PATH": "/usr/bin:/bin"],
rawCommand: "/usr/bin/printf safe_marker")
#expect(patterns == ["/usr/bin/printf"])
}
@Test func `match all requires every segment to match`() {
let first = ExecCommandResolution(
rawExecutable: "echo",

View File

@@ -85,48 +85,6 @@ struct ExecSystemRunCommandValidatorTests {
}
}
@Test func `fish attached c command requires canonical raw command binding`() {
let command = ["/usr/bin/fish", "-c/tmp/payload.fish", "/usr/bin/printf safe_marker"]
let result = ExecSystemRunCommandValidator.resolve(
command: command,
rawCommand: "/usr/bin/printf safe_marker")
switch result {
case .ok:
Issue.record("expected rawCommand mismatch for attached fish command payload")
case let .invalid(message):
#expect(message.contains("rawCommand does not match command"))
}
}
@Test func `startup shell wrappers require canonical raw command binding`() {
for command in [
["/bin/bash", "-lc", "/usr/bin/printf safe_marker"],
["/bin/bash", "--rcfile", "/tmp/payload.sh", "-i", "-c", "/usr/bin/printf safe_marker"],
["/bin/bash", "--login", "-c", "/usr/bin/printf safe_marker"],
["/usr/bin/fish", "--init-command=/tmp/payload.fish", "-c", "/usr/bin/printf safe_marker"],
] {
let legacy = ExecSystemRunCommandValidator.resolve(
command: command,
rawCommand: "/usr/bin/printf safe_marker")
switch legacy {
case .ok:
Issue.record("expected rawCommand mismatch for startup shell wrapper")
case let .invalid(message):
#expect(message.contains("rawCommand does not match command"))
}
let canonicalRaw = ExecCommandFormatter.displayString(for: command)
let canonical = ExecSystemRunCommandValidator.resolve(command: command, rawCommand: canonicalRaw)
switch canonical {
case let .ok(resolved):
#expect(resolved.displayCommand == canonicalRaw)
case let .invalid(message):
Issue.record("unexpected invalid result for canonical raw command: \(message)")
}
}
}
private static func loadContractCases() throws -> [SystemRunCommandContractCase] {
let fixtureURL = try self.findContractFixtureURL()
let data = try Data(contentsOf: fixtureURL)

View File

@@ -336,118 +336,4 @@ struct OpenClawConfigFileTests {
}
}
}
@MainActor
@Test
func `save dict records preserved gateway auth in audit`() async throws {
let stateDir = FileManager().temporaryDirectory
.appendingPathComponent("openclaw-state-\(UUID().uuidString)", isDirectory: true)
let configPath = stateDir.appendingPathComponent("openclaw.json")
let auditPath = stateDir.appendingPathComponent("logs/config-audit.jsonl")
defer { try? FileManager().removeItem(at: stateDir) }
try await TestIsolation.withEnvValues([
"OPENCLAW_STATE_DIR": stateDir.path,
"OPENCLAW_CONFIG_PATH": configPath.path,
]) {
OpenClawConfigFile.saveDict([
"gateway": [
"mode": "local",
"auth": [
"mode": "token",
"token": "test-token", // pragma: allowlist secret
],
],
])
let saved = OpenClawConfigFile.saveDict([
"gateway": [
"mode": "local",
],
"browser": [
"enabled": false,
],
])
#expect(saved)
let data = try Data(contentsOf: configPath)
let root = try JSONSerialization.jsonObject(with: data) as? [String: Any]
let gateway = root?["gateway"] as? [String: Any]
let auth = gateway?["auth"] as? [String: Any]
#expect(gateway?["mode"] as? String == "local")
#expect(auth?["mode"] as? String == "token")
#expect(auth?["token"] as? String == "test-token") // pragma: allowlist secret
#expect((root?["meta"] as? [String: Any]) != nil)
let rawAudit = try String(contentsOf: auditPath, encoding: .utf8)
let last = rawAudit.split(whereSeparator: \.isNewline).map(String.init).last
let auditRoot = try JSONSerialization.jsonObject(with: Data((last ?? "{}").utf8)) as? [String: Any]
#expect(auditRoot?["result"] as? String == "success")
#expect(auditRoot?["preservedGatewayAuth"] as? Bool == true)
let suspicious = auditRoot?["suspicious"] as? [String] ?? []
#expect(suspicious.contains("gateway-auth-preserved"))
}
}
@MainActor
@Test
func `save dict rejects gateway mode removal and keeps previous config`() async throws {
let stateDir = FileManager().temporaryDirectory
.appendingPathComponent("openclaw-state-\(UUID().uuidString)", isDirectory: true)
let configPath = stateDir.appendingPathComponent("openclaw.json")
let auditPath = stateDir.appendingPathComponent("logs/config-audit.jsonl")
defer { try? FileManager().removeItem(at: stateDir) }
try await TestIsolation.withEnvValues([
"OPENCLAW_STATE_DIR": stateDir.path,
"OPENCLAW_CONFIG_PATH": configPath.path,
]) {
OpenClawConfigFile.saveDict([
"gateway": [
"mode": "local",
"auth": [
"mode": "token",
"token": "test-token", // pragma: allowlist secret
],
],
"browser": [
"enabled": true,
],
])
let before = try String(contentsOf: configPath, encoding: .utf8)
let saved = OpenClawConfigFile.saveDict([
"browser": [
"enabled": false,
],
])
#expect(!saved)
let after = try String(contentsOf: configPath, encoding: .utf8)
#expect(after == before)
let rawAudit = try String(contentsOf: auditPath, encoding: .utf8)
let lines = rawAudit.split(whereSeparator: \.isNewline).map(String.init)
guard let last = lines.last else {
Issue.record("Missing rejected config audit line")
return
}
let auditRoot = try JSONSerialization.jsonObject(with: Data(last.utf8)) as? [String: Any]
#expect(auditRoot?["result"] as? String == "rejected")
let suspicious = auditRoot?["suspicious"] as? [String] ?? []
let blocking = auditRoot?["blocking"] as? [String] ?? []
#expect(suspicious.contains("gateway-mode-removed"))
#expect(blocking.contains("gateway-mode-removed"))
if let rejectedPath = auditRoot?["rejectedPath"] as? String {
#expect(FileManager().fileExists(atPath: rejectedPath))
let attributes = try FileManager().attributesOfItem(atPath: rejectedPath)
let mode = attributes[.posixPermissions] as? NSNumber
#expect(mode?.intValue == 0o600)
} else {
Issue.record("Missing rejected payload path")
}
}
}
}

View File

@@ -2243,9 +2243,6 @@ public struct SessionsUsageParams: Codable, Sendable {
public let startdate: String?
public let enddate: String?
public let mode: AnyCodable?
public let range: AnyCodable?
public let groupby: AnyCodable?
public let includehistorical: Bool?
public let utcoffset: String?
public let limit: Int?
public let includecontextweight: Bool?
@@ -2255,9 +2252,6 @@ public struct SessionsUsageParams: Codable, Sendable {
startdate: String?,
enddate: String?,
mode: AnyCodable?,
range: AnyCodable?,
groupby: AnyCodable?,
includehistorical: Bool?,
utcoffset: String?,
limit: Int?,
includecontextweight: Bool?)
@@ -2266,9 +2260,6 @@ public struct SessionsUsageParams: Codable, Sendable {
self.startdate = startdate
self.enddate = enddate
self.mode = mode
self.range = range
self.groupby = groupby
self.includehistorical = includehistorical
self.utcoffset = utcoffset
self.limit = limit
self.includecontextweight = includecontextweight
@@ -2279,9 +2270,6 @@ public struct SessionsUsageParams: Codable, Sendable {
case startdate = "startDate"
case enddate = "endDate"
case mode
case range
case groupby = "groupBy"
case includehistorical = "includeHistorical"
case utcoffset = "utcOffset"
case limit
case includecontextweight = "includeContextWeight"
@@ -5144,7 +5132,6 @@ public struct ExecApprovalRequestParams: Codable, Sendable {
public let security: AnyCodable?
public let ask: AnyCodable?
public let warningtext: AnyCodable?
public let commandspans: [[String: AnyCodable]]?
public let agentid: AnyCodable?
public let resolvedpath: AnyCodable?
public let sessionkey: AnyCodable?
@@ -5167,7 +5154,6 @@ public struct ExecApprovalRequestParams: Codable, Sendable {
security: AnyCodable?,
ask: AnyCodable?,
warningtext: AnyCodable?,
commandspans: [[String: AnyCodable]]?,
agentid: AnyCodable?,
resolvedpath: AnyCodable?,
sessionkey: AnyCodable?,
@@ -5189,7 +5175,6 @@ public struct ExecApprovalRequestParams: Codable, Sendable {
self.security = security
self.ask = ask
self.warningtext = warningtext
self.commandspans = commandspans
self.agentid = agentid
self.resolvedpath = resolvedpath
self.sessionkey = sessionkey
@@ -5213,7 +5198,6 @@ public struct ExecApprovalRequestParams: Codable, Sendable {
case security
case ask
case warningtext = "warningText"
case commandspans = "commandSpans"
case agentid = "agentId"
case resolvedpath = "resolvedPath"
case sessionkey = "sessionKey"

View File

@@ -1,4 +1,4 @@
98f80c92fc4fcb37d41470216ae6cd19b094d7f67b0ddc4983eba04aba314fe0 config-baseline.json
d9c4b2035178d3ffe637b751036f12082d4f26761681bb8496b86550565307e8 config-baseline.core.json
ed15b24c1ccf0234e6b3435149a6f1c1e709579d1259f1d09402688799b149bd config-baseline.channel.json
7a9ed89a6ff7e578bfcab7828ab660af59e62402a85bfbfc05d5ae3d975e9728 config-baseline.plugin.json
7238265b921affbb481198f603293c9b1c988025713c55ee19fdbf132a8339ab config-baseline.json
97579293de31bc607194bce3e22c16d140c08ab9e6f1e38298f3ce47fbc9d68b config-baseline.core.json
463c45a79d02598184caccbc6f316692df962fe6b0e84d1a3e3cc1809f862b15 config-baseline.channel.json
b6d36d17e554a2ec5a1a6c6d32107a9a1113c274a700100962d97b6afbdafb25 config-baseline.plugin.json

View File

@@ -1,2 +1,2 @@
9f7ea91407a66fee6bcdebaf64bd15d0a6ae8b48cf100753c96f2ad29ad86390 plugin-sdk-api-baseline.json
4d516f6ac681cf55e916f712601abb8f5a7ddcd92a7710e8947f89c38e4054e7 plugin-sdk-api-baseline.jsonl
28e280d21693216c99cfa8da553589b41741d37c0ada956e316ee01d3d6c202c plugin-sdk-api-baseline.json
633dae33da97f6a073c5561709c57d5c0b7ff67af0512d0261f05455c24b38de plugin-sdk-api-baseline.jsonl

View File

@@ -3,26 +3,6 @@
"source": "OpenClaw",
"target": "OpenClaw"
},
{
"source": "iMessage",
"target": "iMessage"
},
{
"source": "Coming from BlueBubbles",
"target": "Coming from BlueBubbles"
},
{
"source": "BlueBubbles",
"target": "BlueBubbles"
},
{
"source": "Pairing",
"target": "配对"
},
{
"source": "Channel Routing",
"target": "频道路由"
},
{
"source": "ClawHub",
"target": "ClawHub"

View File

@@ -203,6 +203,7 @@
"zh-CN/tools/slash-commands",
"zh-CN/tools/skills",
"zh-CN/tools/skills-config",
"zh-CN/tools/clawhub",
"zh-CN/tools/plugin"
]
},

View File

@@ -16,14 +16,6 @@ This directory owns docs authoring, Mintlify link rules, and docs i18n policy.
- For docs, UI copy, and picker lists, order services/providers alphabetically unless the section is explicitly describing runtime order or auto-detection order.
- Keep bundled plugin naming consistent with the repo-wide plugin terminology rules in the root `AGENTS.md`.
## Internal Docs
- Long-lived private operator docs belong in `~/Projects/manager/docs/`.
- Repo-local internal scratch/mirror docs may live under ignored `docs/internal/`.
- Never add `docs/internal/**` pages to `docs/docs.json` navigation or link them from public docs.
- `scripts/docs-sync-publish.mjs` excludes and prunes `docs/internal/**` from the public `openclaw/docs` publish repo if a page is force-added later.
- Internal docs may mention repo paths, private app names, 1Password item names, and runbooks, but never include secret values.
## Docs i18n
- Foreign-language docs are not maintained in this repo. The generated publish output lives in the separate `openclaw/docs` repo (often cloned locally as `../openclaw-docs`).

View File

@@ -90,7 +90,7 @@ Recommended data provenance fields for every collected item:
Have the workflow reject or mark stale items before summarization. The LLM step should receive only structured JSON and should be asked to preserve `sourceUrl`, `retrievedAt`, and `asOf` in its output. Use [LLM Task](/tools/llm-task) when you need a schema-validated model step inside the workflow.
For reusable team or community workflows, package the CLI, `.lobster` files, and any setup notes as a skill or plugin and publish it through [ClawHub](/clawhub). Keep workflow-specific guardrails in that package unless the plugin API is missing a needed generic capability.
For reusable team or community workflows, package the CLI, `.lobster` files, and any setup notes as a skill or plugin and publish it through [ClawHub](/tools/clawhub). Keep workflow-specific guardrails in that package unless the plugin API is missing a needed generic capability.
## Sync modes

View File

@@ -662,7 +662,7 @@ Default slash command settings:
</Accordion>
<Accordion title="Live stream preview">
OpenClaw can stream draft replies by sending a temporary message and editing it as text arrives. `channels.discord.streaming` takes `off` | `partial` | `block` | `progress` (default). `progress` keeps one editable status draft and updates it with tool progress until final delivery; the shared starter label is a rolling line, so it scrolls away like the rest once enough work appears. `streamMode` is a legacy runtime alias. Run `openclaw doctor --fix` to rewrite persisted config to the canonical key.
OpenClaw can stream draft replies by sending a temporary message and editing it as text arrives. `channels.discord.streaming` takes `off` | `partial` | `block` | `progress` (default). `progress` keeps one editable status draft and updates it with tool progress until final delivery; `streamMode` is a legacy runtime alias. Run `openclaw doctor --fix` to rewrite persisted config to the canonical key.
Set `channels.discord.streaming.mode` to `off` to disable Discord preview edits. If Discord block streaming is explicitly enabled, OpenClaw skips the preview stream to avoid double-streaming.
@@ -687,7 +687,6 @@ Default slash command settings:
- `block` emits draft-sized chunks (use `draftChunk` to tune size and breakpoints, clamped to `textChunkLimit`).
- Media, error, and explicit-reply finals cancel pending preview edits.
- `streaming.preview.toolProgress` (default `true`) controls whether tool/progress updates reuse the preview message.
- Tool/progress rows render as compact emoji + title + detail when available, for example `🛠️ Bash: run tests` or `🔎 Web Search: for "query"`.
- `streaming.preview.commandText` / `streaming.progress.commandText` controls command/exec detail in compact progress lines: `raw` (default) or `status` (tool label only).
Hide raw command/exec text while keeping compact progress lines:
@@ -1172,7 +1171,6 @@ Auto-join example:
discord: {
voice: {
enabled: true,
mode: "stt-tts",
model: "openai/gpt-5.4-mini",
autoJoin: [
{
@@ -1186,10 +1184,7 @@ Auto-join example:
reconnectGraceMs: 15000,
tts: {
provider: "openai",
openai: {
model: "gpt-4o-mini-tts",
voice: "cedar",
},
openai: { voice: "onyx" },
},
},
},
@@ -1200,21 +1195,17 @@ Auto-join example:
Notes:
- `voice.tts` overrides `messages.tts` for voice playback only.
- `voice.mode` controls the conversation path: `stt-tts` keeps the existing batch STT plus TTS flow, `talk-buffer` uses a realtime voice shell for turn timing/transcription/playback while the OpenClaw agent produces the answer, and `bidi` lets the realtime model converse directly while exposing `openclaw_agent_consult` for the OpenClaw brain.
- `voice.model` overrides the OpenClaw agent brain for Discord voice responses and realtime consults. Leave it unset to inherit the routed agent model. It is separate from `voice.realtime.model`.
- In `stt-tts` mode, STT uses `tools.media.audio`; `voice.model` does not affect transcription.
- In realtime modes, `voice.realtime.provider`, `voice.realtime.model`, and `voice.realtime.voice` configure the realtime audio session. For OpenAI Realtime 2 plus the Codex brain, use `voice.realtime.model: "gpt-realtime-2"` and `voice.model: "openai-codex/gpt-5.5"`.
- For an OpenAI voice on Discord playback, set `voice.tts.provider: "openai"` and choose a Text-to-speech voice under `voice.tts.openai.voice` or `voice.tts.providers.openai.voice`. `cedar` is a good masculine-sounding choice on the current OpenAI TTS model.
- `voice.model` overrides the LLM used for Discord voice channel responses only. Leave it unset to inherit the routed agent model.
- STT uses `tools.media.audio`; `voice.model` does not affect transcription.
- Per-channel Discord `systemPrompt` overrides apply to voice transcript turns for that voice channel.
- Voice transcript turns derive owner status from Discord `allowFrom` (or `dm.allowFrom`); non-owner speakers cannot access owner-only tools (for example `gateway` and `cron`).
- Discord voice is opt-in for text-only configs; set `channels.discord.voice.enabled=true` (or keep an existing `channels.discord.voice` block) to enable `/vc` commands, the voice runtime, and the `GuildVoiceStates` gateway intent.
- `channels.discord.intents.voiceStates` can explicitly override voice-state intent subscription. Leave it unset for the intent to follow effective voice enablement.
- If `voice.autoJoin` has multiple entries for the same guild, OpenClaw joins the last configured channel for that guild.
- `voice.daveEncryption` and `voice.decryptionFailureTolerance` pass through to `@discordjs/voice` join options.
- `@discordjs/voice` defaults are `daveEncryption=true` and `decryptionFailureTolerance=24` if unset.
- `voice.connectTimeoutMs` controls the initial `@discordjs/voice` Ready wait for `/vc join` and auto-join attempts. Default: `30000`.
- `voice.reconnectGraceMs` controls how long OpenClaw waits for a disconnected voice session to begin reconnecting before destroying it. Default: `15000`.
- In `stt-tts` mode, voice playback does not stop just because another user starts speaking. To avoid feedback loops, OpenClaw ignores new voice capture while TTS is playing; speak after playback finishes for the next turn. Realtime modes forward speaker starts as barge-in signals to the realtime provider.
- Voice playback does not stop just because another user starts speaking. To avoid feedback loops, OpenClaw ignores new voice capture while TTS is playing; speak after playback finishes for the next turn.
- `voice.captureSilenceGraceMs` controls how long OpenClaw waits after Discord reports a speaker has stopped before finalizing that audio segment for STT. Default: `2500`; raise this if Discord splits normal pauses into choppy partial transcripts.
- When ElevenLabs is the selected TTS provider, Discord voice playback uses streaming TTS and starts from the provider response stream. Providers without streaming support fall back to the synthesized temp-file path.
- OpenClaw also watches receive decrypt failures and auto-recovers by leaving/rejoining the voice channel after repeated failures in a short window.
@@ -1222,7 +1213,7 @@ Notes:
- `The operation was aborted` receive events are expected when OpenClaw finalizes a captured speaker segment; they are verbose diagnostics, not warnings.
- Verbose Discord voice logs include a bounded one-line STT transcript preview for each accepted speaker segment, so debugging shows both the user side and the agent reply side without dumping unbounded transcript text.
STT plus TTS pipeline:
Voice channel pipeline:
- Discord PCM capture is converted to a WAV temp file.
- `tools.media.audio` handles STT, for example `openai/gpt-4o-mini-transcribe`.
@@ -1230,51 +1221,7 @@ STT plus TTS pipeline:
- `voice.model`, when set, overrides only the response LLM for this voice-channel turn.
- `voice.tts` is merged over `messages.tts`; streaming-capable providers feed the player directly, otherwise the resulting audio file is played in the joined channel.
Realtime talk-buffer example:
```json5
{
channels: {
discord: {
voice: {
enabled: true,
mode: "talk-buffer",
model: "openai-codex/gpt-5.5",
realtime: {
provider: "openai",
model: "gpt-realtime-2",
voice: "cedar",
},
},
},
},
}
```
Realtime bidi example:
```json5
{
channels: {
discord: {
voice: {
enabled: true,
mode: "bidi",
model: "openai-codex/gpt-5.5",
realtime: {
provider: "openai",
model: "gpt-realtime-2",
voice: "cedar",
toolPolicy: "safe-read-only",
consultPolicy: "always",
},
},
},
},
}
```
Credentials are resolved per component: LLM route auth for `voice.model`, STT auth for `tools.media.audio`, TTS auth for `messages.tts`/`voice.tts`, and realtime provider auth for `voice.realtime.providers` or the provider's normal auth config.
Credentials are resolved per component: LLM route auth for `voice.model`, STT auth for `tools.media.audio`, and TTS auth for `messages.tts`/`voice.tts`.
### Voice messages

View File

@@ -1,242 +0,0 @@
---
summary: "Migrate old BlueBubbles configs to the bundled iMessage plugin without losing pairing, allowlists, or group bindings."
read_when:
- Planning a move from BlueBubbles to the bundled iMessage plugin
- Translating BlueBubbles config keys to iMessage equivalents
- Verifying imsg before enabling the iMessage plugin
title: "Coming from BlueBubbles"
---
The bundled `imessage` plugin now reaches the same private API surface as BlueBubbles (`react`, `edit`, `unsend`, `reply`, `sendWithEffect`, group management, attachments) by driving [`steipete/imsg`](https://github.com/steipete/imsg) over JSON-RPC. If you already run a Mac with `imsg` installed, you can drop the BlueBubbles server and let the plugin talk to Messages.app directly.
BlueBubbles support was removed. OpenClaw supports iMessage through `imsg` only. This guide is for migrating old `channels.bluebubbles` configs to `channels.imessage`; there is no other supported migration path.
## When this migration makes sense
- You already run `imsg` on the same Mac (or one reachable over SSH) where Messages.app is signed in.
- You want one fewer moving part — no separate BlueBubbles server, no REST endpoint to authenticate, no webhook plumbing. Single CLI binary instead of a server + client app + helper.
- You are on a [supported macOS / `imsg` build](/channels/imessage#requirements-and-permissions-macos) where the private API probe reports `available: true`.
## What imsg does
`imsg` is a local macOS CLI for Messages. OpenClaw starts `imsg rpc` as a child process and talks JSON-RPC over stdin/stdout. There is no HTTP server, webhook URL, background daemon, launch agent, or port to expose.
- Reads come from `~/Library/Messages/chat.db` using a read-only SQLite handle.
- Live inbound messages come from `imsg watch` / `watch.subscribe`, which follows `chat.db` filesystem events with a polling fallback.
- Sends use Messages.app automation for normal text and file sends.
- Advanced actions use `imsg launch` to inject the `imsg` helper into Messages.app. That is what unlocks read receipts, typing indicators, rich sends, edit, unsend, threaded reply, tapbacks, and group management.
- Linux builds can inspect a copied `chat.db`, but cannot send, watch the live Mac database, or drive Messages.app. For OpenClaw iMessage, run `imsg` on the signed-in Mac or through an SSH wrapper to that Mac.
## Before you start
1. Install `imsg` on the Mac that runs Messages.app:
```bash
brew install steipete/tap/imsg
imsg --version
imsg chats --limit 3
```
If `imsg chats` fails with `unable to open database file`, empty output, or `authorization denied`, grant Full Disk Access to the terminal, editor, Node process, Gateway service, or SSH parent process that launches `imsg`, then reopen that parent process.
2. Verify the read, watch, send, and RPC surfaces before changing OpenClaw config:
```bash
imsg chats --limit 10 --json | jq -s
imsg history --chat-id 42 --limit 10 --attachments --json | jq -s
imsg watch --chat-id 42 --reactions --json
imsg send --chat-id 42 --text "OpenClaw imsg test"
imsg rpc --help
```
Replace `42` with a real chat id from `imsg chats`. Sending requires Automation permission for Messages.app. If OpenClaw will run through SSH, run these commands through the same SSH wrapper or user context that OpenClaw will use.
3. Enable the private API bridge when you need advanced actions:
```bash
imsg launch
imsg status --json
```
`imsg launch` requires SIP to be disabled. Basic send, history, and watch work without `imsg launch`; advanced actions do not.
4. Verify the bridge through OpenClaw:
```bash
openclaw channels status --probe
```
You want `imessage.privateApi.available: true`. If it reports `false`, fix that first — see [Capability detection](/channels/imessage#private-api-actions).
5. Snapshot your config:
```bash
cp ~/.openclaw/openclaw.json5 ~/.openclaw/openclaw.json5.bak
```
## Config translation
iMessage and BlueBubbles share a lot of channel-level config. The keys that change are mostly transport (REST server vs local CLI). Behavior keys (`dmPolicy`, `groupPolicy`, `allowFrom`, etc.) keep the same meaning.
| BlueBubbles | bundled iMessage | Notes |
| ---------------------------------------------------------- | ----------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `channels.bluebubbles.enabled` | `channels.imessage.enabled` | Same semantics. |
| `channels.bluebubbles.serverUrl` | _(removed)_ | No REST server — the plugin spawns `imsg rpc` over stdio. |
| `channels.bluebubbles.password` | _(removed)_ | No webhook authentication needed. |
| _(implicit)_ | `channels.imessage.cliPath` | Path to `imsg` (default `imsg`); use a wrapper script for SSH. |
| _(implicit)_ | `channels.imessage.dbPath` | Optional Messages.app `chat.db` override; auto-detected when omitted. |
| _(implicit)_ | `channels.imessage.remoteHost` | `host` or `user@host` — only needed when `cliPath` is an SSH wrapper and you want SCP attachment fetches. |
| `channels.bluebubbles.dmPolicy` | `channels.imessage.dmPolicy` | Same values (`pairing` / `allowlist` / `open` / `disabled`). |
| `channels.bluebubbles.allowFrom` | `channels.imessage.allowFrom` | Pairing approvals carry over by handle, not by token. |
| `channels.bluebubbles.groupPolicy` | `channels.imessage.groupPolicy` | Same values (`allowlist` / `open` / `disabled`). |
| `channels.bluebubbles.groupAllowFrom` | `channels.imessage.groupAllowFrom` | Same. |
| `channels.bluebubbles.groups` | `channels.imessage.groups` | **Copy this verbatim, including any `groups: { "*": { ... } }` wildcard entry.** Per-group `requireMention`, `tools`, `toolsBySender` carry over. With `groupPolicy: "allowlist"`, an empty or missing `groups` block silently drops every group message — see "Group registry footgun" below. |
| `channels.bluebubbles.sendReadReceipts` | `channels.imessage.sendReadReceipts` | Default `true`. With the bundled plugin this only fires when the private API probe is up. |
| `channels.bluebubbles.includeAttachments` | `channels.imessage.includeAttachments` | Same shape, **same off-by-default**. If you had attachments flowing on BlueBubbles you must re-set this explicitly on the iMessage block — it does not carry over implicitly, and inbound photos/media will be silently dropped with no `Inbound message` log line until you do. |
| `channels.bluebubbles.attachmentRoots` | `channels.imessage.attachmentRoots` | Local roots; same wildcard rules. |
| _(N/A)_ | `channels.imessage.remoteAttachmentRoots` | Only used when `remoteHost` is set for SCP fetches. |
| `channels.bluebubbles.mediaMaxMb` | `channels.imessage.mediaMaxMb` | Default 16 MB on iMessage (BlueBubbles default was 8 MB). Set explicitly if you want to keep the lower cap. |
| `channels.bluebubbles.textChunkLimit` | `channels.imessage.textChunkLimit` | Default 4000 on both. |
| `channels.bluebubbles.coalesceSameSenderDms` | `channels.imessage.coalesceSameSenderDms` | Same opt-in. DM-only — group chats keep instant per-message dispatch on both channels. Widens the default inbound debounce to 2500 ms when enabled without an explicit `messages.inbound.byChannel.imessage`. See [iMessage docs § Coalescing split-send DMs](/channels/imessage#coalescing-split-send-dms-command--url-in-one-composition). |
| `channels.bluebubbles.enrichGroupParticipantsFromContacts` | _(N/A)_ | iMessage already reads sender display names from `chat.db`. |
| `channels.bluebubbles.actions.*` | `channels.imessage.actions.*` | Per-action toggles: `reactions`, `edit`, `unsend`, `reply`, `sendWithEffect`, `renameGroup`, `setGroupIcon`, `addParticipant`, `removeParticipant`, `leaveGroup`, `sendAttachment`. |
Multi-account configs (`channels.bluebubbles.accounts.*`) translate one-to-one to `channels.imessage.accounts.*`.
## Group registry footgun
The bundled iMessage plugin runs **two** separate group allowlist gates back-to-back. Both must pass for a group message to reach the agent:
1. **Sender / chat-target allowlist** (`channels.imessage.groupAllowFrom`) — checked by `isAllowedIMessageSender`. Matches inbound messages by sender handle, `chat_guid`, `chat_identifier`, or `chat_id`. Same shape as BlueBubbles.
2. **Group registry** (`channels.imessage.groups`) — checked by `resolveChannelGroupPolicy` from `inbound-processing.ts:199`. With `groupPolicy: "allowlist"`, this gate requires either:
- a `groups: { "*": { ... } }` wildcard entry (sets `allowAll = true`), or
- an explicit per-`chat_id` entry under `groups`.
If gate 1 passes but gate 2 fails, the message is dropped. The plugin emits two `warn`-level signals so this is no longer silent at default log level:
- A one-time startup `warn` per account when `groupPolicy: "allowlist"` is set but `channels.imessage.groups` is empty (no `"*"` wildcard, no per-`chat_id` entries) — fired before any messages land.
- A one-time per-`chat_id` `warn` the first time a specific group is dropped at runtime, naming the chat_id and the exact key to add to `groups` to allow it.
DMs continue to work because they take a different code path.
This is the most common BlueBubbles → bundled-iMessage migration failure mode: operators copy `groupAllowFrom` and `groupPolicy` but skip the `groups` block, because BlueBubbles' `groups: { "*": { "requireMention": true } }` looks like an unrelated mention setting. It's actually load-bearing for the registry gate.
The minimum config to keep group messages flowing after `groupPolicy: "allowlist"`:
```json5
{
channels: {
imessage: {
groupPolicy: "allowlist",
groupAllowFrom: ["+15555550123", "chat_guid:any;-;..."],
groups: {
"*": { requireMention: true },
},
},
},
}
```
`requireMention: true` under `*` is harmless when no mention patterns are configured: the runtime sets `canDetectMention = false` and short-circuits the mention drop at `inbound-processing.ts:512`. With mention patterns configured (`agents.list[].groupChat.mentionPatterns`), it works as expected.
If the gateway logs `imessage: dropping group message from chat_id=<id>` or the startup line `imessage: groupPolicy="allowlist" but channels.imessage.groups is empty`, gate 2 is dropping — add the `groups` block.
## Step-by-step
1. Add an iMessage block alongside the existing BlueBubbles block. Keep the old block only as a copy source until the new path is verified:
```json5
{
channels: {
bluebubbles: {
enabled: true,
// ... existing config ...
},
imessage: {
enabled: false, // turn on after the dry run below
cliPath: "/opt/homebrew/bin/imsg",
dmPolicy: "pairing",
allowFrom: ["+15555550123"], // copy from bluebubbles.allowFrom
groupPolicy: "allowlist",
groupAllowFrom: [], // copy from bluebubbles.groupAllowFrom
groups: { "*": { requireMention: true } }, // copy from bluebubbles.groups — silently drops groups if missing, see "Group registry footgun" above
actions: {
reactions: true,
edit: true,
unsend: true,
reply: true,
sendWithEffect: true,
sendAttachment: true,
},
},
},
}
```
2. **Dry-run probe** — start the gateway and confirm iMessage reports healthy:
```bash
openclaw gateway
openclaw channels status
openclaw channels status --probe # expect imessage.privateApi.available: true
```
Because `imessage.enabled` is still `false`, no inbound iMessage traffic is routed yet — but `--probe` exercises the bridge so you catch permission/install issues before the cutover.
3. **Cut over.** Remove the BlueBubbles config and enable iMessage in one config edit:
```json5
{
channels: {
imessage: { enabled: true /* ... */ },
},
}
```
Restart the gateway. Inbound iMessage traffic now flows through the bundled plugin.
4. **Verify DMs.** Send the agent a direct message; confirm the reply lands.
5. **Verify groups separately.** DMs and groups take different code paths — DM success does not prove groups are routing. Send the agent a message in a paired group chat and confirm the reply lands. If the group goes silent (no agent reply, no error), check the gateway log for `imessage: dropping group message from chat_id=<id>` or the startup `imessage: groupPolicy="allowlist" but channels.imessage.groups is empty` line — both fire at the default log level. If either appears, your `groups` block is missing or empty — see "Group registry footgun" above.
6. **Verify the action surface** — from a paired DM, ask the agent to react, edit, unsend, reply, send a photo, and (in a group) rename the group / add or remove a participant. Each action should land natively in Messages.app. If any throws "iMessage `<action>` requires the imsg private API bridge", run `imsg launch` again and refresh `channels status --probe`.
7. **Remove the BlueBubbles server and config** once iMessage DMs, groups, and actions are verified. OpenClaw will not use `channels.bluebubbles`.
## Action parity at a glance
| Action | legacy BlueBubbles | bundled iMessage |
| ---------------------------------------------------------- | ----------------------------------- | ------------------------------------------------------------------------------------ |
| Send text / SMS fallback | ✅ | ✅ |
| Send media (photo, video, file, voice) | ✅ | ✅ |
| Threaded reply (`reply_to_guid`) | ✅ | ✅ (closes [#51892](https://github.com/openclaw/openclaw/issues/51892)) |
| Tapback (`react`) | ✅ | ✅ |
| Edit / unsend (macOS 13+ recipients) | ✅ | ✅ |
| Send with screen effect | ✅ | ✅ (closes part of [#9394](https://github.com/openclaw/openclaw/issues/9394)) |
| Rich text bold / italic / underline / strikethrough | ✅ | ✅ (typed-run formatting via attributedBody) |
| Rename group / set group icon | ✅ | ✅ |
| Add / remove participant, leave group | ✅ | ✅ |
| Read receipts and typing indicator | ✅ | ✅ (gated on private API probe) |
| Same-sender DM coalescing | ✅ | ✅ (DM-only; opt-in via `channels.imessage.coalesceSameSenderDms`) |
| Catchup of inbound messages received while gateway is down | ✅ (webhook replay + history fetch) | _(not yet — tracked at [#78649](https://github.com/openclaw/openclaw/issues/78649))_ |
The catchup gap is the most operationally significant one for production deployments: planned restarts, mac sleep, or an unexpected gateway crash that takes more than a few seconds will silently drop any inbound iMessage traffic that arrives during the gap when running on bundled iMessage. BlueBubbles' webhook + history-fetch flow recovered those messages on reconnect, but BlueBubbles is no longer supported. There is no supported migration path that preserves catchup today; wait for [#78649](https://github.com/openclaw/openclaw/issues/78649).
## Pairing, sessions, and ACP bindings
- **Pairing approvals** carry over by handle. You do not need to re-approve known senders — `channels.imessage.allowFrom` recognizes the same `+15555550123` / `user@example.com` strings BlueBubbles used.
- **Sessions** stay scoped per agent + chat. DMs collapse into the agent main session under default `session.dmScope=main`; group sessions stay isolated per `chat_id`. The session keys differ (`agent:<id>:imessage:group:<chat_id>` vs the BlueBubbles equivalent) — old conversation history under BlueBubbles session keys does not carry into iMessage sessions.
- **ACP bindings** referencing `match.channel: "bluebubbles"` need to be updated to `"imessage"`. The `match.peer.id` shapes (`chat_id:`, `chat_guid:`, `chat_identifier:`, bare handle) are identical.
## No rollback channel
There is no supported BlueBubbles runtime to switch back to. If iMessage verification fails, set `channels.imessage.enabled: false`, restart the Gateway, fix the `imsg` blocker, and retry the cutover.
The reply cache lives at `~/.openclaw/state/imessage/reply-cache.jsonl` (mode `0600`, parent dir `0700`). It is safe to delete if you want a clean slate.
## Related
- [iMessage](/channels/imessage) — full iMessage channel reference, including `imsg launch` setup and capability detection.
- `/channels/bluebubbles` — legacy URL that redirects to this migration guide.
- [Pairing](/channels/pairing) — DM authentication and pairing flow.
- [Channel Routing](/channels/channel-routing) — how the gateway picks a channel for outbound replies.

View File

@@ -1,5 +1,5 @@
---
summary: "Native iMessage support via imsg (JSON-RPC over stdio), with private API actions for replies, tapbacks, effects, attachments, and group management. Preferred for new OpenClaw iMessage setups when host requirements fit."
summary: "Native iMessage support via imsg (JSON-RPC over stdio). Preferred for new OpenClaw iMessage setups when host requirements fit."
read_when:
- Setting up iMessage support
- Debugging iMessage send/receive
@@ -8,20 +8,15 @@ title: "iMessage"
<Note>
For OpenClaw iMessage deployments, use `imsg` on a signed-in macOS Messages host. If your Gateway runs on Linux or Windows, point `channels.imessage.cliPath` at an SSH wrapper that runs `imsg` on the Mac.
**Known gap: no gateway-downtime catchup.** Messages that arrive while the gateway is down (crash, restart, Mac sleep, machine off) are not delivered to the agent once the gateway comes back up — `imsg watch` resumes from the current state and ignores anything that landed in `chat.db` during the gap. Tracked at [openclaw#78649](https://github.com/openclaw/openclaw/issues/78649).
</Note>
<Warning>
BlueBubbles support was removed. Migrate `channels.bluebubbles` configs to `channels.imessage`; OpenClaw supports iMessage through `imsg` only.
BlueBubbles is deprecated and no longer ships as a bundled OpenClaw channel. Migrate `channels.bluebubbles` configs to `channels.imessage`; OpenClaw now supports iMessage through `imsg` only. If you still need a BlueBubbles-backed bridge, publish or install it as a third-party plugin outside core.
</Warning>
Status: native external CLI integration. Gateway spawns `imsg rpc` and communicates over JSON-RPC on stdio (no separate daemon/port). Advanced actions require `imsg launch` and a successful private API probe.
Status: native external CLI integration. Gateway spawns `imsg rpc` and communicates over JSON-RPC on stdio (no separate daemon/port).
<CardGroup cols={3}>
<Card title="Private API actions" icon="wand-sparkles" href="#private-api-actions">
Replies, tapbacks, effects, attachments, and group management.
</Card>
<Card title="Pairing" icon="link" href="/channels/pairing">
iMessage DMs default to pairing mode.
</Card>
@@ -43,8 +38,6 @@ Status: native external CLI integration. Gateway spawns `imsg rpc` and communica
```bash
brew install steipete/tap/imsg
imsg rpc --help
imsg launch
openclaw channels status --probe
```
</Step>
@@ -126,7 +119,6 @@ exec ssh -T gateway-host imsg "$@"
- Messages must be signed in on the Mac running `imsg`.
- Full Disk Access is required for the process context running OpenClaw/`imsg` (Messages DB access).
- Automation permission is required to send messages through Messages.app.
- For advanced actions (react / edit / unsend / threaded reply / effects / group ops), System Integrity Protection must be disabled — see [Enabling the imsg private API](#enabling-the-imsg-private-api) below. Basic text and media send/receive work without it.
<Tip>
Permissions are granted per process context. If gateway runs headless (LaunchAgent/SSH), run a one-time interactive command in that same context to trigger prompts:
@@ -139,71 +131,6 @@ imsg send <handle> "test"
</Tip>
## Enabling the imsg private API
`imsg` ships in two operational modes:
- **Basic mode** (default, no SIP changes needed): outbound text and media via `send`, inbound watch/history, chat list. This is what you get out of the box from a fresh `brew install steipete/tap/imsg` plus the standard macOS permissions above.
- **Private API mode**: `imsg` injects a helper dylib into `Messages.app` to call internal `IMCore` functions. This is what unlocks `react`, `edit`, `unsend`, `reply` (threaded), `sendWithEffect`, `renameGroup`, `setGroupIcon`, `addParticipant`, `removeParticipant`, `leaveGroup`, plus typing indicators and read receipts.
To reach the advanced action surface that this channel page documents, you need Private API mode. The `imsg` README is explicit about the requirement:
> Advanced features such as `read`, `typing`, `launch`, bridge-backed rich send, message mutation, and chat management are opt-in. They require SIP to be disabled and a helper dylib to be injected into `Messages.app`. `imsg launch` refuses to inject when SIP is enabled.
The helper-injection technique uses `imsg`'s own dylib to reach Messages private APIs. There is no third-party server or BlueBubbles runtime in the OpenClaw iMessage path.
<Warning>
**Disabling SIP is a real security tradeoff.** SIP is one of macOS's core protections against running modified system code; turning it off system-wide opens up additional attack surface and side effects. Notably, **disabling SIP on Apple Silicon Macs also disables the ability to install and run iOS apps on your Mac**.
Treat this as a deliberate operational choice, not a default. If your threat model can't tolerate SIP being off, bundled iMessage is limited to basic mode — text and media send/receive only, no reactions / edit / unsend / effects / group ops.
</Warning>
### Setup
1. **Install (or upgrade) `imsg`** on the Mac that runs Messages.app:
```bash
brew install steipete/tap/imsg
imsg --version
imsg status --json
```
The `imsg status --json` output reports `bridge_version`, `rpc_methods`, and per-method `selectors` so you can see what the current build supports before you start.
2. **Disable System Integrity Protection.** This is macOS-version-specific because the underlying Apple requirement depends on the OS and hardware:
- **macOS 10.1310.15 (SierraCatalina):** disable Library Validation via Terminal, reboot to Recovery Mode, run `csrutil disable`, restart.
- **macOS 11+ (Big Sur and later), Intel:** Recovery Mode (or Internet Recovery), `csrutil disable`, restart.
- **macOS 11+, Apple Silicon:** power-button startup sequence to enter Recovery; on recent macOS versions hold the **Left Shift** key when you click Continue, then `csrutil disable`. Virtual-machine setups follow a separate flow — take a VM snapshot first.
- **macOS 26 / Tahoe:** library-validation policies and `imagent` private-entitlement checks have tightened further; `imsg` may need an updated build to keep up. If `imsg launch` injection or specific `selectors` start returning false after a macOS major upgrade, check `imsg`'s release notes before assuming the SIP step succeeded.
Follow Apple's Recovery-mode flow for your Mac to disable SIP before running `imsg launch`.
3. **Inject the helper.** With SIP disabled and Messages.app signed in:
```bash
imsg launch
```
`imsg launch` refuses to inject when SIP is still enabled, so this also doubles as a confirmation that step 2 took.
4. **Verify the bridge from OpenClaw:**
```bash
openclaw channels status --probe
```
The iMessage entry should report `works`, and `imsg status --json | jq '.selectors'` should show `retractMessagePart: true` plus whichever edit / typing / read selectors your macOS build exposes. The OpenClaw plugin per-method gating in `actions.ts` only advertises actions whose underlying selector is `true`, so the action surface you see in the agent's tool list reflects what the bridge can actually do on this host.
If `openclaw channels status --probe` reports the channel as `works` but specific actions throw "iMessage `<action>` requires the imsg private API bridge" at dispatch time, run `imsg launch` again — the helper can fall out (Messages.app restart, OS update, etc.) and the cached `available: true` status will keep advertising actions until the next probe refreshes.
### When you can't disable SIP
If SIP-disabled isn't acceptable for your threat model:
- `imsg` falls back to basic mode — text + media + receive only.
- The OpenClaw plugin still advertises text/media send and inbound monitoring; it just hides `react`, `edit`, `unsend`, `reply`, `sendWithEffect`, and group ops from the action surface (per the per-method capability gate).
- You can run a separate non-Apple-Silicon Mac (or a dedicated bot Mac) with SIP off for the iMessage workload, while keeping SIP enabled on your primary devices. See [Dedicated bot macOS user (separate iMessage identity)](#deployment-patterns) below.
## Access control and routing
<Tabs>
@@ -233,36 +160,6 @@ If SIP-disabled isn't acceptable for your threat model:
Runtime fallback: if `groupAllowFrom` is unset, iMessage group sender checks fall back to `allowFrom` when available.
Runtime note: if `channels.imessage` is completely missing, runtime falls back to `groupPolicy="allowlist"` and logs a warning (even if `channels.defaults.groupPolicy` is set).
<Warning>
Group routing has **two** allowlist gates running back-to-back, and both must pass:
1. **Sender / chat-target allowlist** (`channels.imessage.groupAllowFrom`) — handle, `chat_guid`, `chat_identifier`, or `chat_id`.
2. **Group registry** (`channels.imessage.groups`) — with `groupPolicy: "allowlist"`, this gate requires either a `groups: { "*": { ... } }` wildcard entry (sets `allowAll = true`), or an explicit per-`chat_id` entry under `groups`.
If gate 2 has nothing in it, every group message is dropped. The plugin emits two `warn`-level signals at the default log level:
- one-time per account at startup: `imessage: groupPolicy="allowlist" but channels.imessage.groups is empty for account "<id>"`
- one-time per `chat_id` at runtime: `imessage: dropping group message from chat_id=<id> ...`
DMs continue to work because they take a different code path.
Minimum config to keep groups flowing under `groupPolicy: "allowlist"`:
```json5
{
channels: {
imessage: {
groupPolicy: "allowlist",
groupAllowFrom: ["+15555550123"],
groups: { "*": { "requireMention": true } },
},
},
}
```
If those `warn` lines appear in the gateway log, gate 2 is dropping — add the `groups` block.
</Warning>
Mention gating for groups:
- iMessage has no native mention metadata
@@ -367,24 +264,24 @@ See [ACP Agents](/tools/acp-agents) for shared ACP binding behavior.
Example:
```json5
{
channels: {
imessage: {
enabled: true,
cliPath: "~/.openclaw/scripts/imsg-ssh",
remoteHost: "bot@mac-mini.tailnet-1234.ts.net",
includeAttachments: true,
dbPath: "/Users/bot/Library/Messages/chat.db",
},
},
}
```
```json5
{
channels: {
imessage: {
enabled: true,
cliPath: "~/.openclaw/scripts/imsg-ssh",
remoteHost: "bot@mac-mini.tailnet-1234.ts.net",
includeAttachments: true,
dbPath: "/Users/bot/Library/Messages/chat.db",
},
},
}
```
```bash
#!/usr/bin/env bash
exec ssh -T bot@mac-mini.tailnet-1234.ts.net imsg "$@"
```
```bash
#!/usr/bin/env bash
exec ssh -T bot@mac-mini.tailnet-1234.ts.net imsg "$@"
```
Use SSH keys so both SSH and SCP are non-interactive.
Ensure the host key is trusted first (for example `ssh bot@mac-mini.tailnet-1234.ts.net`) so `known_hosts` is populated.
@@ -403,7 +300,7 @@ See [ACP Agents](/tools/acp-agents) for shared ACP binding behavior.
<AccordionGroup>
<Accordion title="Attachments and media">
- inbound attachment ingestion is **off by default** — set `channels.imessage.includeAttachments: true` to forward photos, voice memos, video, and other attachments to the agent. With it disabled, attachment-only iMessages are dropped before reaching the agent and may produce no `Inbound message` log line at all.
- inbound attachment ingestion is optional: `channels.imessage.includeAttachments`
- remote attachment paths can be fetched via SCP when `remoteHost` is set
- attachment paths must match allowed roots:
- `channels.imessage.attachmentRoots` (local)
@@ -435,76 +332,10 @@ See [ACP Agents](/tools/acp-agents) for shared ACP binding behavior.
- `sms:+1555...`
- `user@example.com`
```bash
imsg chats --limit 20
```
</Accordion>
</AccordionGroup>
## Private API actions
When `imsg launch` is running and `openclaw channels status --probe` reports `privateApi.available: true`, the message tool can use iMessage-native actions in addition to normal text sends.
```json5
{
channels: {
imessage: {
actions: {
reactions: true,
edit: true,
unsend: true,
reply: true,
sendWithEffect: true,
sendAttachment: true,
renameGroup: true,
setGroupIcon: true,
addParticipant: true,
removeParticipant: true,
leaveGroup: true,
},
},
},
}
```bash
imsg chats --limit 20
```
<AccordionGroup>
<Accordion title="Available actions">
- **react**: Add/remove iMessage tapbacks (`messageId`, `emoji`, `remove`). Supported tapbacks map to love, like, dislike, laugh, emphasize, and question.
- **reply**: Send a threaded reply to an existing message (`messageId`, `text` or `message`, plus `chatGuid`, `chatId`, `chatIdentifier`, or `to`).
- **sendWithEffect**: Send text with an iMessage effect (`text` or `message`, `effect` or `effectId`).
- **edit**: Edit a sent message on supported macOS/private API versions (`messageId`, `text` or `newText`).
- **unsend**: Retract a sent message on supported macOS/private API versions (`messageId`).
- **upload-file**: Send media/files (`buffer` as base64 or a hydrated `media`/`path`/`filePath`, `filename`, optional `asVoice`). Legacy alias: `sendAttachment`.
- **renameGroup**, **setGroupIcon**, **addParticipant**, **removeParticipant**, **leaveGroup**: Manage group chats when the current target is a group conversation.
</Accordion>
<Accordion title="Message IDs">
Inbound iMessage context includes both short `MessageSid` values and full message GUIDs when available. Short IDs are scoped to the recent in-memory reply cache and are checked against the current chat before use. If a short ID has expired or belongs to another chat, retry with the full `MessageSidFull`.
</Accordion>
<Accordion title="Capability detection">
OpenClaw hides private API actions only when the cached probe status says the bridge is unavailable. If the status is unknown, actions remain visible and dispatch probes lazily so the first action can succeed after `imsg launch` without a separate manual status refresh.
</Accordion>
<Accordion title="Read receipts and typing">
When the private API bridge is up, accepted inbound chats are marked read before dispatch and a typing bubble is shown to the sender while the agent generates. Disable read-marking with:
```json5
{
channels: {
imessage: {
sendReadReceipts: false,
},
},
}
```
Older `imsg` builds that pre-date the per-method capability list will gate off typing/read silently; OpenClaw logs a one-time warning per restart so the missing receipt is attributable.
</Accordion>
</AccordionGroup>
@@ -524,98 +355,18 @@ Disable:
}
```
<a id="coalescing-split-send-dms-command--url-in-one-composition"></a>
## Coalescing split-send DMs (command + URL in one composition)
When a user types a command and a URL together — e.g. `Dump https://example.com/article` — Apple's Messages app splits the send into **two separate `chat.db` rows**:
1. A text message (`"Dump"`).
2. A URL-preview balloon (`"https://..."`) with OG-preview images as attachments.
The two rows arrive at OpenClaw ~0.8-2.0 s apart on most setups. Without coalescing, the agent receives the command alone on turn 1, replies (often "send me the URL"), and only sees the URL on turn 2 — at which point the command context is already lost. This is Apple's send pipeline, not anything OpenClaw or `imsg` introduces.
`channels.imessage.coalesceSameSenderDms` opts a DM into merging consecutive same-sender rows into a single agent turn. Group chats continue to dispatch per-message so multi-user turn structure is preserved.
<Tabs>
<Tab title="When to enable">
Enable when:
- You ship skills that expect `command + payload` in one message (dump, paste, save, queue, etc.).
- Your users paste URLs, images, or long content alongside commands.
- You can accept the added DM turn latency (see below).
Leave disabled when:
- You need minimum command latency for single-word DM triggers.
- All your flows are one-shot commands without payload follow-ups.
</Tab>
<Tab title="Enabling">
```json5
{
channels: {
imessage: {
coalesceSameSenderDms: true, // opt in (default: false)
},
},
}
```
With the flag on and no explicit `messages.inbound.byChannel.imessage`, the debounce window widens to **2500 ms** (the legacy default is 0 ms — no debouncing). The wider window is required because Apple's split-send cadence of 0.8-2.0 s does not fit in a tighter default.
To tune the window yourself:
```json5
{
messages: {
inbound: {
byChannel: {
// 2500 ms works for most setups; raise to 4000 ms if your Mac is
// slow or under memory pressure (observed gap can stretch past 2 s
// then).
imessage: 2500,
},
},
},
}
```
</Tab>
<Tab title="Trade-offs">
- **Added latency for DM messages.** With the flag on, every DM (including standalone control commands and single-text follow-ups) waits up to the debounce window before dispatching, in case a payload row is coming. Group-chat messages keep instant dispatch.
- **Merged output is bounded.** Merged text caps at 4000 chars with an explicit `…[truncated]` marker; attachments cap at 20; source entries cap at 10 (first-plus-latest retained beyond that). Every source GUID is tracked in `coalescedMessageGuids` for downstream telemetry.
- **DM-only.** Group chats fall through to per-message dispatch so the bot stays responsive when multiple people are typing.
- **Opt-in, per-channel.** Other channels (Telegram, WhatsApp, Slack, …) are unaffected. Legacy BlueBubbles configs that set `channels.bluebubbles.coalesceSameSenderDms` should migrate that value to `channels.imessage.coalesceSameSenderDms`.
</Tab>
</Tabs>
### Scenarios and what the agent sees
| User composes | `chat.db` produces | Flag off (default) | Flag on + 2500 ms window |
| ------------------------------------------------------------------ | --------------------- | --------------------------------------- | ----------------------------------------------------------------------- |
| `Dump https://example.com` (one send) | 2 rows ~1 s apart | Two agent turns: "Dump" alone, then URL | One turn: merged text `Dump https://example.com` |
| `Save this 📎image.jpg caption` (attachment + text) | 2 rows | Two turns (attachment dropped on merge) | One turn: text + image preserved |
| `/status` (standalone command) | 1 row | Instant dispatch | **Wait up to window, then dispatch** |
| URL pasted alone | 1 row | Instant dispatch | Instant dispatch (only one entry in bucket) |
| Text + URL sent as two deliberate separate messages, minutes apart | 2 rows outside window | Two turns | Two turns (window expires between them) |
| Rapid flood (>10 small DMs inside window) | N rows | N turns | One turn, bounded output (first + latest, text/attachment caps applied) |
| Two people typing in a group chat | N rows from M senders | M+ turns (one per sender bucket) | M+ turns — group chats are not coalesced |
## Troubleshooting
<AccordionGroup>
<Accordion title="imsg not found or RPC unsupported">
Validate the binary and RPC support:
```bash
imsg rpc --help
imsg status --json
openclaw channels status --probe
```
```bash
imsg rpc --help
openclaw channels status --probe
```
If probe reports RPC unsupported, update `imsg`. If private API actions are unavailable, run `imsg launch` in the logged-in macOS user session and probe again. If the Gateway is not running on macOS, use the Remote Mac over SSH setup above instead of the default local `imsg` path.
If probe reports RPC unsupported, update `imsg`. If the Gateway is not running on macOS, use the Remote Mac over SSH setup above instead of the default local `imsg` path.
</Accordion>
@@ -668,10 +419,10 @@ openclaw channels status --probe --channel imessage
<Accordion title="macOS permission prompts were missed">
Re-run in an interactive GUI terminal in the same user/session context and approve prompts:
```bash
imsg chats --limit 1
imsg send <handle> "test"
```
```bash
imsg chats --limit 1
imsg send <handle> "test"
```
Confirm Full Disk Access + Automation are granted for the process context that runs OpenClaw/`imsg`.
@@ -687,7 +438,6 @@ openclaw channels status --probe --channel imessage
## Related
- [Channels Overview](/channels) — all supported channels
- [Coming from BlueBubbles](/channels/imessage-from-bluebubbles) — config translation table and step-by-step cutover
- [Pairing](/channels/pairing) — DM authentication and pairing flow
- [Groups](/channels/groups) — group chat behavior and mention gating
- [Channel Routing](/channels/channel-routing) — session routing for messages

View File

@@ -24,7 +24,7 @@ Text is supported everywhere; media and reactions vary by channel.
- [Discord](/channels/discord) - Discord Bot API + Gateway; supports servers, channels, and DMs.
- [Feishu](/channels/feishu) - Feishu/Lark bot via WebSocket (bundled plugin).
- [Google Chat](/channels/googlechat) - Google Chat API app via HTTP webhook (downloadable plugin).
- [iMessage](/channels/imessage) - Native macOS integration via the `imsg` bridge on a signed-in Mac (or SSH wrapper when the Gateway runs elsewhere), including private API actions for replies, tapbacks, effects, attachments, and group management. Preferred for new OpenClaw iMessage setups when host permissions and Messages access fit.
- [iMessage](/channels/imessage) - Native macOS integration via the `imsg` CLI on a signed-in Mac; use an SSH wrapper when the Gateway runs elsewhere.
- [IRC](/channels/irc) - Classic IRC servers; channels + DMs with pairing/allowlist controls.
- [LINE](/channels/line) - LINE Messaging API bot (downloadable plugin).
- [Matrix](/channels/matrix) - Matrix protocol (downloadable plugin).

View File

@@ -930,7 +930,6 @@ Current Slack message actions include `send`, `upload-file`, `download-file`, `r
- With default `session.dmScope=main`, Slack DMs collapse to agent main session.
- Channel sessions: `agent:<agentId>:slack:channel:<channelId>`.
- Thread replies can create thread session suffixes (`:thread:<threadTs>`) when applicable.
- In channels where OpenClaw handles top-level messages without requiring an explicit mention, non-`off` `replyToMode` routes each handled root into `agent:<agentId>:slack:channel:<channelId>:thread:<rootTs>` so the visible Slack thread maps to one OpenClaw session from the first turn.
- `channels.slack.thread.historyScope` default is `thread`; `thread.inheritParent` default is `false`.
- `channels.slack.thread.initialHistoryLimit` controls how many existing thread messages are fetched when a new thread session starts (default `20`; set `0` to disable).
- `channels.slack.thread.requireExplicitMention` (default `false`): when `true`, suppress implicit thread mentions so the bot only responds to explicit `@bot` mentions inside threads, even when the bot already participated in the thread. Without this, replies in a bot-participated thread bypass `requireMention` gating.

View File

@@ -258,7 +258,7 @@ curl "https://api.telegram.org/bot<bot_token>/getUpdates"
- Telegram is owned by the gateway process.
- Routing is deterministic: Telegram inbound replies back to Telegram (the model does not pick channels).
- Inbound messages normalize into the shared channel envelope with reply metadata, media placeholders, and persisted reply-chain context for Telegram replies the gateway has observed.
- Inbound messages normalize into the shared channel envelope with reply metadata and media placeholders.
- Group sessions are isolated by group ID. Forum topics append `:topic:<threadId>` to keep topics isolated.
- DM messages can carry `message_thread_id`; OpenClaw preserves the thread ID for replies but keeps DMs on the flat session by default. Configure `channels.telegram.dm.threadReplies: "inbound"`, `channels.telegram.direct.<chatId>.threadReplies: "inbound"`, `requireTopic: true`, or a matching topic config when you intentionally want DM topic session isolation.
- Long polling uses grammY runner with per-chat/per-thread sequencing. Overall runner sink concurrency uses `agents.defaults.maxConcurrent`.
@@ -773,7 +773,7 @@ curl "https://api.telegram.org/bot<bot_token>/getUpdates"
- `channels.telegram.timeoutSeconds` overrides Telegram API client timeout (if unset, grammY default applies). Bot clients clamp configured values below the 60-second outbound text/typing request guard so grammY does not abort visible reply delivery before OpenClaw's transport guard and fallback can run. Long polling still uses a 45-second `getUpdates` request guard so idle polls are not abandoned indefinitely.
- `channels.telegram.pollingStallThresholdMs` defaults to `120000`; tune between `30000` and `600000` only for false-positive polling-stall restarts.
- group context history uses `channels.telegram.historyLimit` or `messages.groupChat.historyLimit` (default 50); `0` disables.
- reply/quote/forward supplemental context is normalized into a nearest-first reply chain when the gateway has observed the parent messages; the observed-message cache is persisted beside the session store. Telegram only includes one shallow `reply_to_message` in updates, so chains older than the cache are limited to Telegram's current update payload.
- reply/quote/forward supplemental context is currently passed as received.
- Telegram allowlists primarily gate who can trigger the agent, not a full supplemental-context redaction boundary.
- DM history controls:
- `channels.telegram.dmHistoryLimit`

View File

@@ -1,96 +0,0 @@
---
summary: "How ClawHub publishing works for skills, plugins, owners, scopes, releases, and review."
read_when:
- Publishing a skill or plugin
- Debugging owner or package scope errors
- Adding publish UI, CLI, or backend behavior
---
# Publishing on ClawHub
ClawHub publishing is owner-scoped: every publish targets a publisher, and the
server decides whether the signed-in user is allowed to publish there.
## Owners
An owner is a ClawHub publisher handle, such as `@alice` or `@openclaw`.
Personal owners are created for users. Org owners can have multiple members.
When you publish, you either use your personal owner or choose an org owner
where you have publisher access.
## Skills
Skills are published from a skill folder. The public page is:
```text
https://clawhub.ai/<owner>/<slug>
```
Example:
```text
https://clawhub.ai/alice/review-helper
```
The publish request includes the selected owner, slug, version, changelog, and
files. The server verifies that the actor can publish as that owner before it
creates the release.
## Plugins
Plugins use npm-style package names. Scoped package names include the owner in
the first part of the name:
```text
@owner/package-name
```
The scope must match the selected publish owner. If your package is named
`@openclaw/dronzer`, it can only be published as `@openclaw`. If you publish as
`@vintageayu`, rename the package to `@vintageayu/dronzer`.
This prevents a package from claiming an org namespace that the publisher does
not control.
## Release Flow
1. The UI, CLI, or GitHub workflow gathers package metadata and files.
2. The publish request is sent to ClawHub with the selected owner.
3. The server validates owner permissions, package scope, package name, version,
file limits, and source metadata.
4. ClawHub stores the release and starts automated security checks.
5. New releases are hidden from normal install/download surfaces until review
and verification finish.
If validation fails, the release is not created.
## FAQ
### Package scope must match selected owner
If the package scope and selected owner do not match, ClawHub rejects the
publish:
```text
Package scope "@openclaw" must match selected owner "@vintageayu".
Publish as "@openclaw" or rename this package to "@vintageayu/dronzer".
```
To fix it, either choose the owner named by the package scope, or rename the
package so the scope matches the owner you can publish as.
If the package name already has the right scope but the package is owned by the
wrong publisher, transfer ownership instead:
```sh
clawhub package transfer @opik/opik-openclaw --to opik
```
Use package transfer only when you have admin access to both the current package
owner and the destination publisher. It does not let you publish into a scope you
cannot manage.
This protects org namespaces. A package named `@openclaw/dronzer` claims the
`@openclaw` namespace, so only publishers with access to the `@openclaw` owner
can publish it.

View File

@@ -44,7 +44,7 @@ Quick rule:
| `initialize`, `newSession`, `prompt`, `cancel` | Implemented | Core bridge flow over stdio to Gateway chat/send + abort. |
| `listSessions`, slash commands | Implemented | Session list works against Gateway session state with bounded cursor pagination and `cwd` filtering where Gateway session rows carry workspace metadata; commands are advertised via `available_commands_update`. |
| `resumeSession`, `closeSession` | Implemented | Resume rebinds an ACP session to an existing Gateway session without replaying history. Close cancels active bridge work, resolves pending prompts as cancelled, and releases bridge session state. |
| `loadSession` | Partial | Rebinds the ACP session to a Gateway session key and replays ACP event-ledger history for bridge-created sessions. Older/no-ledger sessions fall back to stored user/assistant text. |
| `loadSession` | Partial | Rebinds the ACP session to a Gateway session key and replays stored user/assistant text history. Tool/system history is not reconstructed yet. |
| Prompt content (`text`, embedded `resource`, images) | Partial | Text/resources are flattened into chat input; images become Gateway attachments. |
| Session modes | Partial | `session/set_mode` is supported and the bridge exposes initial Gateway-backed session controls for thought level, tool verbosity, reasoning, usage detail, and elevated actions. Broader ACP-native mode/config surfaces are still out of scope. |
| Session info and usage updates | Partial | The bridge emits `session_info_update` and best-effort `usage_update` notifications from cached Gateway session snapshots. Usage is approximate and only sent when Gateway token totals are marked fresh. |
@@ -56,9 +56,9 @@ Quick rule:
## Known Limitations
- `loadSession` can replay complete ACP event-ledger history only for
bridge-created sessions. Older/no-ledger sessions still use transcript
fallback and do not reconstruct historic tool calls or system notices.
- `loadSession` replays stored user and assistant text history, but it does not
reconstruct historic tool calls, system notices, or richer ACP-native event
types.
- If multiple ACP clients share the same Gateway session key, event and cancel
routing are best-effort rather than strictly isolated per client. Prefer the
default isolated `acp:<uuid>` sessions when you need clean editor-local

View File

@@ -170,7 +170,7 @@ configured OpenClaw model. If no configured model is usable yet, it can fall
back to local runtimes already present on the machine:
- Claude Code CLI: `claude-cli/claude-opus-4-7`
- Codex app-server harness: `openai/gpt-5.5`
- Codex app-server harness: `openai/gpt-5.5` with `agentRuntime.id: "codex"`
- Codex CLI: `codex-cli/gpt-5.5`
The model-assisted planner cannot mutate config directly. It must translate the

View File

@@ -56,7 +56,7 @@ Notes:
- Doctor also scans `~/.openclaw/cron/jobs.json` (or `cron.store`) for legacy cron job shapes and can rewrite them in place before the scheduler has to auto-normalize them at runtime.
- On Linux, doctor warns when the user's crontab still runs legacy `~/.openclaw/bin/ensure-whatsapp.sh`; that script is no longer maintained and can log false WhatsApp gateway outages when cron lacks the systemd user-bus environment.
- When WhatsApp is enabled, doctor checks for a degraded Gateway event loop with local `openclaw-tui` clients still running. `doctor --fix` stops only verified local TUI clients so WhatsApp replies are not queued behind stale TUI refresh loops.
- Doctor rewrites legacy `openai-codex/*` model refs to canonical `openai/*` refs across primary models, fallbacks, heartbeat/subagent/compaction overrides, hooks, channel model overrides, and stale session route pins. `--fix` preserves explicit provider/model `agentRuntime` policy, removes stale whole-agent/session runtime pins, and leaves canonical OpenAI agent refs on the default Codex harness when the official OpenAI provider is in use.
- Doctor rewrites legacy `openai-codex/*` model refs to canonical `openai/*` refs across primary models, fallbacks, heartbeat/subagent/compaction overrides, hooks, channel model overrides, and stale session route pins. `--fix` selects `agentRuntime.id: "codex"` only when the Codex plugin is installed, enabled, contributes the `codex` harness, and has usable OAuth; otherwise it selects `agentRuntime.id: "pi"` so the route stays on the default OpenClaw runner.
- Doctor cleans legacy plugin dependency staging state created by older OpenClaw versions. It also repairs missing downloadable plugins that are referenced by config, such as `plugins.entries`, configured channels, configured provider/search settings, or configured agent runtimes. During package updates, doctor skips package-manager plugin repair until the package swap is complete; rerun `openclaw doctor --fix` afterward if a configured plugin still needs recovery. If the download fails, doctor reports the install error and preserves the configured plugin entry for the next repair attempt.
- Doctor repairs stale plugin config by removing missing plugin ids from `plugins.allow`/`plugins.entries`, plus matching dangling channel config, heartbeat targets, and channel model overrides when plugin discovery is healthy.
- Doctor quarantines invalid plugin config by disabling the affected `plugins.entries.<id>` entry and removing its invalid `config` payload. Gateway startup already skips only that bad plugin so other plugins and channels can keep running.

View File

@@ -483,13 +483,11 @@ openclaw gateway restart
- `gateway status`: `--url`, `--token`, `--password`, `--timeout`, `--no-probe`, `--require-rpc`, `--deep`, `--json`
- `gateway install`: `--port`, `--runtime <node|bun>`, `--token`, `--wrapper <path>`, `--force`, `--json`
- `gateway restart`: `--safe`, `--force`, `--wait <duration>`, `--json`
- `gateway uninstall|start`: `--json`
- `gateway stop`: `--disable`, `--json`
- `gateway uninstall|start|stop`: `--json`
</Accordion>
<Accordion title="Lifecycle behavior">
- Use `gateway restart` to restart a managed service. Do not chain `gateway stop` and `gateway start` as a restart substitute.
- On macOS, `gateway stop` uses `launchctl bootout` by default, which removes the LaunchAgent from the current boot session without persisting a disable — KeepAlive auto-recovery remains active for future crashes and `gateway start` re-enables cleanly without a manual `launchctl enable`. Pass `--disable` to persistently suppress KeepAlive and RunAtLoad so the gateway does not respawn until the next explicit `gateway start`; use this when a manual stop should survive reboots or system restarts.
- Use `gateway restart` to restart a managed service. Do not chain `gateway stop` and `gateway start` as a restart substitute; on macOS, `gateway stop` intentionally disables the LaunchAgent before stopping it.
- `gateway restart --safe` asks the running Gateway to preflight active OpenClaw work and defer the restart until reply delivery, embedded runs, and task runs drain. `--safe` cannot be combined with `--force` or `--wait`.
- `gateway restart --wait 30s` overrides the configured restart drain budget for that restart. Bare numbers are milliseconds; units such as `s`, `m`, and `h` are accepted. `--wait 0` waits indefinitely.
- `gateway restart --force` skips the active-work drain and restarts immediately. Use it when an operator has already inspected the listed task blockers and wants the gateway back now.

View File

@@ -24,7 +24,7 @@ apply across the CLI.
| Network and nodes | [`directory`](/cli/directory) · [`nodes`](/cli/nodes) · [`devices`](/cli/devices) · [`node`](/cli/node) |
| Runtime and sandbox | [`approvals`](/cli/approvals) · `exec-policy` (see [`approvals`](/cli/approvals)) · [`sandbox`](/cli/sandbox) · [`tui`](/cli/tui) · `chat`/`terminal` (aliases for [`tui --local`](/cli/tui)) · [`browser`](/cli/browser) |
| Automation | [`cron`](/cli/cron) · [`tasks`](/cli/tasks) · [`hooks`](/cli/hooks) · [`webhooks`](/cli/webhooks) |
| Discovery and docs | [`dns`](/cli/dns) · [`docs`](/cli/docs) · [`path`](/cli/path) |
| Discovery and docs | [`dns`](/cli/dns) · [`docs`](/cli/docs) |
| Pairing and channels | [`pairing`](/cli/pairing) · [`qr`](/cli/qr) · [`channels`](/cli/channels) |
| Security and plugins | [`security`](/cli/security) · [`secrets`](/cli/secrets) · [`skills`](/cli/skills) · [`plugins`](/cli/plugins) · [`proxy`](/cli/proxy) |
| Legacy aliases | [`daemon`](/cli/daemon) (gateway service) · [`clawbot`](/cli/clawbot) (namespace) |

View File

@@ -21,11 +21,9 @@ openclaw migrate list
openclaw migrate claude --dry-run
openclaw migrate codex --dry-run
openclaw migrate codex --skill gog-vault77-google-workspace
openclaw migrate codex --plugin google-calendar --dry-run
openclaw migrate hermes --dry-run
openclaw migrate hermes
openclaw migrate apply codex --yes --skill gog-vault77-google-workspace
openclaw migrate apply codex --yes --plugin google-calendar
openclaw migrate apply codex --yes
openclaw migrate apply claude --yes
openclaw migrate apply hermes --yes
@@ -56,9 +54,6 @@ openclaw onboard --import-from hermes --import-source ~/.hermes
<ParamField path="--skill <name>" type="string">
Select one skill copy item by skill name or item id. Repeat the flag to migrate multiple skills. When omitted, interactive Codex migrations show a checkbox selector and non-interactive migrations keep all planned skills.
</ParamField>
<ParamField path="--plugin <name>" type="string">
Select one Codex plugin install item by plugin name or item id. Repeat the flag to migrate multiple Codex plugins. This only applies to source-installed `openai-curated` Codex plugins discovered by the Codex app-server inventory.
</ParamField>
<ParamField path="--no-backup" type="boolean">
Skip the pre-apply backup. Requires `--force` when local OpenClaw state exists.
</ParamField>
@@ -134,51 +129,20 @@ openclaw migrate codex --dry-run --skill gog-vault77-google-workspace
openclaw migrate apply codex --yes --skill gog-vault77-google-workspace
```
Use `--plugin <name>` to limit native Codex plugin migration to one or more
source-installed curated plugins:
```bash
openclaw migrate codex --dry-run --plugin google-calendar
openclaw migrate apply codex --yes --plugin google-calendar
```
### What Codex imports
- Codex CLI skill directories under `$CODEX_HOME/skills`, excluding Codex's
`.system` cache.
- Personal AgentSkills under `$HOME/.agents/skills`, copied into the current
OpenClaw agent workspace when you want per-agent ownership.
- Source-installed `openai-curated` Codex plugins discovered through Codex
app-server `plugin/list`. Apply calls app-server `plugin/install` for each
selected plugin, even if the target app-server already reports that plugin as
installed and enabled. Migrated Codex plugins are usable only in sessions that
select the native Codex harness; they are not exposed to Pi, normal OpenAI
provider runs, ACP conversation bindings, or other harnesses.
### Manual-review Codex state
Codex `config.toml`, native `hooks/hooks.json`, non-curated marketplaces, and
cached plugin bundles that are not source-installed curated plugins are not
activated automatically. They are copied or reported in the migration report for
manual review.
For migrated source-installed curated plugins, apply writes:
- `plugins.entries.codex.enabled: true`
- `plugins.entries.codex.config.codexPlugins.enabled: true`
- `plugins.entries.codex.config.codexPlugins.allow_destructive_actions: false`
- one explicit plugin entry with `marketplaceName: "openai-curated"` and
`pluginName` for each selected plugin
Migration never writes `plugins["*"]` and never stores local marketplace cache
paths. Auth-required installs are reported on the affected plugin item with
`status: "skipped"`, `reason: "auth_required"`, and sanitized app identifiers.
Their explicit config entries are written disabled until you reauthorize and
enable them. Other install failures are item-scoped `error` results.
If Codex app-server plugin inventory is unavailable during planning, migration
falls back to cached bundle advisory items instead of failing the whole
migration.
Codex native plugins, `config.toml`, and native `hooks/hooks.json` are not
activated automatically. Plugins may expose MCP servers, apps, hooks, or other
executable behavior, so the provider reports them for review instead of loading
them into OpenClaw. Config and hook files are copied into the migration report
for manual review.
## Hermes provider

View File

@@ -1,121 +0,0 @@
---
summary: "CLI reference for `openclaw path` (inspect and edit workspace files via the `oc://` addressing scheme)"
read_when:
- You want to read or write a leaf inside a workspace file from the terminal
- You're scripting against workspace state and want a stable, kind-agnostic addressing scheme
- You're debugging a `oc://` path (validate the syntax, see what it resolves to)
title: "Path"
---
# `openclaw path`
Shell-level access to the `oc://` addressing substrate — one universal,
kind-dispatched path scheme for inspecting and surgically editing workspace
files (markdown, jsonc, jsonl, yaml). Self-hosters and editor extensions use
it to read or write a single leaf inside a workspace file without scripting
against the SDK directly.
## Subcommands
| Subcommand | Purpose |
| ----------------------- | ---------------------------------------------------------------------------- |
| `resolve <oc-path>` | Print the match at the path (or "not found"). |
| `find <pattern>` | Enumerate matches for a wildcard / predicate path. |
| `set <oc-path> <value>` | Write a leaf at the path. Supports `--dry-run`. |
| `validate <oc-path>` | Parse-only — print structural breakdown (file / section / item / field). |
| `emit <file>` | Round-trip a file through `parseXxx` + `emitXxx` (byte-fidelity diagnostic). |
## Global flags
| Flag | Purpose |
| --------------- | ------------------------------------------------------------------------ |
| `--cwd <dir>` | Resolve the file slot against this directory (default: `process.cwd()`). |
| `--file <path>` | Override the file slot's resolved path (absolute access). |
| `--json` | Force JSON output (default when stdout is not a TTY). |
| `--human` | Force human output (default when stdout is a TTY). |
| `--dry-run` | (only on `set`) print the bytes that would be written without writing. |
## `oc://` syntax
```
oc://FILE/SECTION/ITEM/FIELD?session=SCOPE
```
Slot rules — `field` requires `item`, `item` requires `section`. Across all
four slots:
- **Quoted segments** — `"a/b.c"` survives `/` and `.` separators.
`"\\"` and `"\""` are the only escapes inside quotes.
The file slot is also quote-aware: `oc://"skills/email-drafter"/Tools/-1`
treats `skills/email-drafter` as a single file path.
- **Predicates** — `[k=v]`, `[k!=v]`, `[k*=v]`, `[k^=v]`, `[k$=v]`,
`[k<v]`, `[k<=v]`, `[k>v]`, `[k>=v]`.
- **Unions** — `{a,b,c}` matches any of the alternatives.
- **Wildcards** — `*` (single sub-segment) and `**` (zero-or-more,
recursive). `find` accepts these; `resolve` and `set` reject them as
ambiguous.
- **Positional** — `$first`, `$last`, `-N` (Nth from end).
- **Ordinal** — `#N` for Nth match.
- **Insertion markers** — `+`, `+key`, `+nnn` for keyed / indexed
insertion (use with `set`).
- **Session scope** — `?session=cron:daily` etc. Orthogonal to slot
nesting.
Reserved characters (`?`, `&`, `%`) outside quoted, predicate, or union
segments are rejected. Control characters (U+0000U+001F, U+007F) are
rejected anywhere.
## Examples
```bash
# Validate a path (no filesystem access)
openclaw path validate 'oc://AGENTS.md/Tools/-1/risk'
# Read a leaf
openclaw path resolve 'oc://gateway.jsonc/version'
# Wildcard search
openclaw path find 'oc://session.jsonl/*/event' --file ./logs/session.jsonl
# Dry-run a write
openclaw path set 'oc://gateway.jsonc/version' '2.0' --dry-run
# Apply the write
openclaw path set 'oc://gateway.jsonc/version' '2.0'
# Byte-fidelity round-trip (diagnostic)
openclaw path emit ./AGENTS.md
```
## Exit codes
| Code | Meaning |
| ---- | -------------------------------------------------------------------------- |
| `0` | Success. (`resolve` / `find`: at least one match. `set`: write succeeded.) |
| `1` | No match, or `set` rejected by the substrate (no system-level error). |
| `2` | Argument or parse error. |
## Output mode
`openclaw path` is TTY-aware: human-readable output on a terminal, JSON when
stdout is piped or redirected. `--json` and `--human` override the
auto-detection.
## Notes
- `set` writes raw bytes through the substrate's emit path, which applies the
redaction-sentinel guard automatically. A leaf carrying
`__OPENCLAW_REDACTED__` (verbatim or as a substring) is refused at write
time.
- `set` on a JSONC file currently re-renders the file (drops comments and
trailing-comma formatting) when it mutates a leaf. Read-path round-trip is
byte-identical. A byte-splice editor that preserves comments through
writes is planned as a follow-up.
- `path` does not know about LKG. If the file is LKG-tracked, the next
observe call decides whether to promote / recover. `set --batch` for
atomic multi-set through the LKG promote/recover lifecycle is planned
alongside the LKG-recovery substrate.
## Related
- [CLI reference](/cli)

View File

@@ -129,7 +129,7 @@ is available, then fall back to `latest`.
This CLI flag applies to plugin install/update flows. Gateway-backed skill dependency installs use the matching `dangerouslyForceUnsafeInstall` request override, while `openclaw skills install` remains a separate ClawHub skill download/install flow.
If a plugin you published on ClawHub is blocked by a registry scan, use the publisher steps in [ClawHub](/clawhub/security).
If a plugin you published on ClawHub is blocked by a registry scan, use the publisher steps in [ClawHub](/tools/clawhub).
</Accordion>
<Accordion title="Hook packs and npm specs">
@@ -417,4 +417,4 @@ Marketplace list accepts a local marketplace path, a `marketplace.json` path, a
- [Building plugins](/plugins/building-plugins)
- [CLI reference](/cli)
- [ClawHub](/clawhub)
- [Community plugins](/plugins/community)

View File

@@ -33,7 +33,7 @@ It also emits `security.trust_model.multi_user_heuristic` when config suggests l
For intentional shared-user setups, the audit guidance is to sandbox all sessions, keep filesystem access workspace-scoped, and keep personal/private identities or credentials off that runtime.
It also warns when small models (`<=300B`) are used without sandboxing and with web/browser tools enabled.
For webhook ingress, it warns when `hooks.token` reuses the Gateway token, when `hooks.token` is short, when `hooks.path="/"`, when `hooks.defaultSessionKey` is unset, when `hooks.allowedAgentIds` is unrestricted, when request `sessionKey` overrides are enabled, and when overrides are enabled without `hooks.allowedSessionKeyPrefixes`.
It also warns when sandbox Docker settings are configured while sandbox mode is off, when `gateway.nodes.denyCommands` uses ineffective pattern-like/unknown entries (exact node command-name matching only, not shell-text filtering), when `gateway.nodes.allowCommands` explicitly enables dangerous node commands, when global `tools.profile="minimal"` is overridden by agent tool profiles, when write/edit tools are disabled but `exec` is still available without a constraining sandbox filesystem boundary, when open groups expose runtime/filesystem tools without sandbox/workspace guards, and when installed plugin tools may be reachable under permissive tool policy.
It also warns when sandbox Docker settings are configured while sandbox mode is off, when `gateway.nodes.denyCommands` uses ineffective pattern-like/unknown entries (exact node command-name matching only, not shell-text filtering), when `gateway.nodes.allowCommands` explicitly enables dangerous node commands, when global `tools.profile="minimal"` is overridden by agent tool profiles, when open groups expose runtime/filesystem tools without sandbox/workspace guards, and when installed plugin tools may be reachable under permissive tool policy.
It also flags `gateway.allowRealIpFallback=true` (header-spoofing risk if proxies are misconfigured) and `discovery.mdns.mode="full"` (metadata leakage via mDNS TXT records).
It also warns when sandbox browser uses Docker `bridge` network without `sandbox.browser.cdpSourceRange`.
It also flags dangerous sandbox Docker network modes (including `host` and `container:*` namespace joins).

View File

@@ -15,7 +15,7 @@ Related:
- Skills system: [Skills](/tools/skills)
- Skills config: [Skills config](/tools/skills-config)
- ClawHub installs: [ClawHub](/clawhub/cli)
- ClawHub installs: [ClawHub](/tools/clawhub)
## Commands

View File

@@ -23,11 +23,8 @@ configuration. They are different layers:
You will also see the word **harness** in code. A harness is the implementation
that provides an agent runtime. For example, the bundled Codex harness
implements the `codex` runtime. Public config uses `agentRuntime.id` on
provider or model entries; whole-agent runtime keys are legacy and ignored.
`openclaw doctor --fix` removes old whole-agent runtime pins and rewrites
legacy runtime model refs to canonical provider/model refs plus model-scoped
runtime policy where needed.
implements the `codex` runtime. Public config uses `agentRuntime.id`; `openclaw
doctor --fix` rewrites older runtime-policy keys to that shape.
There are two runtime families:
@@ -36,9 +33,9 @@ There are two runtime families:
`codex`.
- **CLI backends** run a local CLI process while keeping the model ref
canonical. For example, `anthropic/claude-opus-4-7` with
a model-scoped `agentRuntime.id: "claude-cli"` means "select the Anthropic
model, execute through Claude CLI." `claude-cli` is not an embedded harness id
and must not be passed to AgentHarness selection.
`agentRuntime.id: "claude-cli"` means "select the Anthropic model, execute
through Claude CLI." `claude-cli` is not an embedded harness id and must not
be passed to AgentHarness selection.
## Codex surfaces
@@ -90,9 +87,9 @@ This is the agent-facing decision tree:
2. If the user asks for **Codex as the embedded runtime** or wants the normal
subscription-backed Codex agent experience, use `openai/<model>`.
3. If the user explicitly chooses **PI for an OpenAI model**, keep the model ref
as `openai/<model>` and set provider/model runtime policy to
`agentRuntime.id: "pi"`. A selected `openai-codex` auth profile is routed
internally through PI's legacy Codex-auth transport.
as `openai/<model>` and set `agentRuntime.id: "pi"`. A selected
`openai-codex` auth profile is routed internally through PI's legacy
Codex-auth transport.
4. If legacy config still contains **`openai-codex/*` model refs**, repair it to
`openai/<model>` with `openclaw doctor --fix`.
5. If the user explicitly says **ACP**, **acpx**, or **Codex ACP adapter**, use
@@ -135,26 +132,21 @@ This ownership split is the main design rule:
OpenClaw chooses an embedded runtime after provider and model resolution:
1. Model-scoped runtime policy wins. This can live in a configured provider
model entry or in `agents.defaults.models["provider/model"].agentRuntime` /
`agents.list[].models["provider/model"].agentRuntime`.
2. Provider-scoped runtime policy comes next at
`models.providers.<provider>.agentRuntime`.
3. In `auto` mode, registered plugin runtimes can claim supported provider/model
1. A session's recorded runtime wins. Config changes do not hot-switch an
existing transcript to a different native thread system.
2. `OPENCLAW_AGENT_RUNTIME=<id>` forces that runtime for new or reset sessions.
3. `agents.defaults.agentRuntime.id` or `agents.list[].agentRuntime.id` can set
`auto`, `pi`, a registered embedded harness id such as `codex`, or a
supported CLI backend alias such as `claude-cli`.
4. In `auto` mode, registered plugin runtimes can claim supported provider/model
pairs.
4. If no runtime claims a turn in `auto` mode, OpenClaw uses PI as the
5. If no runtime claims a turn in `auto` mode, OpenClaw uses PI as the
compatibility runtime. Use an explicit runtime id when the run must be
strict.
Whole-session and whole-agent runtime pins are ignored. That includes
`OPENCLAW_AGENT_RUNTIME`, session `agentHarnessId`/`agentRuntimeOverride` state,
`agents.defaults.agentRuntime`, and `agents.list[].agentRuntime`. Run
`openclaw doctor --fix` to remove stale whole-agent runtime config and convert
legacy runtime model refs where OpenClaw can preserve the intent.
Explicit provider/model plugin runtimes fail closed. For example,
`agentRuntime.id: "codex"` on a provider or model means Codex or a clear
selection/runtime error; it is never silently routed back to PI.
Explicit plugin runtimes fail closed. For example, `agentRuntime.id: "codex"`
means Codex or a clear selection/runtime error; it is never silently routed back
to PI.
CLI backend aliases are different from embedded harness ids. The preferred
Claude CLI form is:
@@ -164,11 +156,7 @@ Claude CLI form is:
agents: {
defaults: {
model: "anthropic/claude-opus-4-7",
models: {
"anthropic/claude-opus-4-7": {
agentRuntime: { id: "claude-cli" },
},
},
agentRuntime: { id: "claude-cli" },
},
},
}
@@ -176,15 +164,15 @@ Claude CLI form is:
Legacy refs such as `claude-cli/claude-opus-4-7` remain supported for
compatibility, but new config should keep the provider/model canonical and put
the execution backend in provider/model runtime policy.
the execution backend in `agentRuntime.id`.
`auto` mode is intentionally conservative for most providers. OpenAI agent
models are the exception: unset runtime and `auto` both resolve to the Codex
harness. Explicit PI runtime config remains an opt-in compatibility route for
`openai/*` agent turns; when paired with a selected `openai-codex` auth profile,
OpenClaw routes PI internally through the legacy Codex-auth transport while
keeping the public model ref as `openai/*`. Stale OpenAI PI session pins are
ignored by runtime selection and can be cleaned with `openclaw doctor --fix`.
keeping the public model ref as `openai/*`. Stale OpenAI PI session pins without
explicit config are repaired back to Codex.
If `openclaw doctor` warns that the `codex` plugin is enabled while
`openai-codex/*` remains in config, treat that as legacy route state. Run
@@ -218,8 +206,10 @@ diagnostics, not as provider names.
- A runtime id such as `codex` tells you which loop is executing the turn.
- A channel label such as Telegram or Discord tells you where the conversation is happening.
If a run still shows an unexpected runtime, inspect the selected provider/model
runtime policy first. Legacy session runtime pins no longer decide routing.
If a session still shows PI after changing runtime config, start a new session
with `/new` or clear the current one with `/reset`. Existing sessions keep their
recorded runtime so a transcript is not replayed through two incompatible native
session systems.
## Related

View File

@@ -29,19 +29,19 @@ Reference for **LLM/model providers** (not chat channels like WhatsApp/Telegram)
<Accordion title="OpenAI provider/runtime split">
OpenAI-family routes are prefix-specific:
- `openai/<model>` uses the native Codex app-server harness for agent turns by default. This is the usual ChatGPT/Codex subscription setup.
- `openai-codex/<model>` is legacy config that doctor rewrites to `openai/<model>`.
- `openai/<model>` plus provider/model `agentRuntime.id: "pi"` uses PI for explicit API-key or compatibility routes.
- `openai/<model>` plus `agents.defaults.agentRuntime.id: "codex"` uses the native Codex app-server harness. This is the usual ChatGPT/Codex subscription setup.
- `openai-codex/<model>` uses Codex OAuth in PI.
- `openai/<model>` without a Codex runtime override uses the direct OpenAI API-key provider in PI.
See [OpenAI](/providers/openai) and [Codex harness](/plugins/codex-harness). If the provider/runtime split is confusing, read [Agent runtimes](/concepts/agent-runtimes) first.
Plugin auto-enable follows the same boundary: `openai/*` agent refs enable the Codex plugin for the default route, and explicit provider/model `agentRuntime.id: "codex"` or legacy `codex/<model>` refs also require it.
Plugin auto-enable follows the same boundary: `openai-codex/<model>` belongs to the OpenAI plugin, while the Codex plugin is enabled by `agentRuntime.id: "codex"` or legacy `codex/<model>` refs.
GPT-5.5 is available through the native Codex app-server harness by default on `openai/gpt-5.5`, and through PI only when provider/model runtime policy explicitly selects `pi`.
GPT-5.5 is available through the native Codex app-server harness when `agentRuntime.id: "codex"` is set, through `openai-codex/gpt-5.5` in PI for Codex OAuth, and through `openai/gpt-5.5` in PI for direct API-key traffic when your account exposes it.
</Accordion>
<Accordion title="CLI runtimes">
CLI runtimes use the same split: choose canonical model refs such as `anthropic/claude-*`, `google/gemini-*`, or `openai/gpt-*`, then set provider/model runtime policy to `claude-cli`, `google-gemini-cli`, or `codex-cli` when you want a local CLI backend.
CLI runtimes use the same split: choose canonical model refs such as `anthropic/claude-*`, `google/gemini-*`, or `openai/gpt-*`, then set `agents.defaults.agentRuntime.id` to `claude-cli`, `google-gemini-cli`, or `codex-cli` when you want a local CLI backend.
Legacy `claude-cli/*`, `google-gemini-cli/*`, and `codex-cli/*` refs migrate back to canonical provider refs with the runtime recorded separately.
@@ -118,7 +118,7 @@ OpenClaw ships with the pi-ai catalog. These providers require **no** `models.pr
- Direct public Anthropic requests support the shared `/fast` toggle and `params.fastMode`, including API-key and OAuth-authenticated traffic sent to `api.anthropic.com`; OpenClaw maps that to Anthropic `service_tier` (`auto` vs `standard_only`)
- Preferred Claude CLI config keeps the model ref canonical and selects the CLI
backend separately: `anthropic/claude-opus-4-7` with
model-scoped `agentRuntime.id: "claude-cli"`. Legacy
`agents.defaults.agentRuntime.id: "claude-cli"`. Legacy
`claude-cli/claude-opus-4-7` refs still work for compatibility.
<Note>
@@ -135,8 +135,8 @@ Anthropic staff told us OpenClaw-style Claude CLI usage is allowed again, so Ope
- Provider: `openai-codex`
- Auth: OAuth (ChatGPT)
- Legacy PI model ref: `openai-codex/gpt-5.5`
- Native Codex app-server harness ref: `openai/gpt-5.5`
- PI model ref: `openai-codex/gpt-5.5`
- Native Codex app-server harness ref: `openai/gpt-5.5` with `agents.defaults.agentRuntime.id: "codex"`
- Native Codex app-server harness docs: [Codex harness](/plugins/codex-harness)
- Legacy model refs: `codex/gpt-*`
- Plugin boundary: `openai-codex/*` loads the OpenAI plugin; the native Codex app-server plugin is selected only by the Codex harness runtime or legacy `codex/*` refs.
@@ -148,8 +148,8 @@ Anthropic staff told us OpenClaw-style Claude CLI usage is allowed again, so Ope
- Shares the same `/fast` toggle and `params.fastMode` config as direct `openai/*`; OpenClaw maps that to `service_tier=priority`
- `openai-codex/gpt-5.5` uses the Codex catalog native `contextWindow = 400000` and default runtime `contextTokens = 272000`; override the runtime cap with `models.providers.openai-codex.models[].contextTokens`
- Policy note: OpenAI Codex OAuth is explicitly supported for external tools/workflows like OpenClaw.
- For the common subscription plus native Codex runtime route, sign in with `openai-codex` auth but configure `openai/gpt-5.5`; OpenAI agent turns select Codex by default.
- Use provider/model `agentRuntime.id: "pi"` only when you want a compatibility route through PI; otherwise keep `openai/gpt-5.5` on the default Codex harness.
- For the common subscription plus native Codex runtime route, sign in with `openai-codex` auth but configure `openai/gpt-5.5` plus `agents.defaults.agentRuntime.id: "codex"`.
- Use `openai-codex/gpt-5.5` only when you want the Codex OAuth/subscription route through PI; use `openai/gpt-5.5` without the Codex runtime override when your API-key setup and local catalog expose the public API route.
- Older `openai-codex/gpt-5.1*`, `openai-codex/gpt-5.2*`, and `openai-codex/gpt-5.3*` refs are suppressed because ChatGPT/Codex OAuth accounts reject them; use `openai-codex/gpt-5.5` or the native Codex runtime route instead.
```json5
@@ -158,6 +158,7 @@ Anthropic staff told us OpenClaw-style Claude CLI usage is allowed again, so Ope
agents: {
defaults: {
model: { primary: "openai/gpt-5.5" },
agentRuntime: { id: "codex" },
},
},
}

View File

@@ -23,7 +23,7 @@ sidebarTitle: "Models CLI"
</Card>
</CardGroup>
Model refs choose a provider and model. They do not usually choose the low-level agent runtime. OpenAI agent refs are the main exception: `openai/gpt-5.5` runs through the Codex app-server runtime by default on the official OpenAI provider. Explicit runtime overrides belong on provider/model policy, not on the whole agent or session. In Codex runtime mode, the `openai/gpt-*` ref does not imply API-key billing; auth can come from a Codex account or `openai-codex` auth profile. See [Agent runtimes](/concepts/agent-runtimes).
Model refs choose a provider and model. They do not usually choose the low-level agent runtime. For example, `openai/gpt-5.5` can run through the normal OpenAI provider path or through the Codex app-server runtime, depending on `agents.defaults.agentRuntime.id`. In Codex runtime mode, the `openai/gpt-*` ref does not imply API-key billing; auth can come from a Codex account or `openai-codex` auth profile. See [Agent runtimes](/concepts/agent-runtimes).
## How model selection works

View File

@@ -18,9 +18,9 @@ into the final answer when the channel can do that safely.
```text
Shelling...
📖 from docs/concepts/progress-drafts.md
📖 Read: from docs/concepts/progress-drafts.md
🔎 Web Search: for "discord edit message"
🛠️ Bash: run tests
🛠️ Exec: run tests
```
Use progress drafts when you want one tidy status message during tool-heavy work
@@ -51,17 +51,15 @@ progress chatter for that turn.
A progress draft has two parts:
| Part | Purpose |
| -------------- | ------------------------------------------------------------------------------------- |
| Label | A short starter/status line such as `Thinking...` or `Shelling...`. |
| Progress lines | Compact run updates using the same tool icons and detail formatter as verbose output. |
| Part | Purpose |
| -------------- | --------------------------------------------------------------------------- |
| Label | A short title such as `Thinking...` or `Shelling...`. |
| Progress lines | Compact run updates using the same tool labels and icons as verbose output. |
The label appears after the agent starts meaningful work and either remains busy
for five seconds or emits a second work event. It is part of the rolling progress
line list, so the starter status scrolls away once enough concrete work appears.
Plain text-only replies do not show a progress draft. Progress lines are added
only when the agent emits useful work updates, for example `🛠️ Bash: run tests`,
`🔎 Web Search: for "discord edit message"`, or `✍️ Write: to /tmp/file`.
for five seconds or emits a second work event. Plain text-only replies do not
show a progress draft. Progress lines are added only when the agent emits useful
work updates, for example `🛠️ Exec`, `🔎 Web Search`, or `✍️ Write: to /tmp/file`.
By default they use the same compact explain mode as `/verbose`; set
`agents.defaults.toolProgressDetail: "raw"` when debugging and you also want raw
commands/details appended.
@@ -191,16 +189,16 @@ OpenClaw uses the same formatter for progress drafts and `/verbose`:
```
`"explain"` is the default and keeps drafts stable with concise labels like
`🛠️ check JS syntax for /tmp/app.js`. `"raw"` appends the underlying
`🛠️ Exec: check JS syntax for /tmp/app.js`. `"raw"` appends the underlying
command/detail when available, which is useful while debugging but noisier in
chat.
For example, the same command appears differently depending on the detail mode:
| Mode | Progress line |
| --------- | -------------------------------------------------------------- |
| `explain` | `🛠️ check JS syntax for /tmp/app.js` |
| `raw` | `🛠️ check JS syntax for /tmp/app.js, node --check /tmp/app.js` |
| Mode | Progress line |
| --------- | -------------------------------------------------------------------- |
| `explain` | `🛠️ Exec: check JS syntax for /tmp/app.js` |
| `raw` | `🛠️ Exec: check JS syntax for /tmp/app.js, node --check /tmp/app.js` |
Limit how many lines stay visible:

View File

@@ -278,7 +278,7 @@ Optional:
- `OPENCLAW_QA_TELEGRAM_CAPTURE_CONTENT=1` keeps message bodies in observed-message artifacts (default redacts).
Scenarios (`extensions/qa-lab/src/live-transports/telegram/telegram-live.runtime.ts`):
Scenarios (`extensions/qa-lab/src/live-transports/telegram/telegram-live.runtime.ts:44`):
- `telegram-canary`
- `telegram-mention-gating`
@@ -287,17 +287,10 @@ Scenarios (`extensions/qa-lab/src/live-transports/telegram/telegram-live.runtime
- `telegram-commands-command`
- `telegram-tools-compact-command`
- `telegram-whoami-command`
- `telegram-status-command`
- `telegram-other-bot-command-gating`
- `telegram-context-command`
- `telegram-current-session-status-tool`
- `telegram-reply-chain-exact-marker`
- `telegram-stream-final-single-message`
- `telegram-long-final-reuses-preview`
- `telegram-long-final-three-chunks`
The implicit default set always covers canary, mention gating, native command replies, command addressing, and bot-to-bot group replies. `mock-openai` defaults also include deterministic reply-chain and final-message streaming checks. `telegram-current-session-status-tool` remains opt-in because it is only stable when threaded directly after canary, not after arbitrary native command replies. Use `pnpm openclaw qa telegram --list-scenarios --provider-mode mock-openai` to print the current default/optional split with regression refs.
Output artifacts:
- `telegram-qa-report.md`
@@ -566,17 +559,15 @@ A green run completes in well under 30 seconds and `slack-qa-report.md` shows bo
### Convex credential pool
Telegram, Discord, Slack, and WhatsApp lanes can lease credentials from a shared Convex pool instead of reading the env vars above. Pass `--credential-source convex` (or set `OPENCLAW_QA_CREDENTIAL_SOURCE=convex`); QA Lab acquires an exclusive lease, heartbeats it for the duration of the run, and releases it on shutdown. Pool kinds are `"telegram"`, `"discord"`, `"slack"`, and `"whatsapp"`.
Telegram, Discord, and Slack lanes can lease credentials from a shared Convex pool instead of reading the env vars above. Pass `--credential-source convex` (or set `OPENCLAW_QA_CREDENTIAL_SOURCE=convex`); QA Lab acquires an exclusive lease, heartbeats it for the duration of the run, and releases it on shutdown. Pool kinds are `"telegram"`, `"discord"`, and `"slack"`.
Payload shapes the broker validates on `admin/add`:
- Telegram (`kind: "telegram"`): `{ groupId: string, driverToken: string, sutToken: string }` - `groupId` must be a numeric chat-id string.
- Discord (`kind: "discord"`): `{ guildId: string, channelId: string, driverBotToken: string, sutBotToken: string, sutApplicationId: string }`.
- WhatsApp (`kind: "whatsapp"`): `{ driverPhoneE164: string, sutPhoneE164: string, driverAuthArchiveBase64: string, sutAuthArchiveBase64: string, groupJid?: string }` - phone numbers must be distinct E.164 strings.
- Slack (`kind: "slack"`): `{ channelId: string, driverBotToken: string, sutBotToken: string, sutAppToken: string }` - `channelId` must match `^[A-Z][A-Z0-9]+$` (a Slack id like `Cxxxxxxxxxx`). See [Setting up the Slack workspace](#setting-up-the-slack-workspace) for app and scope provisioning.
Slack lanes can also use the pool. Slack payload shape checks currently live in the Slack QA runner rather than the broker; use `{ channelId: string, driverBotToken: string, sutBotToken: string, sutAppToken: string }`, with a Slack channel id like `Cxxxxxxxxxx`. See [Setting up the Slack workspace](#setting-up-the-slack-workspace) for app and scope provisioning.
Operational env vars and the Convex broker endpoint contract live in [Testing → Shared Telegram credentials via Convex](/help/testing#shared-telegram-credentials-via-convex-v1) (the section name predates the multi-channel pool; the lease semantics are shared across kinds).
Operational env vars and the Convex broker endpoint contract live in [Testing → Shared Telegram credentials via Convex](/help/testing#shared-telegram-credentials-via-convex-v1) (the section name predates Discord support; the broker semantics are identical for both kinds).
## Repo-backed seeds

View File

@@ -54,7 +54,7 @@
"redirects": [
{
"source": "/channels/bluebubbles",
"destination": "/channels/imessage-from-bluebubbles"
"destination": "/channels/imessage"
},
{
"source": "/install/migrating-matrix",
@@ -393,16 +393,16 @@
"destination": "/channels/pairing"
},
{
"source": "/clawdhub",
"destination": "/clawhub"
"source": "/clawhub",
"destination": "/tools/clawhub"
},
{
"source": "/tools/clawhub",
"destination": "/clawhub"
"source": "/clawdhub",
"destination": "/tools/clawhub"
},
{
"source": "/tools/clawdhub",
"destination": "/clawhub"
"destination": "/tools/clawhub"
},
{
"source": "/configuration",
@@ -1063,7 +1063,6 @@
"channels/msteams",
"channels/googlechat",
"channels/imessage",
"channels/imessage-from-bluebubbles",
"channels/matrix",
"channels/matrix-migration",
"channels/matrix-push-rules"
@@ -1243,6 +1242,7 @@
"tools/creating-skills",
"tools/skills-config",
"tools/slash-commands",
"tools/clawhub",
"prose"
]
},
@@ -1325,36 +1325,6 @@
}
]
},
{
"tab": "ClawHub",
"groups": [
{
"group": "Overview",
"pages": ["clawhub/index", "clawhub/quickstart", "clawhub/how-it-works"]
},
{
"group": "Using ClawHub",
"pages": [
"clawhub/cli",
"clawhub/publishing",
"clawhub/skill-format",
"clawhub/soul-format",
"clawhub/auth",
"clawhub/telemetry",
"clawhub/troubleshooting"
]
},
{
"group": "API and trust",
"pages": [
"clawhub/api",
"clawhub/http-api",
"clawhub/security",
"clawhub/acceptable-usage"
]
}
]
},
{
"tab": "Models",
"groups": [

View File

@@ -336,6 +336,9 @@ Time format in system prompt. Default: `auto` (OS preference).
fallbacks: ["openai/gpt-5.4-mini"],
},
params: { cacheRetention: "long" }, // global default provider params
agentRuntime: {
id: "pi", // pi | auto | registered harness id, e.g. codex
},
pdfMaxBytesMb: 10,
pdfMaxPages: 20,
thinkingDefault: "low",
@@ -395,28 +398,25 @@ Time format in system prompt. Default: `auto` (OS preference).
- `params.chat_template_kwargs`: vLLM/OpenAI-compatible chat-template arguments merged into top-level `api: "openai-completions"` request bodies. For `vllm/nemotron-3-*` with thinking off, the bundled vLLM plugin automatically sends `enable_thinking: false` and `force_nonempty_content: true`; explicit `chat_template_kwargs` override generated defaults, and `extra_body.chat_template_kwargs` still has final precedence. For vLLM Qwen thinking controls, set `params.qwenThinkingFormat` to `"chat-template"` or `"top-level"` on that model entry.
- `compat.supportedReasoningEfforts`: per-model OpenAI-compatible reasoning effort list. Include `"xhigh"` for custom endpoints that truly accept it; OpenClaw then exposes `/think xhigh` in command menus, Gateway session rows, session patch validation, agent CLI validation, and `llm-task` validation for that configured provider/model. Use `compat.reasoningEffortMap` when the backend wants a provider-specific value for a canonical level.
- `params.preserveThinking`: Z.AI-only opt-in for preserved thinking. When enabled and thinking is on, OpenClaw sends `thinking.clear_thinking: false` and replays prior `reasoning_content`; see [Z.AI thinking and preserved thinking](/providers/zai#thinking-and-preserved-thinking).
- Runtime policy belongs on providers or models, not on `agents.defaults`. Use `models.providers.<provider>.agentRuntime` for provider-wide rules or `agents.defaults.models["provider/model"].agentRuntime` / `agents.list[].models["provider/model"].agentRuntime` for model-specific rules. OpenAI agent models on the official OpenAI provider select Codex by default.
- `agentRuntime`: default low-level agent runtime policy. Omitted id defaults to OpenClaw Pi. Use `id: "pi"` to force the built-in PI harness, `id: "auto"` to let registered plugin harnesses claim supported models and use PI when none match, a registered harness id such as `id: "codex"` to require that harness, or a supported CLI backend alias such as `id: "claude-cli"`. Explicit plugin runtimes fail closed when the harness is unavailable or fails. Keep model refs canonical as `provider/model`; select Codex, Claude CLI, Gemini CLI, and other execution backends through runtime config instead of legacy runtime provider prefixes. See [Agent runtimes](/concepts/agent-runtimes) for how this differs from provider/model selection.
- Config writers that mutate these fields (for example `/models set`, `/models set-image`, and fallback add/remove commands) save canonical object form and preserve existing fallback lists when possible.
- `maxConcurrent`: max parallel agent runs across sessions (each session still serialized). Default: 4.
### Runtime policy
### `agents.defaults.agentRuntime`
`agentRuntime` controls which low-level executor runs agent turns. Most
deployments should keep the default OpenClaw Pi runtime. Use it when a trusted
plugin provides a native harness, such as the bundled Codex app-server harness,
or when you want a supported CLI backend such as Claude CLI. For the mental
model, see [Agent runtimes](/concepts/agent-runtimes).
```json5
{
models: {
providers: {
openai: {
agentRuntime: { id: "codex" },
},
},
},
agents: {
defaults: {
model: "openai/gpt-5.5",
models: {
"anthropic/claude-opus-4-7": {
agentRuntime: { id: "claude-cli" },
},
agentRuntime: {
id: "codex",
},
},
},
@@ -425,9 +425,11 @@ Time format in system prompt. Default: `auto` (OS preference).
- `id`: `"auto"`, `"pi"`, a registered plugin harness id, or a supported CLI backend alias. The bundled Codex plugin registers `codex`; the bundled Anthropic plugin provides the `claude-cli` CLI backend.
- `id: "auto"` lets registered plugin harnesses claim supported turns and uses PI when no harness matches. An explicit plugin runtime such as `id: "codex"` requires that harness and fails closed if it is unavailable or fails.
- Whole-agent runtime keys are legacy. `agents.defaults.agentRuntime`, `agents.list[].agentRuntime`, session runtime pins, and `OPENCLAW_AGENT_RUNTIME` are ignored by runtime selection. Run `openclaw doctor --fix` to remove stale values.
- OpenAI agent models use the Codex harness by default; provider/model `agentRuntime.id: "codex"` remains valid when you want to make that explicit.
- For Claude CLI deployments, prefer `model: "anthropic/claude-opus-4-7"` plus model-scoped `agentRuntime.id: "claude-cli"`. Legacy `claude-cli/claude-opus-4-7` model refs still work for compatibility, but new config should keep provider/model selection canonical and put the execution backend in provider/model runtime policy.
- Environment override: `OPENCLAW_AGENT_RUNTIME=<id|auto|pi>` overrides `id` for that process.
- OpenAI agent models use the Codex harness by default; `agentRuntime.id: "codex"` remains valid when you want to make that explicit.
- For Claude CLI deployments, prefer `model: "anthropic/claude-opus-4-7"` plus `agentRuntime.id: "claude-cli"`. Legacy `claude-cli/claude-opus-4-7` model refs still work for compatibility, but new config should keep provider/model selection canonical and put the execution backend in `agentRuntime.id`.
- Older runtime-policy keys are rewritten to `agentRuntime` by `openclaw doctor --fix`.
- Harness choice is pinned per session id after the first embedded run. Config/env changes affect new or reset sessions, not an existing transcript. Legacy OpenAI sessions with transcript history but no recorded pin use Codex; stale OpenAI PI pins can be repaired with `openclaw doctor --fix`. `/status` reports the effective runtime, for example `Runtime: OpenClaw Pi Default` or `Runtime: OpenAI Codex`.
- This only controls text agent-turn execution. Media generation, vision, PDF, music, video, and TTS still use their provider/model settings.
**Built-in alias shorthands** (only apply when the model is in `agents.defaults.models`):
@@ -957,6 +959,7 @@ for provider examples and precedence.
thinkingDefault: "high", // per-agent thinking level override
reasoningDefault: "on", // per-agent reasoning visibility override
fastModeDefault: false, // per-agent fast mode override
agentRuntime: { id: "auto" },
params: { cacheRetention: "none" }, // overrides matching defaults.models params by key
tts: {
providers: {
@@ -1003,7 +1006,7 @@ for provider examples and precedence.
- `thinkingDefault`: optional per-agent default thinking level (`off | minimal | low | medium | high | xhigh | adaptive | max`). Overrides `agents.defaults.thinkingDefault` for this agent when no per-message or session override is set. The selected provider/model profile controls which values are valid; for Google Gemini, `adaptive` keeps provider-owned dynamic thinking (`thinkingLevel` omitted on Gemini 3/3.1, `thinkingBudget: -1` on Gemini 2.5).
- `reasoningDefault`: optional per-agent default reasoning visibility (`on | off | stream`). Overrides `agents.defaults.reasoningDefault` for this agent when no per-message or session reasoning override is set.
- `fastModeDefault`: optional per-agent default for fast mode (`true | false`). Applies when no per-message or session fast-mode override is set.
- `models`: optional per-agent model catalog/runtime overrides keyed by full `provider/model` ids. Use `models["provider/model"].agentRuntime` for per-agent runtime exceptions.
- `agentRuntime`: optional per-agent low-level runtime policy override. Use `{ id: "codex" }` to make one agent Codex-only while other agents keep the default PI fallback in `auto` mode.
- `runtime`: optional per-agent runtime descriptor. Use `type: "acp"` with `runtime.acp` defaults (`agent`, `backend`, `mode`, `cwd`) when the agent should default to ACP harness sessions.
- `identity.avatar`: workspace-relative path, `http(s)` URL, or `data:` URI.
- `identity` derives defaults: `ackReaction` from `emoji`, `mentionPatterns` from `name`/`emoji`.
@@ -1385,8 +1388,8 @@ Defaults for Talk mode (macOS/iOS/Android).
provider: "openai",
providers: {
openai: {
model: "gpt-realtime-2",
voice: "cedar",
model: "gpt-realtime",
voice: "alloy",
},
},
mode: "realtime",

View File

@@ -585,7 +585,7 @@ When Mattermost native commands are enabled:
OpenClaw spawns `imsg rpc` (JSON-RPC over stdio). No daemon or port required. This is the preferred path for new OpenClaw iMessage setups when the host can grant Messages database and Automation permissions.
BlueBubbles support was removed. Migrate `channels.bluebubbles` configs to `channels.imessage`; OpenClaw supports iMessage through `imsg` only.
BlueBubbles is deprecated and no longer ships as a bundled OpenClaw channel. Migrate `channels.bluebubbles` configs to `channels.imessage`; third-party BlueBubbles bridges belong outside core.
If the Gateway is not running on the signed-in Messages Mac, keep `channels.imessage.enabled=true` and set `channels.imessage.cliPath` to an SSH wrapper that runs `imsg "$@"` on that Mac. The default local `imsg` path is macOS-only.

View File

@@ -198,75 +198,8 @@ See [MCP](/cli/mcp#openclaw-as-an-mcp-client-registry) and
- `plugins.entries.<id>.hooks.allowConversationAccess`: when `true`, trusted non-bundled plugins may read raw conversation content from typed hooks such as `llm_input`, `llm_output`, `before_model_resolve`, `before_agent_reply`, `before_agent_run`, `before_agent_finalize`, and `agent_end`.
- `plugins.entries.<id>.subagent.allowModelOverride`: explicitly trust this plugin to request per-run `provider` and `model` overrides for background subagent runs.
- `plugins.entries.<id>.subagent.allowedModels`: optional allowlist of canonical `provider/model` targets for trusted subagent overrides. Use `"*"` only when you intentionally want to allow any model.
- `plugins.entries.<id>.llm.allowModelOverride`: explicitly trust this plugin to request model overrides for `api.runtime.llm.complete`.
- `plugins.entries.<id>.llm.allowedModels`: optional allowlist of canonical `provider/model` targets for trusted plugin LLM completion overrides. Use `"*"` only when you intentionally want to allow any model.
- `plugins.entries.<id>.llm.allowAgentIdOverride`: explicitly trust this plugin to run `api.runtime.llm.complete` against a non-default agent id.
- `plugins.entries.<id>.config`: plugin-defined config object (validated by native OpenClaw plugin schema when available).
- Channel plugin account/runtime settings live under `channels.<id>` and should be described by the owning plugin's manifest `channelConfigs` metadata, not by a central OpenClaw option registry.
### Codex harness plugin config
The bundled `codex` plugin owns native Codex app-server harness settings under
`plugins.entries.codex.config`. See [Codex harness](/plugins/codex-harness) for
the full runtime model.
`codexPlugins` applies only to sessions that select the native Codex harness.
It does not enable Codex plugins for Pi, normal OpenAI provider runs, ACP
conversation bindings, or any non-Codex harness.
```json5
{
plugins: {
entries: {
codex: {
enabled: true,
config: {
codexPlugins: {
enabled: true,
allow_destructive_actions: false,
plugins: {
"google-calendar": {
enabled: true,
marketplaceName: "openai-curated",
pluginName: "google-calendar",
allow_destructive_actions: false,
},
},
},
},
},
},
},
}
```
- `plugins.entries.codex.config.codexPlugins.enabled`: enables native Codex
plugin/app support for the Codex harness. Default: `false`.
- `plugins.entries.codex.config.codexPlugins.allow_destructive_actions`:
default destructive-action policy for migrated plugin app elicitations.
Default: `false`.
- `plugins.entries.codex.config.codexPlugins.plugins.<key>.enabled`: enables a
migrated plugin entry when global `codexPlugins.enabled` is also true.
Default: `true` for explicit entries.
- `plugins.entries.codex.config.codexPlugins.plugins.<key>.marketplaceName`:
stable marketplace identity. V1 only supports `"openai-curated"`.
- `plugins.entries.codex.config.codexPlugins.plugins.<key>.pluginName`: stable
Codex plugin identity from migration, for example `"google-calendar"`.
- `plugins.entries.codex.config.codexPlugins.plugins.<key>.allow_destructive_actions`:
per-plugin destructive-action override. When omitted, the global
`allow_destructive_actions` value is used.
`codexPlugins.enabled` is the global enablement directive. Explicit plugin
entries written by migration are the durable install and repair eligibility set.
`plugins["*"]` is not supported, there is no `install` switch, and local
`marketplacePath` values are intentionally not config fields because they are
host-specific.
`app/list` readiness checks are cached for one hour and refreshed
asynchronously when stale. Codex thread app config is computed at Codex harness
session establishment, not on every turn; use `/new`, `/reset`, or a gateway
restart after changing native plugin config.
- `plugins.entries.firecrawl.config.webFetch`: Firecrawl web-fetch provider settings.
- `apiKey`: Firecrawl API key (accepts SecretRef). Falls back to `plugins.entries.firecrawl.config.webSearch.apiKey`, legacy `tools.web.fetch.firecrawl.apiKey`, or `FIRECRAWL_API_KEY` env var.
- `baseUrl`: Firecrawl API base URL (default: `https://api.firecrawl.dev`; self-hosted overrides must target private/internal endpoints).
@@ -510,10 +443,6 @@ See [Inferred commitments](/concepts/commitments).
value, so repeated failures from one localhost origin do not automatically
lock out a different origin.
- `tailscale.mode`: `serve` (tailnet only, loopback bind) or `funnel` (public, requires auth).
- `tailscale.preserveFunnel`: when `true` and `tailscale.mode = "serve"`, OpenClaw
checks `tailscale funnel status` before re-applying Serve at startup and skips
it if an externally configured Funnel route already covers the gateway port.
Default `false`.
- `controlUi.allowedOrigins`: explicit browser-origin allowlist for Gateway WebSocket connects. Required when browser clients are expected from non-loopback origins.
- `controlUi.chatMessageMaxWidth`: optional max-width for grouped Control UI chat messages. Accepts constrained CSS width values such as `960px`, `82%`, `min(1280px, 82%)`, and `calc(100% - 2rem)`.
- `controlUi.dangerouslyAllowHostHeaderOriginFallback`: dangerous mode that enables Host-header origin fallback for deployments that intentionally rely on Host-header origin policy.

View File

@@ -87,7 +87,7 @@ cat ~/.openclaw/openclaw.json
- Legacy on-disk state migration (sessions/agent dir/WhatsApp auth).
- Legacy plugin manifest contract key migration (`speechProviders`, `realtimeTranscriptionProviders`, `realtimeVoiceProviders`, `mediaUnderstandingProviders`, `imageGenerationProviders`, `videoGenerationProviders`, `webFetchProviders`, `webSearchProviders` → `contracts`).
- Legacy cron store migration (`jobId`, `schedule.cron`, top-level delivery/payload fields, payload `provider`, simple `notify: true` webhook fallback jobs).
- Legacy whole-agent runtime-policy cleanup; provider/model runtime policy is the active route selector.
- Legacy agent runtime-policy migration to `agents.defaults.agentRuntime` and `agents.list[].agentRuntime`.
- Stale plugin config cleanup when plugins are enabled; when `plugins.enabled=false`, stale plugin references are treated as inert containment config and are preserved.
</Accordion>
@@ -109,7 +109,7 @@ cat ~/.openclaw/openclaw.json
- Channel status warnings (probed from the running gateway).
- Channel-specific permission checks live under `openclaw channels capabilities`; for example, Discord voice channel permissions are audited with `openclaw channels capabilities --channel discord --target channel:<channel-id>`.
- WhatsApp responsiveness checks for degraded Gateway event-loop health with local TUI clients still running; `--fix` stops only verified local TUI clients.
- Codex route repair for legacy `openai-codex/*` model refs in primary models, fallbacks, heartbeat/subagent/compaction overrides, hooks, channel model overrides, and session route pins; `--fix` rewrites them to `openai/*`, removes stale session/whole-agent runtime pins, and leaves canonical OpenAI agent refs on the default Codex harness.
- Codex route repair for legacy `openai-codex/*` model refs in primary models, fallbacks, heartbeat/subagent/compaction overrides, hooks, channel model overrides, and session route pins; `--fix` rewrites them to `openai/*` and selects `agentRuntime.id: "codex"` only when the Codex plugin is installed, enabled, contributes the `codex` harness, and has usable OAuth. Otherwise it selects `agentRuntime.id: "pi"`.
- Supervisor config audit (launchd/systemd/schtasks) with optional repair.
- Embedded proxy environment cleanup for gateway services that captured shell `HTTP_PROXY` / `HTTPS_PROXY` / `NO_PROXY` values during install or update.
- Gateway runtime best-practice checks (Node vs Bun, version-manager paths).
@@ -269,8 +269,8 @@ That stages grounded durable candidates into the short-term dreaming store while
In `--fix` / `--repair` mode, doctor rewrites affected default-agent and per-agent refs, including primary models, fallbacks, heartbeat/subagent/compaction overrides, hooks, channel model overrides, and stale persisted session route state:
- `openai-codex/gpt-*` becomes `openai/gpt-*`.
- Stale whole-agent runtime config and persisted session runtime pins are removed because runtime selection is provider/model-scoped.
- Explicit provider/model runtime policy is preserved.
- The matching agent runtime becomes `agentRuntime.id: "codex"` only when Codex is installed, enabled, contributes the `codex` harness, and has usable OAuth.
- Otherwise the matching agent runtime becomes `agentRuntime.id: "pi"`.
- Existing model fallback lists are preserved with their legacy entries rewritten; copied per-model settings move from the legacy key to the canonical `openai/*` key.
- Persisted session `modelProvider`/`providerOverride`, `model`/`modelOverride`, fallback notices, auth-profile pins, and Codex harness pins are repaired across all discovered agent session stores.
- `/codex ...` means "control or bind a native Codex conversation from chat."

View File

@@ -217,9 +217,7 @@ openclaw gateway restart
openclaw gateway stop
```
Use `openclaw gateway restart` for restarts. Do not chain `openclaw gateway stop` and `openclaw gateway start` as a restart substitute.
On macOS, `gateway stop` uses `launchctl bootout` by default — this removes the LaunchAgent from the current boot session without persisting a disable, so KeepAlive auto-recovery still works after unexpected crashes and `gateway start` re-enables cleanly. To persistently suppress auto-respawn across reboots, pass `--disable`: `openclaw gateway stop --disable`.
Use `openclaw gateway restart` for restarts. Do not chain `openclaw gateway stop` and `openclaw gateway start`; on macOS, `gateway stop` intentionally disables the LaunchAgent before stopping it.
LaunchAgent labels are `ai.openclaw.gateway` (default) or `ai.openclaw.<profile>` (named profile). `openclaw doctor` audits and repairs service config drift.

View File

@@ -64,7 +64,6 @@ Rules of thumb:
- `deny` always wins.
- If `allow` is non-empty, everything else is treated as blocked.
- Tool policy is the hard stop: `/exec` cannot override a denied `exec` tool.
- Tool policy filters tool availability by name; it does not inspect side effects inside `exec`. If `exec` is allowed, denying `write`, `edit`, or `apply_patch` does not make shell commands read-only.
- `/exec` only changes session defaults for authorized senders; it does not grant tool access.
Provider tool keys accept either `provider` (e.g. `google-antigravity`) or `provider/model` (e.g. `openai/gpt-5.4`).
@@ -89,7 +88,6 @@ Available groups:
- `group:runtime`: `exec`, `process`, `code_execution` (`bash` is accepted as
an alias for `exec`)
- `group:fs`: `read`, `write`, `edit`, `apply_patch`
For read-only agents, deny `group:runtime` as well as mutating filesystem tools unless sandbox filesystem policy or a separate host boundary enforces the read-only constraint.
- `group:sessions`: `sessions_list`, `sessions_history`, `sessions_send`, `sessions_spawn`, `sessions_yield`, `subagents`, `session_status`
- `group:memory`: `memory_search`, `memory_get`
- `group:web`: `web_search`, `x_search`, `web_fetch`

View File

@@ -91,7 +91,6 @@ exhaustive):
| `tools.exec.host_sandbox_no_sandbox_defaults` | warn | `exec host=sandbox` fails closed when sandbox is off | `tools.exec.host`, `agents.defaults.sandbox.mode` | no |
| `tools.exec.host_sandbox_no_sandbox_agents` | warn | Per-agent `exec host=sandbox` fails closed when sandbox is off | `agents.list[].tools.exec.host`, `agents.list[].sandbox.mode` | no |
| `tools.exec.security_full_configured` | warn/critical | Host exec is running with `security="full"` | `tools.exec.security`, `agents.list[].tools.exec.security` | no |
| `tools.exec.fs_tools_disabled_but_exec_enabled` | warn | Filesystem tool policy does not make shell execution read-only | `tools.deny`, `agents.list[].tools.deny`, `agents.*.sandbox.workspaceAccess` | no |
| `tools.exec.auto_allow_skills_enabled` | warn | Exec approvals trust skill bins implicitly | `~/.openclaw/exec-approvals.json` | no |
| `tools.exec.allowlist_interpreter_without_strict_inline_eval` | warn | Interpreter allowlists permit inline eval without forced reapproval | `tools.exec.strictInlineEval`, `agents.list[].tools.exec.strictInlineEval`, exec approvals allowlist | no |
| `tools.exec.safe_bins_interpreter_unprofiled` | warn | Interpreter/runtime bins in `safeBins` without explicit profiles broaden exec risk | `tools.exec.safeBins`, `tools.exec.safeBinProfiles`, `agents.list[].tools.exec.*` | no |

View File

@@ -220,7 +220,6 @@ Advisory triage guidance:
- **Inbound access** (DM policies, group policies, allowlists): can strangers trigger the bot?
- **Tool blast radius** (elevated tools + open rooms): could prompt injection turn into shell/file/network actions?
- **Exec filesystem drift**: are mutating filesystem tools denied while `exec`/`process` remain available without sandbox filesystem constraints?
- **Exec approval drift** (`security=full`, `autoAllowSkills`, interpreter allowlists without `strictInlineEval`): are host-exec guardrails still doing what you think they are?
- `security="full"` is a broad posture warning, not proof of a bug. It is the chosen default for trusted personal-assistant setups; tighten it only when your threat model needs approval or allowlist guardrails.
- **Network exposure** (Gateway bind/auth, Tailscale Serve/Funnel, weak/short auth tokens).

View File

@@ -116,11 +116,6 @@ openclaw gateway --tailscale funnel --auth password
- `tailscale.mode: "funnel"` refuses to start unless auth mode is `password` to avoid public exposure.
- Set `gateway.tailscale.resetOnExit` if you want OpenClaw to undo `tailscale serve`
or `tailscale funnel` configuration on shutdown.
- Set `gateway.tailscale.preserveFunnel: true` to keep an externally configured
`tailscale funnel` route alive across gateway restarts. When enabled and the
gateway runs in `mode: "serve"`, OpenClaw checks `tailscale funnel status`
before re-applying Serve and skips it when a Funnel route already covers the
gateway port. The OpenClaw-managed Funnel password-only policy is unchanged.
- `gateway.bind: "tailnet"` is a direct Tailnet bind (no HTTPS, no Serve/Funnel).
- `gateway.bind: "auto"` prefers loopback; use `tailnet` if you want Tailnet-only.
- Serve/Funnel only expose the **Gateway control UI + WS**. Nodes connect over

View File

@@ -97,7 +97,6 @@ If a tool is not allowed by policy, the endpoint returns **404**.
Important boundary notes:
- Exec approvals are operator guardrails, not a separate authorization boundary for this HTTP endpoint. If a tool is reachable here via Gateway auth + tool policy, `/tools/invoke` does not add an extra per-call approval prompt.
- If `exec` is reachable here, treat it as a mutating shell surface. Denying `write`, `edit`, `apply_patch`, or HTTP filesystem-write tools does not make shell execution read-only.
- Do not share Gateway bearer credentials with untrusted callers. If you need separation across trust boundaries, run separate gateways (and ideally separate OS users/hosts).
Gateway HTTP also applies a hard deny list by default (even if session policy allows the tool):

View File

@@ -96,9 +96,9 @@ add Node's sync I/O trace flag through the source runner:
OPENCLAW_TRACE_SYNC_IO=1 pnpm openclaw gateway --force
```
`pnpm gateway:watch` leaves this flag disabled by default for the watched
Gateway child. Set `OPENCLAW_TRACE_SYNC_IO=1` when you explicitly want Node
sync I/O trace output in watch mode.
`pnpm gateway:watch` enables this flag by default for the watched Gateway child.
Set `OPENCLAW_TRACE_SYNC_IO=0` to suppress Node sync I/O trace output in watch
mode.
## Gateway watch mode

View File

@@ -594,11 +594,12 @@ and troubleshooting see the main [FAQ](/help/faq).
<Accordion title="How does Codex auth work?">
OpenClaw supports **OpenAI Code (Codex)** via OAuth (ChatGPT sign-in). Use
`openai/gpt-5.5` for the common setup: ChatGPT/Codex subscription auth plus
native Codex app-server execution. `openai-codex/gpt-*` model refs are
legacy config repaired by `openclaw doctor --fix`. Direct OpenAI API-key
access remains available for non-agent OpenAI API surfaces and for agent
models through an ordered `openai-codex` API-key profile.
`openai/gpt-5.5` with `agentRuntime.id: "codex"` for the common setup:
ChatGPT/Codex subscription auth plus native Codex app-server execution. Use
`openai-codex/gpt-5.5` only when you want Codex OAuth through the default
Codex runtime. Direct OpenAI API-key access remains available for non-agent
OpenAI API surfaces and for agent models through an ordered
`openai-codex` API-key profile.
See [Model providers](/concepts/model-providers) and [Onboarding (CLI)](/start/wizard).
</Accordion>

View File

@@ -150,7 +150,7 @@ troubleshooting, see the main [FAQ](/help/faq).
- **Native Codex coding agent:** set `agents.defaults.model.primary` to `openai/gpt-5.5`. Sign in with `openclaw models auth login --provider openai-codex` when you want ChatGPT/Codex subscription auth.
- **Direct OpenAI API tasks outside the agent loop:** configure `OPENAI_API_KEY` for images, embeddings, speech, realtime, and other non-agent OpenAI API surfaces.
- **OpenAI agent API-key auth:** use `/model openai/gpt-5.5` with an ordered `openai-codex` API-key profile.
- **Sub-agents:** route coding tasks to a Codex-focused agent with its own `openai/gpt-5.5` model.
- **Sub-agents:** route coding tasks to a Codex-only agent with its own model and `agentRuntime` default.
See [Models](/concepts/models) and [Slash commands](/tools/slash-commands).

View File

@@ -409,7 +409,7 @@ lives on the [First-run FAQ](/help/faq-first-run).
openclaw skills update --all
```
Native installs land in the active workspace `skills/` directory. For shared skills across agents, place them in `~/.openclaw/skills/<name>/SKILL.md`. If only some agents should see a shared install, configure `agents.defaults.skills` or `agents.list[].skills`. Some skills expect binaries installed via Homebrew; on Linux that means Linuxbrew (see the Homebrew Linux FAQ entry above). See [Skills](/tools/skills), [Skills config](/tools/skills-config), and [ClawHub](/clawhub).
Native installs land in the active workspace `skills/` directory. For shared skills across agents, place them in `~/.openclaw/skills/<name>/SKILL.md`. If only some agents should see a shared install, configure `agents.defaults.skills` or `agents.list[].skills`. Some skills expect binaries installed via Homebrew; on Linux that means Linuxbrew (see the Homebrew Linux FAQ entry above). See [Skills](/tools/skills), [Skills config](/tools/skills-config), and [ClawHub](/tools/clawhub).
</Accordion>

View File

@@ -285,8 +285,8 @@ Docker notes:
- Goal: validate the plugin-owned Codex harness through the normal gateway
`agent` method:
- load the bundled `codex` plugin
- select `openai/gpt-5.5`, which routes OpenAI agent turns through Codex by default
- send a first gateway agent turn to `openai/gpt-5.5` with the Codex harness selected
- select `OPENCLAW_AGENT_RUNTIME=codex`
- send a first gateway agent turn to `openai/gpt-5.5` with the Codex harness forced
- send a second turn to the same OpenClaw session and verify the app-server
thread can resume
- run `/codex status` and `/codex models` through the same gateway command
@@ -300,8 +300,8 @@ Docker notes:
- Optional image probe: `OPENCLAW_LIVE_CODEX_HARNESS_IMAGE_PROBE=1`
- Optional MCP/tool probe: `OPENCLAW_LIVE_CODEX_HARNESS_MCP_PROBE=1`
- Optional Guardian probe: `OPENCLAW_LIVE_CODEX_HARNESS_GUARDIAN_PROBE=1`
- The smoke forces provider/model `agentRuntime.id: "codex"` so a broken Codex
harness cannot pass by silently falling back to PI.
- The smoke uses `agentRuntime.id: "codex"` so a broken Codex harness cannot
pass by silently falling back to PI.
- Auth: Codex app-server auth from the local Codex subscription login. Docker
smokes can also provide `OPENAI_API_KEY` for non-Codex probes when applicable,
plus optional copied `~/.codex/auth.json` and `~/.codex/config.toml`.

View File

@@ -195,10 +195,6 @@ inside every shard.
- Installs an OpenClaw package candidate in Docker, runs installed-package
onboarding, configures Telegram through the installed CLI, then reuses the
live Telegram QA lane with that installed package as the SUT Gateway.
- The wrapper mounts only the `qa-lab` harness source from the checkout; the
installed package owns `dist`, `openclaw/plugin-sdk`, and bundled plugin
runtime so the lane does not mix current checkout plugins into the package
under test.
- Defaults to `OPENCLAW_NPM_TELEGRAM_PACKAGE_SPEC=openclaw@beta`; set
`OPENCLAW_NPM_TELEGRAM_PACKAGE_TGZ=/path/to/openclaw-current.tgz` or
`OPENCLAW_CURRENT_PACKAGE_TGZ` to test a resolved local tarball instead of
@@ -311,7 +307,6 @@ gh workflow run package-acceptance.yml --ref main \
- Runs the Telegram live QA lane against a real private group using the driver and SUT bot tokens from env.
- Requires `OPENCLAW_QA_TELEGRAM_GROUP_ID`, `OPENCLAW_QA_TELEGRAM_DRIVER_BOT_TOKEN`, and `OPENCLAW_QA_TELEGRAM_SUT_BOT_TOKEN`. The group id must be the numeric Telegram chat id.
- Supports `--credential-source convex` for shared pooled credentials. Use env mode by default, or set `OPENCLAW_QA_CREDENTIAL_SOURCE=convex` to opt into pooled leases.
- Defaults cover canary, mention gating, command addressing, `/status`, bot-to-bot mentioned replies, and core native command replies. `mock-openai` defaults also cover deterministic reply-chain and Telegram final-message streaming regressions. Use `--list-scenarios` for optional probes such as `session_status`.
- Exits non-zero when any scenario fails. Use `--allow-failures` when you
want artifacts without a failing exit code.
- Requires two distinct bots in the same private group, with the SUT bot exposing a Telegram username.
@@ -323,9 +318,8 @@ Live transport lanes share one standard contract so new transports do not drift;
### Shared Telegram credentials via Convex (v1)
When `--credential-source convex` (or `OPENCLAW_QA_CREDENTIAL_SOURCE=convex`) is enabled for
live transport QA, QA lab acquires an exclusive lease from a Convex-backed pool, heartbeats that
lease while the lane is running, and releases the lease on shutdown. The section name predates
Discord, Slack, and WhatsApp support; the lease contract is shared across kinds.
`openclaw qa telegram`, QA lab acquires an exclusive lease from a Convex-backed pool, heartbeats
that lease while the lane is running, and releases the lease on shutdown.
Reference Convex project scaffold:
@@ -399,16 +393,6 @@ Payload shape for Telegram kind:
- `groupId` must be a numeric Telegram chat id string.
- `admin/add` validates this shape for `kind: "telegram"` and rejects malformed payloads.
Broker-validated multi-channel payloads:
- Discord: `{ guildId: string, channelId: string, driverBotToken: string, sutBotToken: string, sutApplicationId: string, voiceChannelId?: string }`
- WhatsApp: `{ driverPhoneE164: string, sutPhoneE164: string, driverAuthArchiveBase64: string, sutAuthArchiveBase64: string, groupJid?: string }`
Slack lanes can also lease from the pool, but Slack payload validation currently
lives in the Slack QA runner rather than the broker. Use
`{ channelId: string, driverBotToken: string, sutBotToken: string, sutAppToken: string }`
for Slack rows.
### Adding a channel to QA
The architecture and scenario-helper names for new channel adapters live in [QA overview → Adding a channel](/concepts/qa-e2e-automation#adding-a-channel). The minimum bar: implement the transport runner on the shared `qa-lab` host seam, declare `qaRunners` in the plugin manifest, mount as `openclaw qa <runner>`, and author scenarios under `qa/scenarios/`.

View File

@@ -50,18 +50,9 @@ DigitalOcean is the simplest paid VPS path. If you prefer cheaper or free option
# Install OpenClaw
curl -fsSL https://openclaw.ai/install.sh | bash
# Create the non-root user that will own OpenClaw state and services.
adduser openclaw
usermod -aG sudo openclaw
loginctl enable-linger openclaw
su - openclaw
openclaw --version
```
Use the root shell only for system bootstrap. Run OpenClaw commands as the non-root `openclaw` user so state lives under `/home/openclaw/.openclaw/` and the Gateway installs as that user's systemd service.
</Step>
<Step title="Run onboarding">
@@ -106,8 +97,8 @@ DigitalOcean is the simplest paid VPS path. If you prefer cheaper or free option
**Option B: Tailscale Serve**
```bash
curl -fsSL https://tailscale.com/install.sh | sudo sh
sudo tailscale up
curl -fsSL https://tailscale.com/install.sh | sh
tailscale up
openclaw config set gateway.tailscale.mode serve
openclaw gateway restart
```

View File

@@ -427,7 +427,8 @@ See [ClawDock](/install/clawdock) for the full helper guide.
</Accordion>
<Accordion title="Base image metadata">
The main Docker runtime image uses `node:24-bookworm-slim` and includes `tini` as the entrypoint init process (PID 1) to ensure zombie processes are reaped and signals are handled correctly in long-running containers. It publishes OCI base-image annotations including `org.opencontainers.image.base.name`,
The main Docker runtime image uses `node:24-bookworm-slim` and publishes OCI
base-image annotations including `org.opencontainers.image.base.name`,
`org.opencontainers.image.source`, and others. The Node base digest is
refreshed through Dependabot Docker base-image PRs; release builds do not run
a distro upgrade layer. See

View File

@@ -78,8 +78,6 @@ read_when:
destination = "/data"
```
The OpenClaw Docker image uses `tini` as its entrypoint. Fly process commands replace Docker `CMD` without replacing `ENTRYPOINT`, so the process still runs under `tini`.
**Key settings:**
| Setting | Why |

View File

@@ -81,8 +81,8 @@ Supported keys:
providers: {
openai: {
apiKey: "openai_api_key",
model: "gpt-realtime-2",
voice: "cedar",
model: "gpt-realtime",
voice: "alloy",
},
},
mode: "realtime",
@@ -104,7 +104,6 @@ Defaults:
- `providers.elevenlabs.apiKey`: falls back to `ELEVENLABS_API_KEY` (or gateway shell profile if available).
- `realtime.provider`: selects the active browser/server realtime voice provider. Use `openai` for WebRTC, `google` for provider WebSocket, or a bridge-only provider through Gateway relay.
- `realtime.providers.<provider>` stores provider-owned realtime config. The browser receives only ephemeral or constrained session credentials, never a standard API key.
- `realtime.providers.openai.voice`: built-in OpenAI Realtime voice id. Current `gpt-realtime-2` voices are `alloy`, `ash`, `ballad`, `coral`, `echo`, `sage`, `shimmer`, `verse`, `marin`, and `cedar`; `marin` and `cedar` are recommended for best quality.
- `realtime.brain`: `agent-consult` routes realtime tool calls through Gateway policy; `direct-tools` is owner-only compatibility behavior; `none` is for transcription or external orchestration.
- `talk.catalog` exposes each provider's valid modes, transports, brain strategies, realtime audio formats, and capability flags so first-party Talk clients can avoid unsupported combinations.
- Streaming transcription providers are discovered through `talk.catalog.transcription`. The current Gateway relay uses the Voice Call streaming provider config until the dedicated Talk transcription config surface is added.

View File

@@ -14,7 +14,7 @@ generation, video generation, web fetch, web search, agent tools, or any
combination.
You do not need to add your plugin to the OpenClaw repository. Publish to
[ClawHub](/clawhub) and users install with
[ClawHub](/tools/clawhub) and users install with
`openclaw plugins install clawhub:<package-name>`. Bare package specs still
install from npm during the launch cutover.

View File

@@ -96,6 +96,9 @@ Computer Use available before a thread starts:
agents: {
defaults: {
model: "openai/gpt-5.5",
agentRuntime: {
id: "codex",
},
},
},
}
@@ -111,8 +114,9 @@ register the bundled Codex marketplace from
fails. If setup still cannot make the MCP server available, the turn fails
before the thread starts.
After changing Computer Use config, use `/new` or `/reset` in the affected chat
before testing if an existing Codex thread has already started.
Existing sessions keep their runtime and Codex thread binding. After changing
`agentRuntime` or Computer Use config, use `/new` or `/reset` in the affected
chat before testing.
## Commands

View File

@@ -22,9 +22,9 @@ it only posts to the channel when it calls `message(action="send")`. Set
`messages.visibleReplies: "automatic"` to keep direct-chat final replies on the
legacy automatic delivery path.
Codex heartbeat turns also get `heartbeat_respond` in the searchable OpenClaw
tool catalog by default, so the agent can record whether the wake should stay
quiet or notify without encoding that control flow in final text.
Codex heartbeat turns also get the `heartbeat_respond` tool by default, so the
agent can record whether the wake should stay quiet or notify without encoding
that control flow in final text.
Heartbeat-specific initiative guidance is sent as a Codex collaboration-mode
developer instruction on the heartbeat turn itself. Ordinary chat turns restore
@@ -50,8 +50,7 @@ First sign in with Codex OAuth if you have not already:
openclaw models auth login --provider openai-codex
```
Then enable the bundled `codex` plugin and use the canonical OpenAI model ref.
OpenAI agent turns select the Codex runtime by default:
Then enable the bundled `codex` plugin and force the Codex runtime:
```json5
{
@@ -65,6 +64,9 @@ OpenAI agent turns select the Codex runtime by default:
agents: {
defaults: {
model: "openai/gpt-5.5",
agentRuntime: {
id: "codex",
},
},
},
}
@@ -96,7 +98,7 @@ The bundled `codex` plugin contributes several separate capabilities:
| Capability | How you use it | What it does |
| --------------------------------- | --------------------------------------------------- | ----------------------------------------------------------------------------- |
| Native embedded runtime | `openai/gpt-*` agent model refs | Runs OpenClaw embedded agent turns through Codex app-server. |
| Native embedded runtime | `agentRuntime.id: "codex"` | Runs OpenClaw embedded agent turns through Codex app-server. |
| Native chat-control commands | `/codex bind`, `/codex resume`, `/codex steer`, ... | Binds and controls Codex app-server threads from a messaging conversation. |
| Codex app-server provider/catalog | `codex` internals, surfaced through the harness | Lets the runtime discover and validate app-server models. |
| Codex media-understanding path | `codex/*` image-model compatibility paths | Runs bounded Codex app-server turns for supported image understanding models. |
@@ -108,7 +110,7 @@ Enabling the plugin makes those capabilities available. It does **not**:
realtime
- convert `openai-codex/*` model refs without `openclaw doctor --fix`
- make ACP/acpx the default Codex path
- use stale whole-agent or session runtime pins for routing
- hot-switch existing sessions that already recorded a PI runtime
- replace OpenClaw channel delivery, session files, auth-profile storage, or
message routing
@@ -139,37 +141,35 @@ For the plugin hook semantics themselves, see [Plugin hooks](/plugins/hooks)
and [Plugin guard behavior](/tools/plugin).
OpenAI agent model refs use the harness by default. New configs should keep
OpenAI model refs canonical as `openai/gpt-*`; provider/model
`agentRuntime.id: "codex"` is still valid but no longer required for OpenAI
agent turns. Legacy `codex/*` model refs still auto-select the harness for
compatibility, but
OpenAI model refs canonical as `openai/gpt-*`; `agentRuntime.id: "codex"` is
still valid but no longer required for OpenAI agent turns. Legacy `codex/*`
model refs still auto-select the harness for compatibility, but
runtime-backed legacy provider prefixes are not shown as normal model/provider
choices.
If any configured model route is still `openai-codex/*`, `openclaw doctor --fix`
rewrites it to `openai/*` and preserves existing `openai-codex` auth profile
overrides. It does not pin the whole agent to `agentRuntime.id: "codex"` because
canonical OpenAI refs already select the Codex harness automatically.
rewrites it to `openai/*`. For matching agent routes, it sets the agent runtime
to `codex` and preserves existing `openai-codex` auth profile overrides.
## Route map
Use this table before changing config:
| Desired behavior | Model ref | Runtime config | Auth/profile route | Expected status label |
| ---------------------------------------------------- | -------------------------- | -------------------------------------------------------- | ------------------------------ | ---------------------------- |
| ChatGPT/Codex subscription with native Codex runtime | `openai/gpt-*` | omitted or provider/model `agentRuntime.id: "codex"` | Codex OAuth or Codex account | `Runtime: OpenAI Codex` |
| OpenAI API-key auth for agent models | `openai/gpt-*` | omitted or provider/model `agentRuntime.id: "codex"` | `openai-codex` API-key profile | `Runtime: OpenAI Codex` |
| Legacy config that needs doctor repair | `openai-codex/gpt-*` | preserved or automatic | Existing configured auth | Recheck after `doctor --fix` |
| Mixed providers with conservative auto mode | provider-specific refs | omitted unless a provider/model needs a runtime override | Per selected provider | Depends on selected runtime |
| Explicit Codex ACP adapter session | ACP prompt/model dependent | `sessions_spawn` with `runtime: "acp"` | ACP backend auth | ACP task/session status |
| Desired behavior | Model ref | Runtime config | Auth/profile route | Expected status label |
| ---------------------------------------------------- | -------------------------- | -------------------------------------- | ------------------------------ | ---------------------------- |
| ChatGPT/Codex subscription with native Codex runtime | `openai/gpt-*` | omitted or `agentRuntime.id: "codex"` | Codex OAuth or Codex account | `Runtime: OpenAI Codex` |
| OpenAI API-key auth for agent models | `openai/gpt-*` | omitted or `agentRuntime.id: "codex"` | `openai-codex` API-key profile | `Runtime: OpenAI Codex` |
| Legacy config that needs doctor repair | `openai-codex/gpt-*` | repaired to `codex` | Existing configured auth | Recheck after `doctor --fix` |
| Mixed providers with conservative auto mode | provider-specific refs | `agentRuntime.id: "auto"` | Per selected provider | Depends on selected runtime |
| Explicit Codex ACP adapter session | ACP prompt/model dependent | `sessions_spawn` with `runtime: "acp"` | ACP backend auth | ACP task/session status |
The important split is provider versus runtime:
- `openai-codex/*` is a legacy route that doctor rewrites.
- Provider/model `agentRuntime.id: "codex"` requires the Codex harness and fails
closed if it is unavailable.
- Provider/model `agentRuntime.id: "auto"` lets registered harnesses claim
matching provider routes; OpenAI agent refs resolve to Codex instead of PI.
- `agentRuntime.id: "codex"` requires the Codex harness and fails closed if it
is unavailable.
- `agentRuntime.id: "auto"` lets registered harnesses claim matching provider
routes; OpenAI agent refs resolve to Codex instead of PI.
- `/codex ...` answers "which native Codex conversation should this chat bind
or control?"
- ACP answers "which external harness process should acpx launch?"
@@ -188,14 +188,13 @@ Treat `openai-codex/*` as legacy config that doctor should rewrite:
GPT-5.5 can appear on both direct OpenAI API-key and Codex subscription routes
when your account exposes them. Use `openai/gpt-5.5` with the Codex app-server
harness for native Codex runtime. For direct API-key traffic through PI, opt in
with provider/model `agentRuntime.id: "pi"` and a normal `openai` auth profile.
harness for native Codex runtime, or `openai/gpt-5.5` without a Codex runtime
override for direct API-key traffic.
Legacy `codex/gpt-*` refs remain accepted as compatibility aliases. Doctor
compatibility migration rewrites legacy runtime refs to canonical model refs
and records the runtime policy separately. New native app-server harness configs
should use `openai/gpt-*`; explicit provider/model `agentRuntime.id: "codex"`
is only needed when you want the policy written down.
should use `openai/gpt-*` plus `agentRuntime.id: "codex"`.
`agents.defaults.imageModel` follows the same prefix split. Use
`openai/gpt-*` for the normal OpenAI route and `codex/gpt-*` when image
@@ -214,13 +213,27 @@ in `auto` mode, each plugin candidate's support result.
`openclaw doctor` warns when configured model refs or persisted session route
state still use `openai-codex/*`. `openclaw doctor --fix` rewrites those routes
to `openai/<model>`. Canonical OpenAI agent refs already select the native Codex
harness, so doctor does not pin the whole agent to Codex.
to:
Whole-session and whole-agent runtime pins are legacy state. Runtime selection
now comes from provider/model policy; `openclaw doctor --fix` removes stale
session pins and old whole-agent runtime config so they do not mask the selected
provider/model route.
- `openai/<model>`
- `agentRuntime.id: "codex"`
The `codex` route forces the native Codex harness. PI runtime config is not
allowed for OpenAI agent model turns.
Doctor also repairs stale persisted session pins across discovered agent session
stores so old conversations do not stay wedged on the removed route.
Harness selection is not a live session control. When an embedded turn runs,
OpenClaw records the selected harness id on that session and keeps using it for
later turns in the same session id. Change `agentRuntime` config or
`OPENCLAW_AGENT_RUNTIME` when you want future sessions to use another harness;
use `/new` or `/reset` to start a fresh session before switching an existing
conversation between PI and Codex. This avoids replaying one transcript through
two incompatible native session systems.
Legacy sessions created before harness pins are treated as PI-pinned once they
have transcript history. Use `/new` or `/reset` to opt that conversation into
Codex after changing config.
`/status` shows the effective model runtime. The default PI harness appears as
`Runtime: OpenClaw Pi Default`, and the Codex app-server harness appears as
@@ -261,21 +274,22 @@ Codex behavior-shaping lane without duplicating `AGENTS.md`.
## Add Codex alongside other models
Do not set a whole-agent runtime. Whole-agent runtime pins are legacy and
ignored, and they were the source of mixed-provider traps after upgrades. Keep
runtime policy on the provider or model that needs it.
Do not set `agentRuntime.id: "codex"` globally if the same agent should freely switch
between Codex and non-Codex provider models. A forced runtime applies to every
embedded turn for that agent or session. If you select an Anthropic model while
that runtime is forced, OpenClaw still tries the Codex harness and fails closed
instead of silently routing that turn through PI.
Use one of these shapes instead:
- Use `openai/gpt-*` for OpenAI agent turns; Codex is selected by default.
- Put runtime overrides on `models.providers.<provider>.agentRuntime` or on a
model entry such as `agents.defaults.models["anthropic/claude-opus-4-7"].agentRuntime`.
- Put Codex on a dedicated agent with `agentRuntime.id: "codex"`.
- Keep the default agent on `agentRuntime.id: "auto"` and PI fallback for normal mixed
provider usage.
- Use legacy `codex/*` refs only for compatibility. New configs should prefer
`openai/*`; add an explicit Codex runtime policy only when you need to make
the provider/model rule strict.
`openai/*` plus an explicit Codex runtime policy.
For example, this keeps mixed-provider routing ergonomic while using OpenAI
through Codex by default and Claude through PI:
For example, this keeps the default agent on normal automatic selection and
adds a separate Codex agent:
```json5
{
@@ -288,7 +302,9 @@ through Codex by default and Claude through PI:
},
agents: {
defaults: {
model: "anthropic/claude-opus-4-6",
agentRuntime: {
id: "auto",
},
},
list: [
{
@@ -300,6 +316,9 @@ through Codex by default and Claude through PI:
id: "codex",
name: "Codex",
model: "openai/gpt-5.5",
agentRuntime: {
id: "codex",
},
},
],
},
@@ -336,36 +355,45 @@ routing.
## Codex-only deployments
For OpenAI agent turns, `openai/gpt-*` already resolves to Codex. If you need a
strict written policy, put it on the OpenAI provider or model. Explicit plugin
runtimes fail closed and are never silently retried through PI:
Force the Codex harness when you need to prove that every embedded agent turn
uses Codex. Explicit plugin runtimes fail closed and are never silently retried
through PI:
```json5
{
models: {
providers: {
openai: {
agentRuntime: {
id: "codex",
},
agents: {
defaults: {
model: "openai/gpt-5.5",
agentRuntime: {
id: "codex",
},
},
},
agents: { defaults: { model: "openai/gpt-5.5" } },
}
```
Environment override:
```bash
OPENCLAW_AGENT_RUNTIME=codex openclaw gateway run
```
With Codex forced, OpenClaw fails early if the Codex plugin is disabled, the
app-server is too old, or the app-server cannot start.
## Per-agent Codex
You can make one agent Codex-strict while the default agent keeps normal
selection by using a per-agent model runtime override:
You can make one agent Codex-only while the default agent keeps normal
auto-selection:
```json5
{
agents: {
defaults: {
agentRuntime: {
id: "auto",
},
},
list: [
{
id: "main",
@@ -376,12 +404,8 @@ selection by using a per-agent model runtime override:
id: "codex",
name: "Codex",
model: "openai/gpt-5.5",
models: {
"openai/gpt-5.5": {
agentRuntime: {
id: "codex",
},
},
agentRuntime: {
id: "codex",
},
},
],
@@ -460,13 +484,7 @@ By default, OpenClaw starts local Codex harness sessions in YOLO mode:
`approvalPolicy: "never"`, `approvalsReviewer: "user"`, and
`sandbox: "danger-full-access"`. This is the trusted local operator posture used
for autonomous heartbeats: Codex can use shell and network tools without
stopping on native approval prompts that nobody is around to answer. On local
stdio Codex app-server installs where Codex's system requirements file
disallows the implicit YOLO approval, reviewer, or sandbox value, OpenClaw
treats the implicit default as guardian instead and selects allowed guardian
permissions so it does not send an override that Codex app-server will reject.
Hostname-matching `[[remote_sandbox_config]]` entries in the same requirements
file are honored for the sandbox default decision.
stopping on native approval prompts that nobody is around to answer.
To opt in to Codex guardian-reviewed approvals, set `appServer.mode:
"guardian"`:
@@ -480,7 +498,7 @@ To opt in to Codex guardian-reviewed approvals, set `appServer.mode:
config: {
appServer: {
mode: "guardian",
serviceTier: "priority",
serviceTier: "fast",
},
},
},
@@ -545,11 +563,9 @@ openclaw migrate apply codex --yes
```
The Codex migration provider copies skills into the current OpenClaw agent
workspace. For source-installed `openai-curated` Codex plugins, migration also
calls Codex app-server `plugin/install` and records explicit native plugin
config under `plugins.entries.codex.config.codexPlugins`. Codex config files,
hooks, and cached plugin bundles that are not source-installed curated plugins
remain report-only manual-review items.
workspace. Codex native plugins, hooks, and config files are reported or archived
for manual review instead of being activated automatically, because they can
execute commands, expose MCP servers, or carry credentials.
Auth is selected in this order:
@@ -590,49 +606,38 @@ If a deployment needs additional environment isolation, add those variables to
`appServer.clearEnv` only affects the spawned Codex app-server child process.
Codex dynamic tools default to the `native-first` profile and `searchable`
loading. In that mode, OpenClaw does not expose dynamic tools that duplicate
Codex-native workspace operations: `read`, `write`, `edit`, `apply_patch`,
`exec`, `process`, and `update_plan`. Remaining OpenClaw integration tools such
as messaging, sessions, media, cron, browser, nodes, gateway,
`heartbeat_respond`, and `web_search` are available through Codex tool search
under the `openclaw` namespace, keeping the initial model context smaller.
`sessions_yield` and message-tool-only source replies stay direct because those
are turn-control contracts. Heartbeat collaboration instructions tell Codex to
search for `heartbeat_respond` before ending a heartbeat turn when the tool is
not already loaded.
Set `codexDynamicToolsLoading: "direct"` only when connecting to a custom Codex
app-server that cannot search deferred dynamic tools or when debugging the full
tool payload.
Codex dynamic tools default to the `native-first` profile. In that mode,
OpenClaw does not expose dynamic tools that duplicate Codex-native workspace
operations: `read`, `write`, `edit`, `apply_patch`, `exec`, `process`, and
`update_plan`. OpenClaw integration tools such as messaging, sessions, media,
cron, browser, nodes, gateway, `heartbeat_respond`, and `web_search` remain
available.
Supported top-level Codex plugin fields:
| Field | Default | Meaning |
| -------------------------- | ---------------- | ----------------------------------------------------------------------------------------- |
| `codexDynamicToolsProfile` | `"native-first"` | Use `"openclaw-compat"` to expose the full OpenClaw dynamic tool set to Codex app-server. |
| `codexDynamicToolsLoading` | `"searchable"` | Use `"direct"` to put OpenClaw dynamic tools directly in the initial Codex tool context. |
| `codexDynamicToolsExclude` | `[]` | Additional OpenClaw dynamic tool names to omit from Codex app-server turns. |
| `codexPlugins` | disabled | Native Codex plugin/app support for migrated source-installed curated plugins. |
Supported `appServer` fields:
| Field | Default | Meaning |
| ----------------------------- | ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `transport` | `"stdio"` | `"stdio"` spawns Codex; `"websocket"` connects to `url`. |
| `command` | managed Codex binary | Executable for stdio transport. Leave unset to use the managed binary; set it only for an explicit override. |
| `args` | `["app-server", "--listen", "stdio://"]` | Arguments for stdio transport. |
| `url` | unset | WebSocket app-server URL. |
| `authToken` | unset | Bearer token for WebSocket transport. |
| `headers` | `{}` | Extra WebSocket headers. |
| `clearEnv` | `[]` | Extra environment variable names removed from the spawned stdio app-server process after OpenClaw builds its inherited environment. `CODEX_HOME` and `HOME` are reserved for OpenClaw's per-agent Codex isolation on local launches. |
| `requestTimeoutMs` | `60000` | Timeout for app-server control-plane calls. |
| `turnCompletionIdleTimeoutMs` | `60000` | Quiet window after a turn-scoped Codex app-server request while OpenClaw waits for `turn/completed`. Raise this for slow post-tool or status-only synthesis phases. |
| `mode` | `"yolo"` unless local Codex requirements disallow YOLO | Preset for YOLO or guardian-reviewed execution. Local stdio requirements that omit `danger-full-access`, `never` approval, or the `user` reviewer make the implicit default guardian. |
| `approvalPolicy` | `"never"` or an allowed guardian approval policy | Native Codex approval policy sent to thread start/resume/turn. Guardian defaults prefer `"on-request"` when allowed. |
| `sandbox` | `"danger-full-access"` or an allowed guardian sandbox | Native Codex sandbox mode sent to thread start/resume. Guardian defaults prefer `"workspace-write"` when allowed, otherwise `"read-only"`. |
| `approvalsReviewer` | `"user"` or an allowed guardian reviewer | Use `"auto_review"` to let Codex review native approval prompts when allowed, otherwise `guardian_subagent` or `user`. `guardian_subagent` remains a legacy alias. |
| `serviceTier` | unset | Optional Codex app-server service tier. `"priority"` enables fast-mode routing, `"flex"` requests flex processing, `null` clears the override, and legacy `"fast"` is accepted as `"priority"`. |
| Field | Default | Meaning |
| ----------------------------- | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `transport` | `"stdio"` | `"stdio"` spawns Codex; `"websocket"` connects to `url`. |
| `command` | managed Codex binary | Executable for stdio transport. Leave unset to use the managed binary; set it only for an explicit override. |
| `args` | `["app-server", "--listen", "stdio://"]` | Arguments for stdio transport. |
| `url` | unset | WebSocket app-server URL. |
| `authToken` | unset | Bearer token for WebSocket transport. |
| `headers` | `{}` | Extra WebSocket headers. |
| `clearEnv` | `[]` | Extra environment variable names removed from the spawned stdio app-server process after OpenClaw builds its inherited environment. `CODEX_HOME` and `HOME` are reserved for OpenClaw's per-agent Codex isolation on local launches. |
| `requestTimeoutMs` | `60000` | Timeout for app-server control-plane calls. |
| `turnCompletionIdleTimeoutMs` | `60000` | Quiet window after a turn-scoped Codex app-server request while OpenClaw waits for `turn/completed`. Raise this for slow post-tool or status-only synthesis phases. |
| `mode` | `"yolo"` | Preset for YOLO or guardian-reviewed execution. |
| `approvalPolicy` | `"never"` | Native Codex approval policy sent to thread start/resume/turn. |
| `sandbox` | `"danger-full-access"` | Native Codex sandbox mode sent to thread start/resume. |
| `approvalsReviewer` | `"user"` | Use `"auto_review"` to let Codex review native approval prompts. `guardian_subagent` remains a legacy alias. |
| `serviceTier` | unset | Optional Codex app-server service tier: `"fast"`, `"flex"`, or `null`. Invalid legacy values are ignored. |
OpenClaw-owned dynamic tool calls are bounded independently from
`appServer.requestTimeoutMs`: each Codex `item/tool/call` request must receive
@@ -669,106 +674,6 @@ Environment overrides remain available for local testing:
preferred for repeatable deployments because it keeps the plugin behavior in the
same reviewed file as the rest of the Codex harness setup.
## Native Codex plugins
Native Codex plugin support uses Codex app-server's own app and plugin
capabilities in the same Codex thread as the OpenClaw harness turn. OpenClaw
does not translate Codex plugins into synthetic `codex_plugin_*` OpenClaw
dynamic tools. That keeps plugin calls in the native Codex transcript and avoids
starting a second ephemeral Codex thread for each plugin invocation.
Codex plugins only work when the selected OpenClaw agent runtime is the native
Codex harness. The `codexPlugins` config has no effect on Pi runs, normal
OpenAI provider runs, ACP conversation bindings, or other harnesses, because
those paths do not create Codex app-server threads with native `apps` config.
V1 support is intentionally narrow:
- Only `openai-curated` plugins that were already installed in the source Codex
app-server inventory are migration-eligible.
- Migration writes explicit plugin identities with `marketplaceName` and
`pluginName`; it does not write local `marketplacePath` cache paths.
- `codexPlugins.enabled` is the global enablement switch. There is no
`plugins["*"]` wildcard and no config key that grants arbitrary install
authority.
- Unsupported marketplaces, cached plugin bundles, hooks, and Codex config files
are preserved in the migration report for manual review.
Example migrated config:
```json5
{
plugins: {
entries: {
codex: {
enabled: true,
config: {
codexPlugins: {
enabled: true,
allow_destructive_actions: false,
plugins: {
"google-calendar": {
enabled: true,
marketplaceName: "openai-curated",
pluginName: "google-calendar",
},
},
},
},
},
},
},
}
```
Thread app config is computed when OpenClaw establishes a Codex harness session
or replaces a stale Codex thread binding. It is not recomputed on every turn.
After changing `codexPlugins`, use `/new`, `/reset`, or restart the gateway so
future Codex harness sessions start with the updated app set.
OpenClaw reads Codex app inventory through app-server `app/list`, caches it for
one hour, and refreshes stale or missing entries asynchronously. A plugin app is
exposed only when OpenClaw can map it back to the migrated plugin through stable
ownership: an exact app id from plugin detail, a known MCP server name, or
unique stable metadata. Display-name-only or ambiguous ownership is excluded
until the next inventory refresh proves ownership.
Plugin-owned app tools use Codex's native app configuration. OpenClaw injects a
restrictive `config.apps` patch for the Codex thread: `_default` is disabled and
only apps owned by enabled migrated plugins are enabled. OpenClaw sets
app-level `destructive_enabled` from the effective global/per-plugin
`allow_destructive_actions` policy and lets Codex enforce destructive tool
metadata from its native app tool annotations. Plugin apps are emitted with
`open_world_enabled: true`; OpenClaw does not expose a separate plugin
open-world policy knob. OpenClaw does not maintain per-plugin destructive
tool-name deny lists. Tool approval mode is prompted by default for plugin
apps, because OpenClaw does not have an interactive app-elicitation UI in this
same-thread path.
Destructive plugin elicitations fail closed by default:
- Global `allow_destructive_actions` defaults to `false`.
- Per-plugin `allow_destructive_actions` overrides the global policy for that
plugin.
- When policy is `false`, OpenClaw returns a deterministic decline.
- When policy is `true`, OpenClaw auto-accepts only safe schemas it can map to
an approval response, such as a boolean approve field.
- Missing plugin identity, ambiguous ownership, a missing turn id, a wrong turn
id, or an unsafe elicitation schema declines instead of prompting.
Common diagnostics:
- `auth_required`: migration installed the plugin but one of its apps still
needs authentication. The explicit plugin entry is written disabled until you
reauthorize and enable it.
- `marketplace_missing` or `plugin_missing`: the target Codex app-server cannot
see the expected `openai-curated` marketplace or plugin.
- `app_inventory_missing` or `app_inventory_stale`: app readiness came from an
empty or stale cache; OpenClaw schedules an async refresh and excludes plugin
apps until ownership/readiness is known.
- `app_ownership_ambiguous`: app inventory only matched by display name, so the
app is not exposed to the Codex thread.
## Computer use
Computer Use is covered in its own setup guide:
@@ -803,6 +708,9 @@ Minimal config:
agents: {
defaults: {
model: "openai/gpt-5.5",
agentRuntime: {
id: "codex",
},
},
},
}
@@ -849,18 +757,12 @@ Codex-only harness validation:
```json5
{
models: {
providers: {
openai: {
agentRuntime: {
id: "codex",
},
},
},
},
agents: {
defaults: {
model: "openai/gpt-5.5",
agentRuntime: {
id: "codex",
},
},
},
plugins: {
@@ -1164,16 +1066,16 @@ understanding continue to use the matching provider/model settings such as
## Troubleshooting
**Codex does not appear as a normal `/model` provider:** that is expected for
new configs. Select an `openai/gpt-*` model, enable
new configs. Select an `openai/gpt-*` model with
`agentRuntime.id: "codex"` (or a legacy `codex/*` ref), enable
`plugins.entries.codex.enabled`, and check whether `plugins.allow` excludes
`codex`. Legacy `codex/*` refs remain compatibility aliases, not normal model
provider choices.
`codex`.
**OpenClaw uses PI instead of Codex:** make sure the model ref is `openai/gpt-*`
on the official OpenAI provider and that the Codex plugin is installed/enabled.
If you need a strict policy while testing, set provider/model
`agentRuntime.id: "codex"`. A forced Codex runtime fails instead of falling back
to PI. Once Codex app-server is selected, its failures surface directly.
**OpenClaw uses PI instead of Codex:** `agentRuntime.id: "auto"` can still use PI as the
compatibility backend when no Codex harness claims the run. Set
`agentRuntime.id: "codex"` to force Codex selection while testing. A
forced Codex runtime fails instead of falling back to PI. Once Codex app-server
is selected, its failures surface directly.
**The app-server is rejected:** upgrade Codex so the app-server handshake
reports version `0.125.0` or newer. Same-version prereleases or build-suffixed
@@ -1186,11 +1088,11 @@ or disable discovery.
**WebSocket transport fails immediately:** check `appServer.url`, `authToken`,
and that the remote app-server speaks the same Codex app-server protocol version.
**A non-Codex model uses PI:** that is expected unless provider/model runtime
policy routes it to another harness. Plain non-OpenAI provider refs stay on
their normal provider path in `auto` mode. If you force
`agentRuntime.id: "codex"` on a provider or model, matching embedded turns must
be Codex-supported OpenAI models.
**A non-Codex model uses PI:** that is expected unless you forced
`agentRuntime.id: "codex"` for that agent or selected a legacy
`codex/*` ref. Plain `openai/gpt-*` and other provider refs stay on their normal
provider path in `auto` mode. If you force `agentRuntime.id: "codex"`, every embedded
turn for that agent must be a Codex-supported OpenAI model.
**Computer Use is installed but tools do not run:** check
`/codex computer-use status` from a fresh session. If a tool reports

View File

@@ -8,7 +8,7 @@ title: "Community plugins"
Community plugins are third-party packages that extend OpenClaw with new
channels, tools, providers, or other capabilities. They are built and maintained
by the community, usually published on [ClawHub](/clawhub), and installable
by the community, usually published on [ClawHub](/tools/clawhub), and installable
with a single command. Npm remains the launch default for bare package specs
while ClawHub pack installs roll out.
@@ -152,7 +152,7 @@ We welcome community plugins that are useful, documented, and safe to operate.
<Steps>
<Step title="Publish to ClawHub or npm">
Your plugin must be installable via `openclaw plugins install \<package-name\>`.
Publish to [ClawHub](/clawhub) unless you specifically need npm-only
Publish to [ClawHub](/tools/clawhub) unless you specifically need npm-only
distribution.
See [Building Plugins](/plugins/building-plugins) for the full guide.

View File

@@ -187,6 +187,6 @@ forces npm resolution.
- [Plugins](/tools/plugin) - overview and troubleshooting
- [`openclaw plugins`](/cli/plugins) - full CLI reference
- [ClawHub](/clawhub/cli) - publish and registry operations
- [ClawHub](/tools/clawhub) - publish and registry operations
- [Building plugins](/plugins/building-plugins) - create a plugin package
- [Plugin manifest](/plugins/manifest) - manifest and package metadata

View File

@@ -103,11 +103,14 @@ export default definePluginEntry({
OpenClaw chooses a harness after provider/model resolution:
1. Model-scoped runtime policy wins.
2. Provider-scoped runtime policy comes next.
3. `auto` asks registered harnesses if they support the resolved
provider/model.
4. If no registered harness matches, OpenClaw uses PI unless PI fallback is
1. An existing session's recorded harness id wins, so config/env changes do not
hot-switch that transcript to another runtime.
2. `OPENCLAW_AGENT_RUNTIME=<id>` forces a registered harness with that id for
sessions that are not already pinned.
3. `OPENCLAW_AGENT_RUNTIME=pi` forces the built-in PI harness.
4. `OPENCLAW_AGENT_RUNTIME=auto` asks registered harnesses if they support the
resolved provider/model.
5. If no registered harness matches, OpenClaw uses PI unless PI fallback is
disabled.
Plugin harness failures surface as run failures. In `auto` mode, PI fallback is
@@ -116,10 +119,11 @@ provider/model. Once a plugin harness has claimed a run, OpenClaw does not
replay that same turn through PI because that can change auth/runtime semantics
or duplicate side effects.
Whole-session and whole-agent runtime pins are ignored by selection. That
includes stale session `agentHarnessId` values, `agents.defaults.agentRuntime`,
`agents.list[].agentRuntime`, and `OPENCLAW_AGENT_RUNTIME`. `/status` shows the
effective runtime selected from the provider/model route.
The selected harness id is persisted with the session id after an embedded run.
Legacy sessions created before harness pins are treated as PI-pinned once they
have transcript history. Use a new/reset session when changing between PI and a
native plugin harness. `/status` shows non-default harness ids such as `codex`
next to `Fast`; PI stays hidden because it is the default compatibility path.
If the selected harness is surprising, enable `agents/harness` debug logging and
inspect the gateway's structured `agent harness selected` record. It includes
the selected harness id, selection reason, runtime/fallback policy, and, in
@@ -137,7 +141,8 @@ OpenClaw. The harness then claims that provider in `supports(...)`.
The bundled Codex plugin follows this pattern:
- preferred user model refs: `openai/gpt-5.5`
- preferred user model refs: `openai/gpt-5.5` plus
`agentRuntime.id: "codex"`
- compatibility refs: legacy `codex/gpt-*` refs remain accepted, but new
configs should not use them as normal provider/model refs
- harness id: `codex`
@@ -146,9 +151,10 @@ The bundled Codex plugin follows this pattern:
- app-server request: OpenClaw sends the bare model id to Codex and lets the
harness talk to the native app-server protocol
The Codex plugin is additive. Plain `openai/gpt-*` agent refs on the official
OpenAI provider select the Codex harness by default. Older `codex/gpt-*` refs
still select the Codex provider and harness for compatibility.
The Codex plugin is additive. Plain `openai/gpt-*` refs continue to use the
normal OpenClaw provider path unless you force the Codex harness with
`agentRuntime.id: "codex"`. Older `codex/gpt-*` refs still select the
Codex provider and harness for compatibility.
For operator setup, model prefix examples, and Codex-only configs, see
[Codex Harness](/plugins/codex-harness).
@@ -196,88 +202,28 @@ aliases for the native harness.
When this mode runs, Codex owns the native thread id, resume behavior,
compaction, and app-server execution. OpenClaw still owns the chat channel,
visible transcript mirror, tool policy, approvals, media delivery, and session
selection. Use provider/model `agentRuntime.id: "codex"` when you need to prove
that only the Codex app-server path can claim the run. Explicit plugin runtimes
fail closed; Codex app-server selection failures and runtime failures are not
retried through PI.
selection. Use `agentRuntime.id: "codex"` when you need to prove that only the
Codex app-server path can claim the run. Explicit plugin runtimes fail closed;
Codex app-server selection failures and runtime failures are not retried through
PI.
## Runtime strictness
By default, OpenClaw uses `auto` provider/model runtime policy: registered
plugin harnesses can claim a provider/model pair, and PI handles the turn when
none match. OpenAI agent refs on the official OpenAI provider default to Codex.
Use an explicit provider/model plugin runtime such as
By default, OpenClaw runs embedded agents with OpenClaw Pi. In `auto` mode,
registered plugin harnesses can claim a provider/model pair, and PI handles the
turn when none match. Use an explicit plugin runtime such as
`agentRuntime.id: "codex"` when missing harness selection should fail instead
of routing through PI. Selected plugin harness failures always fail hard. This
does not block an explicit provider/model `agentRuntime.id: "pi"`.
does not block an explicit `agentRuntime.id: "pi"` or
`OPENCLAW_AGENT_RUNTIME=pi`.
For Codex-only embedded runs:
```json
{
"models": {
"providers": {
"openai": {
"agentRuntime": {
"id": "codex"
}
}
}
},
"agents": {
"defaults": {
"model": "openai/gpt-5.5"
}
}
}
```
If you want a CLI backend for one canonical model, put the runtime on that
model entry:
```json
{
"agents": {
"defaults": {
"model": "anthropic/claude-opus-4-7",
"models": {
"anthropic/claude-opus-4-7": {
"agentRuntime": {
"id": "claude-cli"
}
}
}
}
}
}
```
Per-agent overrides use the same model-scoped shape:
```json
{
"agents": {
"list": [
{
"id": "codex-only",
"model": "openai/gpt-5.5",
"models": {
"openai/gpt-5.5": {
"agentRuntime": { "id": "codex" }
}
}
}
]
}
}
```
Legacy whole-agent runtime examples like this are ignored:
```json
{
"agents": {
"defaults": {
"model": "openai/gpt-5.5",
"agentRuntime": {
"id": "codex"
}
@@ -286,6 +232,46 @@ Legacy whole-agent runtime examples like this are ignored:
}
```
If you want any registered plugin harness to claim matching models and otherwise
use PI, set `id: "auto"`:
```json
{
"agents": {
"defaults": {
"agentRuntime": {
"id": "auto"
}
}
}
}
```
Per-agent overrides use the same shape:
```json
{
"agents": {
"defaults": {
"agentRuntime": { "id": "auto" }
},
"list": [
{
"id": "codex-only",
"model": "openai/gpt-5.5",
"agentRuntime": { "id": "codex" }
}
]
}
}
```
`OPENCLAW_AGENT_RUNTIME` still overrides the configured runtime.
```bash
OPENCLAW_AGENT_RUNTIME=codex openclaw gateway run
```
With an explicit plugin runtime, a session fails early when the requested
harness is not registered, does not support the resolved provider/model, or
fails before producing turn side effects. That is intentional for Codex-only

View File

@@ -133,32 +133,6 @@ Provider and channel execution paths must use the active runtime config snapshot
const provider = api.runtime.agent.defaults.provider; // e.g. "anthropic"
```
</Accordion>
<Accordion title="api.runtime.llm">
Run a host-owned text completion without importing provider internals or
duplicating OpenClaw model/auth/base URL preparation.
```typescript
const result = await api.runtime.llm.complete({
messages: [{ role: "user", content: "Summarize this transcript." }],
purpose: "my-plugin.summary",
maxTokens: 512,
temperature: 0.2,
});
```
The helper uses the same simple-completion preparation path as OpenClaw's
built-in runtime and the host-owned runtime config snapshot. Context engines
receive a session-bound `llm.complete` capability, so model calls use the
active session's agent and do not silently fall back to the default agent. The
result includes provider/model/agent attribution plus normalized token,
cache, and estimated cost usage when available.
<Warning>
Model overrides require operator opt-in via `plugins.entries.<id>.llm.allowModelOverride: true` in config. Use `plugins.entries.<id>.llm.allowedModels` to restrict trusted plugins to specific canonical `provider/model` targets. Cross-agent completions require `plugins.entries.<id>.llm.allowAgentIdOverride: true`.
</Warning>
</Accordion>
<Accordion title="api.runtime.subagent">
Launch and manage background subagent runs.

View File

@@ -492,7 +492,7 @@ The `ChannelSetupWizard` type supports `credentials`, `textInputs`, `dmPolicy`,
## Publishing and installing
**External plugins:** publish to [ClawHub](/clawhub), then install:
**External plugins:** publish to [ClawHub](/tools/clawhub), then install:
<Tabs>
<Tab title="npm">

View File

@@ -46,7 +46,6 @@ For the plugin authoring guide, see [Plugin SDK overview](/plugins/sdk-overview)
| --- | --- |
| `plugin-sdk/channel-core` | `defineChannelPluginEntry`, `defineSetupPluginEntry`, `createChatChannelPlugin`, `createChannelPluginBase` |
| `plugin-sdk/config-schema` | Root `openclaw.json` Zod schema export (`OpenClawSchema`) |
| `plugin-sdk/json-schema-runtime` | Cached JSON Schema validation helper for plugin-owned schemas |
| `plugin-sdk/channel-setup` | `createOptionalChannelSetupSurface`, `createOptionalChannelSetupAdapter`, `createOptionalChannelSetupWizard`, plus `DEFAULT_ACCOUNT_ID`, `createTopLevelChannelDmPolicy`, `setSetupChannelEnabled`, `splitSetupEntries` |
| `plugin-sdk/setup` | Shared setup wizard helpers, allowlist prompts, setup status builders |
| `plugin-sdk/setup-runtime` | `createPatchedAccountSetupAdapter`, `createEnvPatchedAccountSetupAdapter`, `createSetupInputPresenceValidator`, `noteChannelLookupFailure`, `noteChannelLookupSummary`, `promptResolvedAllowFrom`, `splitSetupEntries`, `createAllowlistSetupWizardProxy`, `createDelegatedSetupWizardProxy` |
@@ -265,7 +264,6 @@ For the plugin authoring guide, see [Plugin SDK overview](/plugins/sdk-overview)
| Subpath | Key exports |
| --- | --- |
| `plugin-sdk/media-runtime` | Shared media fetch/transform/store helpers, ffprobe-backed video dimension probing, and media payload builders |
| `plugin-sdk/media-mime` | Narrow MIME normalization, file-extension mapping, MIME detection, and media-kind helpers |
| `plugin-sdk/media-store` | Narrow media store helpers such as `saveMediaBuffer` |
| `plugin-sdk/media-generation-runtime` | Shared media-generation failover helpers, candidate selection, and missing-model messaging |
| `plugin-sdk/media-understanding` | Media understanding provider types plus provider-facing image/audio helper exports |

View File

@@ -83,4 +83,4 @@ Channel message actions also support `react` for message reactions.
## Related
- [Building plugins](/plugins/building-plugins)
- [ClawHub](/clawhub)
- [Community plugins](/plugins/community)

View File

@@ -106,11 +106,7 @@ Anthropic's current public docs:
agents: {
defaults: {
model: { primary: "anthropic/claude-opus-4-7" },
models: {
"anthropic/claude-opus-4-7": {
agentRuntime: { id: "claude-cli" },
},
},
agentRuntime: { id: "claude-cli" },
},
},
}
@@ -118,7 +114,7 @@ Anthropic's current public docs:
Legacy `claude-cli/claude-opus-4-7` model refs still work for
compatibility, but new config should keep provider/model selection as
`anthropic/*` and put the execution backend in provider/model runtime policy.
`anthropic/*` and put the execution backend in `agentRuntime.id`.
<Tip>
If you want the clearest billing path, use an Anthropic API key instead. OpenClaw also supports subscription-style options from [OpenAI Codex](/providers/openai), [Qwen Cloud](/providers/qwen), [MiniMax](/providers/minimax), and [Z.AI / GLM](/providers/glm).

View File

@@ -256,49 +256,6 @@ openclaw models list
</Accordion>
<Accordion title="Service tier">
Some Bedrock models support a `service_tier` parameter to optimize for cost
or latency. The following tiers are available:
| Tier | Description |
|------|-------------|
| `default` | Standard Bedrock tier |
| `flex` | Discounted processing for workloads that can tolerate longer latency |
| `priority` | Prioritized processing for latency-sensitive workloads |
| `reserved` | Reserved capacity for steady-state workloads |
Set `serviceTier` (or `service_tier`) via `agents.defaults.params` for
Bedrock model requests, or per-model in
`agents.defaults.models["<model-key>"].params`:
```json5
{
agents: {
defaults: {
params: {
serviceTier: "flex", // applies to all models
},
models: {
"amazon-bedrock/mistral.mistral-large-3-675b-instruct": {
params: {
serviceTier: "priority", // per-model override
},
},
},
},
},
}
```
Valid values are `default`, `flex`, `priority`, and `reserved`. Not all
models support all tiers — if an unsupported tier is requested, Bedrock will
return a validation error. Note: the error message is somewhat misleading;
it may say "The provided model identifier is invalid" rather than indicating
an unsupported service tier. If you see this error, check whether the model
supports the requested tier.
</Accordion>
<Accordion title="Claude Opus 4.7 temperature">
Bedrock rejects the `temperature` parameter for Claude Opus 4.7. OpenClaw
omits `temperature` automatically for any Opus 4.7 Bedrock ref, including

View File

@@ -13,7 +13,7 @@ Gemini Grounding.
- Provider: `google`
- Auth: `GEMINI_API_KEY` or `GOOGLE_API_KEY`
- API: Google Gemini API
- Runtime option: provider/model `agentRuntime.id: "google-gemini-cli"`
- Runtime option: `agents.defaults.agentRuntime.id: "google-gemini-cli"`
reuses Gemini CLI OAuth while keeping model refs canonical as `google/*`.
## Getting started
@@ -398,10 +398,9 @@ Gateway relay transport, which keeps provider credentials on the Gateway.
For maintainer live verification, run
`OPENAI_API_KEY=... GEMINI_API_KEY=... node --import tsx scripts/dev/realtime-talk-live-smoke.ts`.
The smoke also covers OpenAI backend/WebRTC paths; the Google leg mints the same
constrained Live API token shape used by Control UI Talk, opens the browser
WebSocket endpoint, sends the initial setup payload, and waits for
`setupComplete`.
The Google leg mints the same constrained Live API token shape used by Control
UI Talk, opens the browser WebSocket endpoint, sends the initial setup payload,
and waits for `setupComplete`.
## Advanced configuration

View File

@@ -36,9 +36,9 @@ changing config.
| ---------------------------------------------------- | ------------------------------------------------------- | --------------------------------------------------------------------- |
| ChatGPT/Codex subscription with native Codex runtime | `openai/gpt-5.5` | Default OpenAI agent setup. Sign in with `openai-codex` auth. |
| Direct API-key billing for agent models | `openai/gpt-5.5` plus an `openai-codex` API-key profile | Use `auth.order.openai-codex` to prefer that profile. |
| Direct API-key billing through explicit PI | `openai/gpt-5.5` plus provider/model runtime `pi` | Select a normal `openai` API-key profile. |
| Direct API-key billing through explicit PI | `openai/gpt-5.5` plus `agentRuntime.id: "pi"` | Select a normal `openai` API-key profile. |
| Latest ChatGPT Instant API alias | `openai/chat-latest` | Direct API-key only. Moving alias for experiments, not the default. |
| ChatGPT/Codex subscription auth through explicit PI | `openai/gpt-5.5` plus provider/model runtime `pi` | Select an `openai-codex` auth profile for the compatibility route. |
| ChatGPT/Codex subscription auth through explicit PI | `openai/gpt-5.5` plus `agentRuntime.id: "pi"` | Select an `openai-codex` auth profile for the compatibility route. |
| Image generation or editing | `openai/gpt-image-2` | Works with either `OPENAI_API_KEY` or OpenAI Codex OAuth. |
| Transparent-background images | `openai/gpt-image-1.5` | Use `outputFormat=png` or `webp` and `openai.background=transparent`. |
@@ -46,14 +46,14 @@ changing config.
The names are similar but not interchangeable:
| Name you see | Layer | Meaning |
| --------------------------------------- | ------------------- | ------------------------------------------------------------------------------------------------- |
| `openai` | Provider prefix | Canonical OpenAI model route; agent turns use the Codex runtime. |
| `openai-codex` | Auth/profile prefix | OpenAI Codex OAuth/subscription auth profile provider. |
| `codex` plugin | Plugin | Bundled OpenClaw plugin that provides native Codex app-server runtime and `/codex` chat controls. |
| provider/model `agentRuntime.id: codex` | Agent runtime | Force the native Codex app-server harness for matching embedded turns. |
| `/codex ...` | Chat command set | Bind/control Codex app-server threads from a conversation. |
| `runtime: "acp", agentId: "codex"` | ACP session route | Explicit fallback path that runs Codex through ACP/acpx. |
| Name you see | Layer | Meaning |
| ---------------------------------- | ------------------- | ------------------------------------------------------------------------------------------------- |
| `openai` | Provider prefix | Canonical OpenAI model route; agent turns use the Codex runtime. |
| `openai-codex` | Auth/profile prefix | OpenAI Codex OAuth/subscription auth profile provider. |
| `codex` plugin | Plugin | Bundled OpenClaw plugin that provides native Codex app-server runtime and `/codex` chat controls. |
| `agentRuntime.id: codex` | Agent runtime | Force the native Codex app-server harness for embedded turns. |
| `/codex ...` | Chat command set | Bind/control Codex app-server threads from a conversation. |
| `runtime: "acp", agentId: "codex"` | ACP session route | Explicit fallback path that runs Codex through ACP/acpx. |
This means a config can intentionally contain both `openai/*` model refs and
`openai-codex` auth profiles. `openclaw doctor --fix` rewrites legacy
@@ -79,20 +79,20 @@ explicit runtime config.
## OpenClaw feature coverage
| OpenAI capability | OpenClaw surface | Status |
| ------------------------- | -------------------------------------------------------------------------------- | ------------------------------------------------------ |
| Chat / Responses | `openai/<model>` model provider | Yes |
| Codex subscription models | `openai/<model>` with `openai-codex` OAuth | Yes |
| Legacy Codex model refs | `openai-codex/<model>` | Repaired by doctor to `openai/<model>` |
| Codex app-server harness | `openai/<model>` with omitted runtime or provider/model `agentRuntime.id: codex` | Yes |
| Server-side web search | Native OpenAI Responses tool | Yes, when web search is enabled and no provider pinned |
| Images | `image_generate` | Yes |
| Videos | `video_generate` | Yes |
| Text-to-speech | `messages.tts.provider: "openai"` / `tts` | Yes |
| Batch speech-to-text | `tools.media.audio` / media understanding | Yes |
| Streaming speech-to-text | Voice Call `streaming.provider: "openai"` | Yes |
| Realtime voice | Voice Call `realtime.provider: "openai"` / Control UI Talk | Yes |
| Embeddings | memory embedding provider | Yes |
| OpenAI capability | OpenClaw surface | Status |
| ------------------------- | ----------------------------------------------------------------- | ------------------------------------------------------ |
| Chat / Responses | `openai/<model>` model provider | Yes |
| Codex subscription models | `openai/<model>` with `openai-codex` OAuth | Yes |
| Legacy Codex model refs | `openai-codex/<model>` | Repaired by doctor to `openai/<model>` |
| Codex app-server harness | `openai/<model>` with omitted runtime or `agentRuntime.id: codex` | Yes |
| Server-side web search | Native OpenAI Responses tool | Yes, when web search is enabled and no provider pinned |
| Images | `image_generate` | Yes |
| Videos | `video_generate` | Yes |
| Text-to-speech | `messages.tts.provider: "openai"` / `tts` | Yes |
| Batch speech-to-text | `tools.media.audio` / media understanding | Yes |
| Streaming speech-to-text | Voice Call `streaming.provider: "openai"` | Yes |
| Realtime voice | Voice Call `realtime.provider: "openai"` / Control UI Talk | Yes |
| Embeddings | memory embedding provider | Yes |
## Memory embeddings
@@ -152,9 +152,9 @@ Choose your preferred auth method and follow the setup steps.
| Model ref | Runtime config | Route | Auth |
| ---------------------- | -------------------------- | --------------------------- | ---------------- |
| `openai/gpt-5.5` | omitted / provider/model `agentRuntime.id: "codex"` | Codex app-server harness | `openai-codex` profile |
| `openai/gpt-5.4-mini` | omitted / provider/model `agentRuntime.id: "codex"` | Codex app-server harness | `openai-codex` profile |
| `openai/gpt-5.5` | provider/model `agentRuntime.id: "pi"` | PI embedded runtime | `openai` profile or selected `openai-codex` profile |
| `openai/gpt-5.5` | omitted / `agentRuntime.id: "codex"` | Codex app-server harness | `openai-codex` profile |
| `openai/gpt-5.4-mini` | omitted / `agentRuntime.id: "codex"` | Codex app-server harness | `openai-codex` profile |
| `openai/gpt-5.5` | `agentRuntime.id: "pi"` | PI embedded runtime | `openai` profile or selected `openai-codex` profile |
<Note>
`openai/*` agent models use the Codex app-server harness. To use API-key
@@ -239,8 +239,8 @@ Choose your preferred auth method and follow the setup steps.
| Model ref | Runtime config | Route | Auth |
|-----------|----------------|-------|------|
| `openai/gpt-5.5` | omitted / provider/model `agentRuntime.id: "codex"` | Native Codex app-server harness | Codex sign-in or selected `openai-codex` profile |
| `openai/gpt-5.5` | provider/model `agentRuntime.id: "pi"` | PI embedded runtime with internal Codex-auth transport | Selected `openai-codex` profile |
| `openai/gpt-5.5` | omitted / `agentRuntime.id: "codex"` | Native Codex app-server harness | Codex sign-in or selected `openai-codex` profile |
| `openai/gpt-5.5` | `agentRuntime.id: "pi"` | PI embedded runtime with internal Codex-auth transport | Selected `openai-codex` profile |
| `openai-codex/gpt-5.5` | repaired by doctor | Legacy route rewritten to `openai/gpt-5.5` | Existing `openai-codex` profile |
<Warning>
@@ -265,6 +265,7 @@ Choose your preferred auth method and follow the setup steps.
agents: {
defaults: {
model: { primary: "openai/gpt-5.5" },
agentRuntime: { id: "codex" },
},
},
}
@@ -283,7 +284,7 @@ Choose your preferred auth method and follow the setup steps.
openclaw models status
openclaw models auth list --provider openai-codex
openclaw config get agents.defaults.model --json
openclaw config get models.providers.openai.agentRuntime --json
openclaw config get agents.defaults.agentRuntime --json
```
For a specific agent, add `--agent <id>`:
@@ -366,7 +367,7 @@ Choose your preferred auth method and follow the setup steps.
## Native Codex app-server auth
The native Codex app-server harness uses `openai/*` model refs plus omitted
runtime config or provider/model `agentRuntime.id: "codex"`, but its auth is still
runtime config or `agentRuntime.id: "codex"`, but its auth is still
account-based. OpenClaw
selects auth in this order:
@@ -503,7 +504,7 @@ See [Video Generation](/tools/video-generation) for shared tool parameters, prov
OpenClaw adds a shared GPT-5 prompt contribution for GPT-5-family runs across providers. It applies by model id, so `openai/gpt-5.5`, legacy pre-repair refs such as `openai-codex/gpt-5.5`, `openrouter/openai/gpt-5.5`, `opencode/gpt-5.5`, and other compatible GPT-5 refs receive the same overlay. Older GPT-4.x models do not.
The bundled native Codex harness uses the same GPT-5 behavior and heartbeat overlay through Codex app-server developer instructions, so `openai/gpt-5.x` sessions routed through Codex keep the same follow-through and proactive heartbeat guidance even though Codex owns the rest of the harness prompt.
The bundled native Codex harness uses the same GPT-5 behavior and heartbeat overlay through Codex app-server developer instructions, so `openai/gpt-5.x` sessions forced through `agentRuntime.id: "codex"` keep the same follow-through and proactive heartbeat guidance even though Codex owns the rest of the harness prompt.
The GPT-5 contribution adds a tagged behavior contract for persona persistence, execution safety, tool discipline, output shape, completion checks, and verification. Channel-specific reply and silent-message behavior stays in the shared OpenClaw system prompt and outbound delivery policy. The GPT-5 guidance is always enabled for matching models. The friendly interaction-style layer is separate and configurable.
@@ -640,28 +641,15 @@ Legacy `plugins.entries.openai.config.personality` is still read as a compatibil
| Setting | Config path | Default |
|---------|------------|---------|
| Model | `plugins.entries.voice-call.config.realtime.providers.openai.model` | `gpt-realtime-2` |
| Model | `plugins.entries.voice-call.config.realtime.providers.openai.model` | `gpt-realtime-1.5` |
| Voice | `...openai.voice` | `alloy` |
| Temperature (Azure deployment bridge) | `...openai.temperature` | `0.8` |
| Temperature | `...openai.temperature` | `0.8` |
| VAD threshold | `...openai.vadThreshold` | `0.5` |
| Silence duration | `...openai.silenceDurationMs` | `500` |
| API key | `...openai.apiKey` | Falls back to `OPENAI_API_KEY` |
Available built-in Realtime voices for `gpt-realtime-2`: `alloy`, `ash`,
`ballad`, `coral`, `echo`, `sage`, `shimmer`, `verse`, `marin`, `cedar`.
OpenAI recommends `marin` and `cedar` for the best Realtime quality. This
is a separate set from the Text-to-speech voices above; do not assume a TTS
voice such as `fable`, `nova`, or `onyx` is valid for Realtime sessions.
<Note>
Backend OpenAI realtime bridges use the GA Realtime WebSocket session shape, which does not accept `session.temperature`. Azure OpenAI deployments remain available via `azureEndpoint` and `azureDeployment` and keep the deployment-compatible session shape. Supports bidirectional tool calling and G.711 u-law audio.
</Note>
<Note>
Realtime voice is selected when the session is created. OpenAI allows most
session fields to change later, but the voice cannot be changed after the
model has emitted audio in that session. OpenClaw currently exposes the
built-in Realtime voice ids as strings.
Supports Azure OpenAI via `azureEndpoint` and `azureDeployment` config keys for backend realtime bridges. Supports bidirectional tool calling. Uses G.711 u-law audio format.
</Note>
<Note>
@@ -669,8 +657,9 @@ Legacy `plugins.entries.openai.config.personality` is still read as a compatibil
ephemeral client secret and a direct browser WebRTC SDP exchange against the
OpenAI Realtime API. Maintainer live verification is available with
`OPENAI_API_KEY=... GEMINI_API_KEY=... node --import tsx scripts/dev/realtime-talk-live-smoke.ts`;
the OpenAI legs verify both the backend WebSocket bridge and the browser
WebRTC SDP exchange without logging secrets.
the OpenAI leg mints a client secret in Node, generates a browser SDP offer
with fake microphone media, posts it to OpenAI, and applies the SDP answer
without logging secrets.
</Note>
</Accordion>
@@ -911,7 +900,7 @@ the Server-side compaction accordion below.
- Injects `context_management: [{ type: "compaction", compact_threshold: ... }]`
- Default `compact_threshold`: 70% of `contextWindow` (or `80000` when unavailable)
This applies to the built-in Pi harness path and to OpenAI provider hooks used by embedded runs. The native Codex app-server harness manages its own context through Codex and is configured by OpenAI's default agent route or provider/model runtime policy.
This applies to the built-in Pi harness path and to OpenAI provider hooks used by embedded runs. The native Codex app-server harness manages its own context through Codex and is configured separately with `agents.defaults.agentRuntime.id`.
<Tabs>
<Tab title="Enable explicitly">

View File

@@ -105,9 +105,6 @@ the maintainer-only release runbook.
`OpenClaw Release Publish`, reusing the successful preflight artifact via
`preflight_run_id`; stable macOS release readiness also requires the
packaged `.zip`, `.dmg`, `.dSYM.zip`, and updated `appcast.xml` on `main`.
The private macOS publish workflow publishes the signed appcast to public
`main` automatically after release assets verify; if branch protection blocks
the direct push, it opens or updates an appcast PR.
11. After publish, run the npm post-publish verifier, optional standalone
published-npm Telegram E2E when you need post-publish channel proof,
dist-tag promotion when needed, verify the generated GitHub release page,
@@ -275,9 +272,7 @@ Validation` or from the `main`/release workflow ref so workflow logic and
not describe a stale CI layout
- Stable macOS release readiness also includes the updater surfaces:
- the GitHub release must end up with the packaged `.zip`, `.dmg`, and `.dSYM.zip`
- `appcast.xml` on `main` must point at the new stable zip after publish; the
private macOS publish workflow commits it automatically, or opens an appcast
PR when direct push is blocked
- `appcast.xml` on `main` must point at the new stable zip after publish
- the packaged app must keep a non-debug bundle id, a non-empty Sparkle feed
URL, and a `CFBundleVersion` at or above the canonical Sparkle build floor
for that release version

View File

@@ -167,7 +167,7 @@ Use these hubs to discover every page, including deep dives and reference docs t
- [Plugin manifest](/plugins/manifest)
- [Agent tools](/plugins/building-plugins#registering-agent-tools)
- [Plugin bundles](/plugins/bundles)
- [ClawHub](/clawhub)
- [Community plugins](/plugins/community)
- [Capability cookbook](/tools/capability-cookbook)
- [Voice call plugin](/plugins/voice-call)
- [Zalo user plugin](/plugins/zalouser)
@@ -175,7 +175,7 @@ Use these hubs to discover every page, including deep dives and reference docs t
## Workspace + templates
- [Skills](/tools/skills)
- [ClawHub](/clawhub)
- [ClawHub](/tools/clawhub)
- [Skills config](/tools/skills-config)
- [Default AGENTS](/reference/AGENTS.default)
- [Templates: AGENTS](/reference/templates/AGENTS)

View File

@@ -20,7 +20,7 @@ Codex has two OpenClaw routes:
| Route | Config/command | Setup page |
| -------------------------- | ------------------------------------------------------ | --------------------------------------- |
| Native Codex app-server | `/codex ...`, `openai/gpt-*` agent refs | [Codex harness](/plugins/codex-harness) |
| Native Codex app-server | `/codex ...`, `agentRuntime.id: "codex"` | [Codex harness](/plugins/codex-harness) |
| Explicit Codex ACP adapter | `/acp spawn codex`, `runtime: "acp", agentId: "codex"` | This page |
Prefer the native route unless you explicitly need ACP/acpx behavior.

View File

@@ -19,8 +19,8 @@ Each ACP session spawn is tracked as a [background task](/automation/tasks).
<Note>
**ACP is the external-harness path, not the default Codex path.** The
native Codex app-server plugin owns `/codex ...` controls and the default
`openai/gpt-*` embedded runtime for agent turns; ACP owns
native Codex app-server plugin owns `/codex ...` controls and the
`agentRuntime.id: "codex"` embedded runtime; ACP owns
`/acp ...` controls and `sessions_spawn({ runtime: "acp" })` sessions.
If you want Codex or Claude Code to connect as an external MCP client

View File

@@ -1,5 +1,470 @@
---
summary: "Redirect to /clawhub"
title: "ClawHub (redirect)"
redirect: /clawhub
summary: "ClawHub: public registry for OpenClaw skills and plugins, native install flows, and the clawhub CLI"
read_when:
- Searching for, installing, or updating skills or plugins
- Publishing skills or plugins to the registry
- Configuring the clawhub CLI or its environment overrides
title: "ClawHub"
sidebarTitle: "ClawHub"
---
ClawHub is the public registry for **OpenClaw skills and plugins**.
- Use native `openclaw` commands to search, install, and update skills, and to install plugins from ClawHub.
- Use the separate `clawhub` CLI for registry auth, publish, delete/undelete, and sync workflows.
Site: [clawhub.ai](https://clawhub.ai)
## Quick start
<Steps>
<Step title="Search">
```bash
openclaw skills search "calendar"
```
</Step>
<Step title="Install">
```bash
openclaw skills install <skill-slug>
```
</Step>
<Step title="Use">
Start a new OpenClaw session - it picks up the new skill.
</Step>
<Step title="Publish (optional)">
For registry-authenticated workflows (publish, sync, manage), install
the separate `clawhub` CLI:
```bash
npm i -g clawhub
# or
pnpm add -g clawhub
```
</Step>
</Steps>
## Native OpenClaw flows
<Tabs>
<Tab title="Skills">
```bash
openclaw skills search "calendar"
openclaw skills install <skill-slug>
openclaw skills update --all
```
Native `openclaw` commands install into your active workspace and
persist source metadata so later `update` calls can stay on ClawHub.
</Tab>
<Tab title="Plugins">
```bash
openclaw plugins search "calendar"
openclaw plugins install clawhub:<package>
openclaw plugins update --all
```
`plugins search` queries the ClawHub plugin catalog and prints install-ready
package names. Use `clawhub:<package>` when you want ClawHub resolution.
Bare npm-safe plugin specs install from npm during the launch cutover:
```bash
openclaw plugins install openclaw-codex-app-server
```
`npm:<package>` is also npm-only and is useful when a spec could otherwise
be ambiguous:
```bash
openclaw plugins install npm:openclaw-codex-app-server
```
Plugin installs validate advertised `pluginApi` and
`minGatewayVersion` compatibility before archive install runs, so
incompatible hosts fail closed early instead of partially installing
the package. When a package version publishes a ClawPack artifact,
OpenClaw prefers the exact uploaded npm-pack `.tgz`, verifies the ClawHub
digest header and downloaded bytes, and records the artifact kind, npm
integrity, npm shasum, tarball name, and ClawPack digest metadata for later
updates. Older package versions without ClawPack metadata still use the
legacy package archive verification path.
</Tab>
</Tabs>
<Note>
`openclaw plugins install clawhub:...` only accepts installable plugin
families. If a ClawHub package is actually a skill, OpenClaw stops and
points you at `openclaw skills install <slug>` instead.
Anonymous ClawHub plugin installs also fail closed for private packages.
Community or other non-official channels can still install, but OpenClaw
warns so operators can review source and verification before enabling
them.
</Note>
## What ClawHub is
- A public registry for OpenClaw skills and plugins.
- A versioned store of skill bundles and metadata.
- A discovery surface for search, tags, and usage signals.
A typical skill is a versioned bundle of files that includes:
- A `SKILL.md` file with the primary description and usage.
- Optional configs, scripts, or supporting files used by the skill.
- Metadata such as tags, summary, and install requirements.
ClawHub uses metadata to power discovery and safely expose skill
capabilities. The registry tracks usage signals (stars, downloads) to
improve ranking and visibility. Each publish creates a new semver
version, and the registry keeps version history so users can audit
changes.
## Workspace and skill loading
The separate `clawhub` CLI also installs skills into `./skills` under
your current working directory. If an OpenClaw workspace is configured,
`clawhub` falls back to that workspace unless you override `--workdir`
(or `CLAWHUB_WORKDIR`). OpenClaw loads workspace skills from
`<workspace>/skills` and picks them up in the **next** session.
If you already use `~/.openclaw/skills` or bundled skills, workspace
skills take precedence. For more detail on how skills are loaded,
shared, and gated, see [Skills](/tools/skills).
## Service features
| Feature | Notes |
| ------------------------ | ------------------------------------------------------------------- |
| Public browsing | Skills and their `SKILL.md` content are publicly viewable. |
| Search | Embedding-powered (vector search), not just keywords. |
| Versioning | Semver, changelogs, and tags (including `latest`). |
| Downloads | Zip per version. |
| Stars and comments | Community feedback. |
| Security scan summaries | Detail pages show the latest scan state before install or download. |
| Scanner detail pages | VirusTotal, ClawScan, and static-analysis results have deep links. |
| Owner recovery dashboard | Publishers can see scan-held owned content from `/dashboard`. |
| Owner-requested rescans | Owners can request limited rescans for false-positive recovery. |
| Moderation | Approvals and audits. |
| CLI-friendly API | Suitable for automation and scripting. |
## Security and moderation
ClawHub is open by default - anyone can upload skills, but a GitHub
account must be **at least one week old** to publish. This slows down
abuse without blocking legitimate contributors.
<AccordionGroup>
<Accordion title="Security scans">
ClawHub runs automated security checks on published skills and plugin
releases. Public detail pages summarize the current result, and scanner
rows link to dedicated detail pages for VirusTotal, ClawScan, and static
analysis.
Scan-held or blocked releases may be unavailable on public catalog and
install surfaces while still visible to their owner in `/dashboard`.
</Accordion>
<Accordion title="Reporting">
- Any signed-in user can report a skill.
- Report reasons are required and recorded.
- Each user can have up to 20 active reports at a time.
- Skills with more than 3 unique reports are auto-hidden by default.
</Accordion>
<Accordion title="Moderation">
- Moderators can view hidden skills, unhide them, delete them, or ban users.
- Abusing the report feature can result in account bans.
- Interested in becoming a moderator? Ask in the OpenClaw Discord and contact a moderator or maintainer.
</Accordion>
</AccordionGroup>
## ClawHub CLI
You only need this for registry-authenticated workflows such as
publish/sync.
### Global options
<ParamField path="--workdir <dir>" type="string">
Working directory. Default: current dir; falls back to OpenClaw workspace.
</ParamField>
<ParamField path="--dir <dir>" type="string" default="skills">
Skills directory, relative to workdir.
</ParamField>
<ParamField path="--site <url>" type="string">
Site base URL (browser login).
</ParamField>
<ParamField path="--registry <url>" type="string">
Registry API base URL.
</ParamField>
<ParamField path="--no-input" type="boolean">
Disable prompts (non-interactive).
</ParamField>
<ParamField path="-V, --cli-version" type="boolean">
Print CLI version.
</ParamField>
### Commands
<AccordionGroup>
<Accordion title="Auth (login / logout / whoami)">
```bash
clawhub login # browser flow
clawhub login --token <token>
clawhub logout
clawhub whoami
```
Login options:
- `--token <token>` - paste an API token.
- `--label <label>` - label stored for browser login tokens (default: `CLI token`).
- `--no-browser` - do not open a browser (requires `--token`).
</Accordion>
<Accordion title="Search">
```bash
clawhub search "query"
```
Searches skills. For plugin/package discovery, use `clawhub package explore`.
- `--limit <n>` - max results.
</Accordion>
<Accordion title="Browse / inspect plugins">
```bash
clawhub package explore --family code-plugin
clawhub package explore "episodic-claw" --family code-plugin
clawhub package inspect episodic-claw
```
`package explore` and `package inspect` are the ClawHub CLI surfaces for plugin/package discovery and metadata inspection. Native OpenClaw installs still use `openclaw plugins install clawhub:<package>`.
Options:
- `--family skill|code-plugin|bundle-plugin` - filter package family.
- `--official` - show only official packages.
- `--executes-code` - show only packages that execute code.
- `--version <version>` / `--tag <tag>` - inspect a specific package version.
- `--versions`, `--files`, `--file <path>` - inspect package history and files.
- `--json` - machine-readable output.
</Accordion>
<Accordion title="Install / update / list">
```bash
clawhub install <slug>
clawhub update <slug>
clawhub update --all
clawhub list
```
Options:
- `--version <version>` - install or update to a specific version (single slug only on `update`).
- `--force` - overwrite if the folder already exists, or when local files do not match any published version.
- `clawhub list` reads `.clawhub/lock.json`.
</Accordion>
<Accordion title="Publish skills">
```bash
clawhub skill publish <path>
```
Options:
- `--slug <slug>` - skill slug.
- `--name <name>` - display name.
- `--version <version>` - semver version.
- `--changelog <text>` - changelog text (can be empty).
- `--tags <tags>` - comma-separated tags (default: `latest`).
</Accordion>
<Accordion title="Publish plugins">
```bash
clawhub package publish <source>
```
`<source>` can be a local folder, `owner/repo`, `owner/repo@ref`, or a
GitHub URL.
Options:
- `--dry-run` - build the exact publish plan without uploading anything.
- `--json` - emit machine-readable output for CI.
- `--source-repo`, `--source-commit`, `--source-ref` - optional overrides when auto-detection is not enough.
</Accordion>
<Accordion title="Request rescans">
```bash
clawhub skill rescan <slug>
clawhub skill rescan <slug> --yes --json
clawhub package rescan <name>
clawhub package rescan <name> --yes --json
```
Rescan commands require a logged-in owner token and target the latest
published skill version or plugin release. In non-interactive runs, pass
`--yes`.
JSON responses include the target kind, name, version, rescan status, and
remaining/max request counts for that version or release.
</Accordion>
<Accordion title="Delete / undelete (owner or admin)">
```bash
clawhub delete <slug> --yes
clawhub undelete <slug> --yes
```
</Accordion>
<Accordion title="Sync (scan local + publish new or updated)">
```bash
clawhub sync
```
Options:
- `--root <dir...>` - extra scan roots.
- `--all` - upload everything without prompts.
- `--dry-run` - show what would be uploaded.
- `--bump <type>` - `patch|minor|major` for updates (default: `patch`).
- `--changelog <text>` - changelog for non-interactive updates.
- `--tags <tags>` - comma-separated tags (default: `latest`).
- `--concurrency <n>` - registry checks (default: `4`).
</Accordion>
</AccordionGroup>
## Common workflows
<Tabs>
<Tab title="Search">
```bash
clawhub search "postgres backups"
```
</Tab>
<Tab title="Find a plugin">
```bash
clawhub package explore --family code-plugin
clawhub package explore "memory" --family code-plugin
clawhub package inspect episodic-claw
```
</Tab>
<Tab title="Install">
```bash
clawhub install my-skill-pack
```
</Tab>
<Tab title="Update all">
```bash
clawhub update --all
```
</Tab>
<Tab title="Publish a single skill">
```bash
clawhub skill publish ./my-skill --slug my-skill --name "My Skill" --version 1.0.0 --tags latest
```
</Tab>
<Tab title="Sync many skills">
```bash
clawhub sync --all
```
</Tab>
<Tab title="Publish a plugin from GitHub">
```bash
clawhub package publish your-org/your-plugin --dry-run
clawhub package publish your-org/your-plugin
clawhub package publish your-org/your-plugin@v1.0.0
clawhub package publish https://github.com/your-org/your-plugin
```
</Tab>
</Tabs>
### Plugin package metadata
Code plugins must include the required OpenClaw metadata in
`package.json`:
```json
{
"name": "@myorg/openclaw-my-plugin",
"version": "1.0.0",
"type": "module",
"openclaw": {
"extensions": ["./src/index.ts"],
"runtimeExtensions": ["./dist/index.js"],
"compat": {
"pluginApi": ">=2026.3.24-beta.2",
"minGatewayVersion": "2026.3.24-beta.2"
},
"build": {
"openclawVersion": "2026.3.24-beta.2",
"pluginSdkVersion": "2026.3.24-beta.2"
}
}
}
```
Published packages should ship **built JavaScript** and point
`runtimeExtensions` at that output. Git checkout installs can still fall
back to TypeScript source when no built files exist, but built runtime
entries avoid runtime TypeScript compilation in startup, doctor, and
plugin loading paths.
## Versioning, lockfile, and telemetry
<AccordionGroup>
<Accordion title="Versioning and tags">
- Each publish creates a new **semver** `SkillVersion`.
- Tags (like `latest`) point to a version; moving tags lets you roll back.
- Changelogs are attached per version and can be empty when syncing or publishing updates.
</Accordion>
<Accordion title="Local changes vs registry versions">
Updates compare the local skill contents to registry versions using a
content hash. If local files do not match any published version, the
CLI asks before overwriting (or requires `--force` in
non-interactive runs).
</Accordion>
<Accordion title="Sync scanning and fallback roots">
`clawhub sync` scans your current workdir first. If no skills are
found, it falls back to known legacy locations (for example
`~/openclaw/skills` and `~/.openclaw/skills`). This is designed to
find older skill installs without extra flags.
</Accordion>
<Accordion title="Storage and lockfile">
- Installed skills are recorded in `.clawhub/lock.json` under your workdir.
- Auth tokens are stored in the ClawHub CLI config file (override via `CLAWHUB_CONFIG_PATH`).
</Accordion>
<Accordion title="Telemetry (install counts)">
When you run `clawhub sync` while logged in, the CLI sends a minimal
snapshot to compute install counts. You can disable this entirely:
```bash
export CLAWHUB_DISABLE_TELEMETRY=1
```
</Accordion>
</AccordionGroup>
## Environment variables
| Variable | Effect |
| ----------------------------- | ----------------------------------------------- |
| `CLAWHUB_SITE` | Override the site URL. |
| `CLAWHUB_REGISTRY` | Override the registry API URL. |
| `CLAWHUB_CONFIG_PATH` | Override where the CLI stores the token/config. |
| `CLAWHUB_WORKDIR` | Override the default workdir. |
| `CLAWHUB_DISABLE_TELEMETRY=1` | Disable telemetry on `sync`. |
## Related
- [Community plugins](/plugins/community)
- [Plugins](/tools/plugin)
- [Skills](/tools/skills)

View File

@@ -116,5 +116,5 @@ The YAML frontmatter supports these fields:
- [Skills reference](/tools/skills) — loading, precedence, and gating rules
- [Skills config](/tools/skills-config) — `skills.*` config schema
- [ClawHub](/clawhub) — public skill registry
- [ClawHub](/tools/clawhub) — public skill registry
- [Building Plugins](/plugins/building-plugins) — plugins can ship skills

View File

@@ -56,8 +56,7 @@ Exec approvals are enforced locally on the execution host:
- Gateway-authenticated callers are trusted operators for that Gateway.
- Paired nodes extend that trusted operator capability onto the node host.
- Exec approvals reduce accidental execution risk, but are **not** a per-user auth boundary or filesystem read-only policy.
- Once approved, a command can mutate files according to the selected host or sandbox filesystem permissions.
- Exec approvals reduce accidental execution risk, but are **not** a per-user auth boundary.
- Approved node-host runs bind canonical execution context: canonical cwd, exact argv, env binding when present, and pinned executable path when applicable.
- For shell scripts and direct interpreter/runtime file invocations, OpenClaw also tries to bind one concrete local file operand. If that bound file changes after approval but before execution, the run is denied instead of executing drifted content.
- File binding is intentionally best-effort, **not** a complete semantic model of every interpreter/runtime loader path. If approval mode cannot identify exactly one concrete local file to bind, it refuses to mint an approval-backed run instead of pretending full coverage.

View File

@@ -6,9 +6,8 @@ read_when:
title: "Exec tool"
---
Run shell commands in the workspace. `exec` is a mutating shell surface: commands can create, edit, or delete files wherever the selected host or sandbox filesystem permits. Disabling OpenClaw filesystem tools such as `write`, `edit`, or `apply_patch` does not make `exec` read-only.
Supports foreground + background execution via `process`. If `process` is disallowed, `exec` runs synchronously and ignores `yieldMs`/`background`.
Run shell commands in the workspace. Supports foreground + background execution via `process`.
If `process` is disallowed, `exec` runs synchronously and ignores `yieldMs`/`background`.
Background sessions are scoped per agent; `process` only sees sessions from the same agent.
## Parameters

View File

@@ -300,7 +300,7 @@ Legacy `agent.*` configs are migrated by `openclaw doctor`; prefer `agents.defau
}
```
</Tab>
<Tab title="Shell execution with filesystem tools disabled">
<Tab title="Safe execution (no file modifications)">
```json
{
"tools": {
@@ -309,11 +309,6 @@ Legacy `agent.*` configs are migrated by `openclaw doctor`; prefer `agents.defau
}
}
```
<Warning>
This policy disables OpenClaw filesystem tools, but `exec` is still a shell and can write files wherever the selected host or sandbox filesystem allows. For a read-only agent, deny `exec` and `process`, or combine shell access with sandbox filesystem controls such as `agents.defaults.sandbox.workspaceAccess: "ro"` or `"none"`.
</Warning>
</Tab>
<Tab title="Communication-only">
```json

View File

@@ -13,7 +13,7 @@ agent harnesses, tools, skills, speech, realtime transcription, realtime
voice, media-understanding, image generation, video generation, web fetch, web
search, and more. Some plugins are **core** (shipped with OpenClaw), others
are **external**. Most external plugins are published and discovered through
[ClawHub](/clawhub). Npm remains supported for direct installs and for a
[ClawHub](/tools/clawhub). Npm remains supported for direct installs and for a
temporary set of OpenClaw-owned plugin packages while that migration finishes.
## Quick start
@@ -279,7 +279,7 @@ current OpenClaw or a local checkout until a newer npm package is published.
</Accordion>
</AccordionGroup>
Looking for third-party plugins? See [ClawHub](/clawhub).
Looking for third-party plugins? See [Community Plugins](/plugins/community).
## Configuration
@@ -391,8 +391,8 @@ even when source overlay mounts are present.
re-enable plugins before running doctor cleanup if you want stale ids removed
- OpenAI-family Codex routes keep separate plugin boundaries:
`openai-codex/*` belongs to the OpenAI plugin, while the bundled Codex
app-server plugin is selected by canonical `openai/*` agent refs, explicit
provider/model `agentRuntime.id: "codex"`, or legacy `codex/*` model refs
app-server plugin is selected by `agentRuntime.id: "codex"` or legacy
`codex/*` model refs
## Troubleshooting runtime hooks
@@ -728,4 +728,4 @@ For full typed hook behavior, see [SDK overview](/plugins/sdk-overview#hook-deci
- [Plugin manifest](/plugins/manifest) - manifest schema
- [Registering tools](/plugins/building-plugins#registering-agent-tools) - add agent tools in a plugin
- [Plugin internals](/plugins/architecture) - capability model and load pipeline
- [ClawHub](/clawhub) - third-party plugin discovery
- [Community plugins](/plugins/community) - third-party listings

View File

@@ -125,7 +125,7 @@ its proposals. Full guide: [Skill Workshop plugin](/plugins/skill-workshop).
[ClawHub](https://clawhub.ai) is the public skills registry for OpenClaw.
Use native `openclaw skills` commands for discover/install/update, or the
separate `clawhub` CLI for publish/sync workflows. Full guide:
[ClawHub](/clawhub).
[ClawHub](/tools/clawhub).
| Action | Command |
| ---------------------------------- | -------------------------------------- |
@@ -475,7 +475,7 @@ schema: [Skills config](/tools/skills-config).
## Related
- [ClawHub](/clawhub) - public skills registry
- [ClawHub](/tools/clawhub) - public skills registry
- [Creating skills](/tools/creating-skills) - building custom skills
- [Plugins](/tools/plugin) - plugin system overview
- [Skill Workshop plugin](/plugins/skill-workshop) - generate skills from agent work

View File

@@ -125,7 +125,7 @@ Current source-of-truth:
<AccordionGroup>
<Accordion title="Sessions and runs">
- `/new [model]` starts a new session; `/reset` is the reset alias.
- Control UI intercepts typed `/new` to create and switch to a fresh dashboard session, except when `session.dmScope: "main"` is configured and the current parent is the agent's main session; in that case `/new` resets the main session in place. Typed `/reset` still runs the Gateway's in-place reset.
- Control UI intercepts typed `/new` to create and switch to a fresh dashboard session; typed `/reset` still runs the Gateway's in-place reset.
- `/reset soft [message]` keeps the current transcript, drops reused CLI backend session ids, and reruns startup/system-prompt loading in-place.
- `/compact [instructions]` compacts the session context. See [Compaction](/concepts/compaction).
- `/stop` aborts the current run.

Some files were not shown because too many files have changed in this diff Show More