Compare commits

..

2 Commits

Author SHA1 Message Date
Mason Huang
16a367787c CI: increase CodeQL JavaScript runner size 2026-04-25 12:30:28 +08:00
Mason Huang
a07aaee3ec CI: trim CodeQL JavaScript scope 2026-04-25 09:54:06 +08:00
723 changed files with 4701 additions and 33023 deletions

View File

@@ -40,7 +40,6 @@ jobs:
has_changed_extensions: ${{ steps.manifest.outputs.has_changed_extensions }}
changed_extensions_matrix: ${{ steps.manifest.outputs.changed_extensions_matrix }}
run_build_artifacts: ${{ steps.manifest.outputs.run_build_artifacts }}
run_checks_fast_core: ${{ steps.manifest.outputs.run_checks_fast_core }}
run_checks_fast: ${{ steps.manifest.outputs.run_checks_fast }}
checks_fast_core_matrix: ${{ steps.manifest.outputs.checks_fast_core_matrix }}
channel_contracts_matrix: ${{ steps.manifest.outputs.channel_contracts_matrix }}
@@ -131,9 +130,6 @@ jobs:
OPENCLAW_CI_RUN_MACOS: ${{ steps.changed_scope.outputs.run_macos || 'false' }}
OPENCLAW_CI_RUN_ANDROID: ${{ steps.changed_scope.outputs.run_android || 'false' }}
OPENCLAW_CI_RUN_WINDOWS: ${{ steps.changed_scope.outputs.run_windows || 'false' }}
OPENCLAW_CI_RUN_NODE_FAST_ONLY: ${{ steps.changed_scope.outputs.run_node_fast_only || 'false' }}
OPENCLAW_CI_RUN_NODE_FAST_PLUGIN_CONTRACTS: ${{ steps.changed_scope.outputs.run_node_fast_plugin_contracts || 'false' }}
OPENCLAW_CI_RUN_NODE_FAST_CI_ROUTING: ${{ steps.changed_scope.outputs.run_node_fast_ci_routing || 'false' }}
OPENCLAW_CI_RUN_SKILLS_PYTHON: ${{ steps.changed_scope.outputs.run_skills_python || 'false' }}
OPENCLAW_CI_RUN_CONTROL_UI_I18N: ${{ steps.changed_scope.outputs.run_control_ui_i18n || 'false' }}
OPENCLAW_CI_HAS_CHANGED_EXTENSIONS: ${{ steps.changed_extensions.outputs.has_changed_extensions || 'false' }}
@@ -177,23 +173,12 @@ jobs:
const docsOnly = parseBoolean(process.env.OPENCLAW_CI_DOCS_ONLY);
const docsChanged = parseBoolean(process.env.OPENCLAW_CI_DOCS_CHANGED);
const runNode = parseBoolean(process.env.OPENCLAW_CI_RUN_NODE) && !docsOnly;
const runNodeFastOnly =
runNode && parseBoolean(process.env.OPENCLAW_CI_RUN_NODE_FAST_ONLY);
const runNodeFull = runNode && !runNodeFastOnly;
const runNodeFastPluginContracts =
runNode && parseBoolean(process.env.OPENCLAW_CI_RUN_NODE_FAST_PLUGIN_CONTRACTS);
const runNodeFastCiRouting =
runNode && parseBoolean(process.env.OPENCLAW_CI_RUN_NODE_FAST_CI_ROUTING);
const runChecksFastCore = runNodeFull || runNodeFastPluginContracts || runNodeFastCiRouting;
const runMacos =
parseBoolean(process.env.OPENCLAW_CI_RUN_MACOS) && !docsOnly && isCanonicalRepository;
const runAndroid =
parseBoolean(process.env.OPENCLAW_CI_RUN_ANDROID) && !docsOnly && isCanonicalRepository;
const runWindows =
parseBoolean(process.env.OPENCLAW_CI_RUN_WINDOWS) &&
!docsOnly &&
!runNodeFastOnly &&
isCanonicalRepository;
parseBoolean(process.env.OPENCLAW_CI_RUN_WINDOWS) && !docsOnly && isCanonicalRepository;
const runSkillsPython = parseBoolean(process.env.OPENCLAW_CI_RUN_SKILLS_PYTHON) && !docsOnly;
const runControlUiI18n =
parseBoolean(process.env.OPENCLAW_CI_RUN_CONTROL_UI_I18N) && !docsOnly;
@@ -206,7 +191,7 @@ jobs:
? DEFAULT_EXTENSION_TEST_SHARD_COUNT
: Math.max(DEFAULT_EXTENSION_TEST_SHARD_COUNT, 36);
const extensionShardMatrix = createMatrix(
runNodeFull
runNode
? createExtensionTestShards({
shardCount: extensionTestShardCount,
}).map((shard) => ({
@@ -222,33 +207,7 @@ jobs:
}))
: [],
);
const checksFastCoreTasks = [];
if (runNodeFull) {
checksFastCoreTasks.push(
{ check_name: "checks-fast-bundled", runtime: "node", task: "bundled" },
{
check_name: "checks-fast-contracts-plugins",
runtime: "node",
task: "contracts-plugins",
},
);
} else {
if (runNodeFastPluginContracts) {
checksFastCoreTasks.push({
check_name: "checks-fast-contracts-plugins",
runtime: "node",
task: runNodeFastCiRouting ? "contracts-plugins-ci-routing" : "contracts-plugins",
});
} else if (runNodeFastCiRouting) {
checksFastCoreTasks.push({
check_name: "checks-fast-ci-routing",
runtime: "node",
task: "ci-routing",
});
}
}
const nodeTestShards = runNodeFull
const nodeTestShards = runNode
? createNodeTestShards().map((shard) => ({
check_name: shard.checkName,
runtime: "node",
@@ -273,17 +232,25 @@ jobs:
run_windows: runWindows,
has_changed_extensions: hasChangedExtensions,
changed_extensions_matrix: changedExtensionsMatrix,
run_build_artifacts: runNodeFull,
run_checks_fast_core: runChecksFastCore,
run_checks_fast: runNodeFull,
checks_fast_core_matrix: createMatrix(checksFastCoreTasks),
channel_contracts_matrix: createMatrix(
runNodeFull ? createChannelContractTestShards() : [],
run_build_artifacts: runNode,
run_checks_fast: runNode,
checks_fast_core_matrix: createMatrix(
runNode
? [
{ check_name: "checks-fast-bundled", runtime: "node", task: "bundled" },
{
check_name: "checks-fast-contracts-plugins",
runtime: "node",
task: "contracts-plugins",
},
]
: [],
),
channel_contracts_matrix: createMatrix(runNode ? createChannelContractTestShards() : []),
checks_node_extensions_matrix: extensionShardMatrix,
run_checks: runNodeFull,
run_checks: runNode,
checks_matrix: createMatrix(
runNodeFull
runNode
? [
{ check_name: "checks-node-channels", runtime: "node", task: "channels" },
]
@@ -302,9 +269,9 @@ jobs:
}))
: [],
),
run_check: runNodeFull,
run_check_additional: runNodeFull,
run_build_smoke: runNodeFull,
run_check: runNode,
run_check_additional: runNode,
run_build_smoke: runNode,
run_check_docs: docsChanged,
run_control_ui_i18n: runControlUiI18n,
run_skills_python_job: runSkillsPython,
@@ -695,7 +662,7 @@ jobs:
contents: read
name: ${{ matrix.check_name }}
needs: [preflight]
if: needs.preflight.outputs.run_checks_fast_core == 'true'
if: needs.preflight.outputs.run_checks_fast == 'true'
runs-on: ${{ github.repository == 'openclaw/openclaw' && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-24.04' }}
timeout-minutes: 60
strategy:
@@ -772,13 +739,6 @@ jobs:
contracts-plugins)
pnpm test:contracts:plugins
;;
contracts-plugins-ci-routing)
pnpm test:contracts:plugins
pnpm test src/commands/status.scan-result.test.ts src/scripts/ci-changed-scope.test.ts test/scripts/test-projects.test.ts
;;
ci-routing)
pnpm test src/commands/status.scan-result.test.ts src/scripts/ci-changed-scope.test.ts test/scripts/test-projects.test.ts
;;
*)
echo "Unsupported checks-fast task: $TASK" >&2
exit 1
@@ -1084,7 +1044,7 @@ jobs:
contents: read
name: checks-node-compat-node22
needs: [preflight]
if: needs.preflight.outputs.run_build_artifacts == 'true' && github.event_name == 'push'
if: needs.preflight.outputs.run_node == 'true' && github.event_name == 'push'
runs-on: ${{ github.repository == 'openclaw/openclaw' && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-24.04' }}
timeout-minutes: 60
steps:
@@ -1627,7 +1587,7 @@ jobs:
packages/plugin-sdk/dist
extensions/*/dist/.boundary-tsc.tsbuildinfo
extensions/*/dist/.boundary-tsc.stamp
key: ${{ runner.os }}-extension-package-boundary-v1-${{ hashFiles('tsconfig.json', 'tsconfig.plugin-sdk.dts.json', 'packages/plugin-sdk/tsconfig.json', 'scripts/check-extension-package-tsc-boundary.mjs', 'scripts/prepare-extension-package-boundary-artifacts.mjs', 'scripts/write-plugin-sdk-entry-dts.ts', 'scripts/lib/plugin-sdk-entrypoints.json', 'scripts/lib/plugin-sdk-entries.mjs', 'src/plugin-sdk/**', 'src/auto-reply/**', 'src/video-generation/dashscope-compatible.ts', 'src/video-generation/types.ts', 'src/types/**', 'extensions/**', 'extensions/tsconfig.package-boundary*.json', 'package.json', 'pnpm-lock.yaml') }}
key: ${{ runner.os }}-extension-package-boundary-v1-${{ hashFiles('tsconfig.json', 'tsconfig.plugin-sdk.dts.json', 'packages/plugin-sdk/tsconfig.json', 'scripts/check-extension-package-tsc-boundary.mjs', 'scripts/prepare-extension-package-boundary-artifacts.mjs', 'scripts/write-plugin-sdk-entry-dts.ts', 'scripts/lib/plugin-sdk-entrypoints.json', 'scripts/lib/plugin-sdk-entries.mjs', 'src/plugin-sdk/**', 'src/video-generation/dashscope-compatible.ts', 'src/video-generation/types.ts', 'src/types/**', 'extensions/**', 'extensions/tsconfig.package-boundary*.json', 'package.json', 'pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-extension-package-boundary-v1-

View File

@@ -349,5 +349,4 @@ jobs:
- name: Run fast bundled plugin Docker E2E
env:
OPENCLAW_BUNDLED_CHANNEL_DEPS_E2E_IMAGE: openclaw-bundled-channel-fast:local
OPENCLAW_BUNDLED_CHANNEL_DOCKER_RUN_TIMEOUT: 90s
run: timeout 240s pnpm test:docker:bundled-channel-deps:fast
run: timeout 120s pnpm test:docker:bundled-channel-deps:fast

View File

@@ -432,35 +432,24 @@ jobs:
OPENCLAW_DISCORD_SMOKE_CHANNEL_ID: ${{ secrets.OPENCLAW_DISCORD_SMOKE_CHANNEL_ID }}
OPENCLAW_RELEASE_CHECK_OS: ${{ matrix.os_id }}
OPENCLAW_RELEASE_CHECK_RUNNER: ${{ matrix.runner }}
CANDIDATE_TGZ: ${{ runner.temp }}/openclaw-cross-os-release-checks/candidate/${{ needs.prepare.outputs.candidate_file_name }}
CANDIDATE_VERSION: ${{ needs.prepare.outputs.candidate_version }}
SOURCE_SHA: ${{ needs.prepare.outputs.source_sha }}
BASELINE_SPEC: ${{ needs.prepare.outputs.baseline_spec }}
PREVIOUS_VERSION: ${{ inputs.previous_version }}
BASELINE_TGZ: ${{ runner.temp }}/openclaw-cross-os-release-checks/baseline/${{ needs.prepare.outputs.baseline_file_name }}
PROVIDER: ${{ inputs.provider }}
MODE: ${{ matrix.lane }}
SUITE: ${{ matrix.suite }}
REF: ${{ inputs.ref }}
OUTPUT_DIR: ${{ runner.temp }}/openclaw-cross-os-release-checks/${{ matrix.artifact_name }}-${{ matrix.suite }}
run: |
DISCORD_ARGS=()
if [[ -n "${OPENCLAW_DISCORD_SMOKE_BOT_TOKEN}" ]] && [[ -n "${OPENCLAW_DISCORD_SMOKE_GUILD_ID}" ]] && [[ -n "${OPENCLAW_DISCORD_SMOKE_CHANNEL_ID}" ]]; then
DISCORD_ARGS+=(--run-discord-roundtrip true)
fi
pnpm dlx "tsx@${TSX_VERSION}" workflow/scripts/openclaw-cross-os-release-checks.ts \
--candidate-tgz "${CANDIDATE_TGZ}" \
--candidate-version "${CANDIDATE_VERSION}" \
--source-sha "${SOURCE_SHA}" \
--baseline-spec "${BASELINE_SPEC}" \
--previous-version "${PREVIOUS_VERSION}" \
--baseline-tgz "${BASELINE_TGZ}" \
--provider "${PROVIDER}" \
--mode "${MODE}" \
--suite "${SUITE}" \
--ref "${REF}" \
--candidate-tgz "$RUNNER_TEMP/openclaw-cross-os-release-checks/candidate/${{ needs.prepare.outputs.candidate_file_name }}" \
--candidate-version "${{ needs.prepare.outputs.candidate_version }}" \
--source-sha "${{ needs.prepare.outputs.source_sha }}" \
--baseline-spec "${{ needs.prepare.outputs.baseline_spec }}" \
--previous-version "${{ inputs.previous_version }}" \
--baseline-tgz "$RUNNER_TEMP/openclaw-cross-os-release-checks/baseline/${{ needs.prepare.outputs.baseline_file_name }}" \
--provider "${{ inputs.provider }}" \
--mode "${{ matrix.lane }}" \
--suite "${{ matrix.suite }}" \
--ref "${{ inputs.ref }}" \
"${DISCORD_ARGS[@]}" \
--output-dir "${OUTPUT_DIR}"
--output-dir "$RUNNER_TEMP/openclaw-cross-os-release-checks/${{ matrix.artifact_name }}-${{ matrix.suite }}"
- name: Summarize release checks
if: always()

View File

@@ -2,21 +2,6 @@
Docs: https://docs.openclaw.ai
## Unreleased
### Changes
- Diagnostics/OTEL: support `OPENCLAW_OTEL_PRELOADED=1` so the plugin can reuse an already-registered OpenTelemetry SDK while keeping OpenClaw diagnostic listeners wired. (#70424) Thanks @jlapenna.
- Control UI: refine the agent Tool Access panel with compact live-tool chips, collapsible tool groups, direct per-tool toggles, and clearer runtime/source provenance. (#71405) Thanks @BunsDev.
- Memory-core/hybrid search: expose raw `vectorScore` and `textScore` alongside the combined `score` on hybrid memory search results, so callers can inspect vector-versus-text retrieval contribution before temporal decay or MMR reordering. Fixes #68166. (#68286) Thanks @ajfonthemove.
### Fixes
- MCP: retire one-shot embedded bundled MCP runtimes at run end, skip bundle-MCP startup when a runtime tool allowlist cannot reach bundle-MCP tools, and add `mcp.sessionIdleTtlMs` idle eviction for leaked session runtimes. Fixes #71106 and #71110.
- Gateway/restart continuation: durably hand restart continuations to a session-delivery queue before deleting the restart sentinel, recover queued continuation work after crashy restarts, and fall back to a session-only wake when no channel route survives reboot. (#70780) Thanks @fuller-stack-dev.
- Agents/tool-result pruning: harden the tool-result character estimator and context-pruning loops against malformed `{ type: "text" }` blocks created by void or undefined tool handler results, serializing non-string text payloads for size accounting so they cannot bypass trimming as zero-sized. Fixes #34979. (#51267) Thanks @cgdusek.
- Daemon/service-env: add Nix Home Manager profile bin directories to generated gateway service PATHs on macOS and Linux, honoring `NIX_PROFILES` right-to-left precedence and falling back to `~/.nix-profile/bin` when unset. Fixes #44402. (#59935) Thanks @jerome-benoit.
## 2026.4.24 (Unreleased)
### Breaking
@@ -25,8 +10,6 @@ Docs: https://docs.openclaw.ai
### Changes
- Gateway/nodes: add disabled-by-default `gateway.nodes.pairing.autoApproveCidrs` for first-time node pairing from explicit trusted CIDRs, while keeping operator/browser pairing and all upgrade flows manual. Fixes #60800. Thanks @sahilsatralkar.
- Browser: add viewport coordinate clicks for managed and existing-session automation, plus `openclaw browser click-coords` for CLI use. (#54452) Thanks @dluttz.
- Browser/config: support per-profile `browser.profiles.<name>.headless` overrides for locally launched browser profiles, so one profile can run headless without forcing all browser profiles headless. Thanks @nakamotoliu.
- Plugins/PDF: move local PDF extraction into a bundled `document-extract` plugin so core no longer owns `pdfjs-dist` or PDF image-rendering dependencies. Thanks @vincentkoc.
- Matrix: require full cross-signing identity trust for self-device verification and add `openclaw matrix verify self` so operators can establish that trust from the CLI. (#70401) Thanks @gumadeiras.
@@ -38,9 +21,6 @@ Docs: https://docs.openclaw.ai
- Plugins/setup: include `setup.providers[].envVars` in generic provider auth/env lookups and warn non-bundled plugins that still rely on deprecated `providerAuthEnvVars` compatibility metadata. Thanks @vincentkoc.
- Plugins/setup: derive generic provider setup choices from descriptor-safe `setup.providers[].authMethods` before falling back to setup runtime. Thanks @vincentkoc.
- Plugins/setup: surface manifest provider auth choices directly in provider setup flow before falling back to setup runtime or install-catalog choices. Thanks @vincentkoc.
- Models/catalog: add a manifest catalog planner that produces stable manifest-sourced model rows and reports duplicate provider/model conflicts without loading provider runtime. (#71368) Thanks @shakkernerd.
- Models/catalog: centralize manifest model catalog normalization behind a shared `src/model-catalog` contract so future provider index, cache, onboarding, and listing consumers reuse the same validation and row refs. (#71360) Thanks @shakkernerd.
- Plugins/manifest: add a `modelCatalog` contract for provider-owned model rows, aliases, suppression rules, and discovery mode metadata without loading plugin runtime. (#71342) Thanks @shakkernerd.
- Plugins/setup: warn when descriptor-only setup plugins still ship ignored setup runtime entries, keeping `setup.requiresRuntime: false` semantics explicit without breaking existing metadata. Thanks @vincentkoc.
- Plugins/channels: use manifest `channelConfigs` for read-only external channel discovery when no setup entry is available or setup descriptors declare runtime unnecessary. Thanks @vincentkoc.
- TUI/dependencies: remove direct `cli-highlight` usage from the OpenClaw TUI code-block renderer, keeping themed code coloring without the extra root dependency. Thanks @vincentkoc.
@@ -74,116 +54,17 @@ Docs: https://docs.openclaw.ai
- Plugins/Google Meet: add a bundled participant plugin with personal Google auth, explicit meeting URL joins, Chrome and Twilio transports, and realtime voice support. (#70765) Thanks @steipete.
- Plugins/Google Meet: default Chrome realtime sessions to OpenAI plus SoX `rec`/`play` audio bridge commands, so the usual setup only needs the plugin enabled and `OPENAI_API_KEY`. Thanks @steipete.
- Plugins/Google Meet: add a `chrome-node` transport so a paired macOS node, such as a Parallels VM, can own Chrome, BlackHole, and SoX while the Gateway machine keeps the agent and model key. Thanks @steipete.
- Plugins/Google Meet: add `googlemeet artifacts` and `googlemeet attendance` commands plus matching tool/gateway actions for conference records, recordings, transcripts and transcript entries, smart notes, and participant sessions. Thanks @steipete.
- Plugins/Google Meet: add markdown and file output for `googlemeet artifacts` and `googlemeet attendance` reports. Thanks @steipete.
- Plugins/Google Meet: add `googlemeet doctor --oauth` so operators can verify OAuth token refresh, Meet space reads, and side-effecting space creation without printing secrets. Thanks @steipete.
- Plugins/Voice Call: expose the shared `openclaw_agent_consult` realtime tool so live phone calls can ask the full OpenClaw agent for deeper/tool-backed answers. Thanks @steipete.
- Plugins/Voice Call: add `voicecall setup` and a dry-run-by-default `voicecall smoke` command so Twilio/provider readiness can be checked before placing a live test call. Thanks @steipete.
- Plugins/Google Meet: add `googlemeet doctor` and a `recover_current_tab`/`recover-tab` flow so agents can inspect an already-open Meet tab and report the blocker without opening another window. Thanks @steipete.
- Plugins/Bonjour: move LAN Gateway discovery advertising into a default-enabled bundled plugin with its own `@homebridge/ciao` dependency, so users can disable Bonjour without cutting wide-area discovery. Thanks @vincentkoc.
- Providers/Google: add a Gemini Live realtime voice provider for backend Voice Call and Google Meet audio bridges, with bidirectional audio and function-call support. Thanks @steipete.
- Providers/Google: let Gemini TTS prepend configured `audioProfile` and `speakerName` prompt text for reusable speech style control. Thanks @tdack.
- Plugins/Google Meet: let realtime Meet sessions consult the full OpenClaw agent for deeper answers while staying in the live voice loop. Thanks @steipete.
- Gateway/VoiceClaw: add a realtime brain WebSocket endpoint backed by Gemini Live, with owner-auth gating and async OpenClaw tool handoff. (#70938) Thanks @yagudaev.
- Providers/DeepSeek: add DeepSeek V4 Flash and V4 Pro to the bundled catalog and make V4 Flash the onboarding default. Thanks @lsdsjy.
- CLI/Gateway: make `gateway status` start faster by skipping plugin loading on the read-only status path. (#71364) Thanks @andyylin.
- Plugins/compatibility: add a central plugin compatibility registry and docs for SDK/config/setup/runtime deprecation records, including dated migration metadata for legacy harness naming and other plugin-facing aliases. Thanks @vincentkoc.
- Agents/bootstrap: add `agents.defaults.contextInjection: "never"` to disable workspace bootstrap file injection for agents that fully own their prompt lifecycle. (#65006) Thanks @xDarkicex.
### Fixes
- Feishu: back off streaming-card creation after HTTP 400 startup failures, so unsupported card setups fall back without delaying every message. Fixes #56981. Thanks @JinnanDuan.
- Feishu/topic groups: key native Feishu/Lark topic-group sessions by `thread_id` so starter messages and replies with different `root_id` formats stay in the same `group_topic` conversation. Fixes #71438. Thanks @1335848090.
- Feishu: suppress duplicate final card delivery when idle closes a streaming card before the final payload arrives. (#68491) Thanks @MoerAI.
- Signal: preserve sender attachment filenames and resolve missing MIME types from those filenames, so Linux `signal-cli` voice notes without `contentType` still enter audio transcription. Fixes #48614. Thanks @mindfury.
- Telegram/agents: suppress the phantom "Agent couldn't generate a response" fallback after a reply was already committed through the messaging tool. (#70623) Thanks @chinar-amrutkar.
- Models/CLI: show provider runtime `contextTokens` beside native `contextWindow` in `openclaw models list`, and align `openai-codex/gpt-5.5` with Codex's 272K runtime cap plus 400K native window. Fixes #71403.
- Dashboard/security: avoid writing tokenized Control UI URLs or SSH hints to runtime logs, keeping gateway bearer fragments out of console-captured logs readable through `logs.tail`. (#70029) Thanks @Ziy1-Tan.
- Providers/OpenRouter: treat DeepSeek refs as cache-TTL eligible without injecting Anthropic cache-control markers, aligning context pruning with OpenRouter-managed prompt caching. (#51983) Thanks @QuinnH496.
- Control UI/browser: defer temp-dir access-mode constants until Node-only temp-dir resolution runs, preventing browser bundles from crashing when `node:fs` constants are stubbed. (#48930) Thanks @Valentinws.
- Discord/cron: deliver text-only isolated cron and heartbeat announce output from the canonical final assistant text once, avoiding duplicate Discord posts when streamed block payloads and the final answer contain the same content. Fixes #71406. Thanks @alexgross21.
- macOS Gateway: wait for launchd to reload the exited Gateway LaunchAgent before bootstrapping repair fallback, preventing config-triggered restarts from leaving the service not loaded. Fixes #45178. Thanks @vincentkoc.
- macOS Gateway: tolerate launchctl bootstrap's already-loaded exit during restart fallback and use non-killing kickstart after bootstrap, avoiding a second race that can unload the LaunchAgent. Fixes #41934. Thanks @zerone0x.
- macOS Gateway: rewrite stale LaunchAgent plists before restart fallback bootstrap, matching install repair behavior when `gateway restart` has to re-register launchd. Thanks @maybegeeker.
- TTS/hooks: preserve audio-only TTS transcripts for `message_sending` and `message_sent` hooks without rendering the transcript as a media caption. Thanks @zqchris.
- WhatsApp/TTS: preserve `audioAsVoice` through shared media payload sends and the WhatsApp outbound adapter, so `[[audio_as_voice]]` reply payloads keep their voice-note intent when routed through `sendPayload`. Fixes #66053. Thanks @masatohoshino.
- Control UI/WebChat: hide heartbeat prompts, `HEARTBEAT_OK` acknowledgments, and internal-only runtime context turns from visible chat history while leaving the underlying transcript intact. Fixes #71381. Thanks @gerald1950ggg-ai.
- Control UI/chat: keep optimistic user and assistant tail messages visible when a final history refresh briefly returns an older snapshot, preventing message cards from flash-disappearing until the next refresh. Fixes #71371. Thanks @WolvenRA.
- Talk/TTS: resolve configured extension speech providers from the active runtime registry before provider-list discovery, so Talk mode no longer rejects valid plugin speech providers as unsupported.
- Sessions/subagents: stop stale ended runs and old store-only child reverse links from reappearing in `childSessions`, while keeping live descendants and recently-ended children visible. Fixes #57920.
- Subagents: stop stale unended runs from counting as active or pending forever, while preserving restart-aborted recovery for recoverable child sessions. Fixes #71252. Thanks @hclsys.
- Gateway/tools: allow `POST /tools/invoke` to reach plugin-backed catalog tools such as `browser` when no core implementation exists, while still preferring built-in tools for real core names. Thanks @chat2way.
- Browser/security: require `operator.admin` for the `browser.request` gateway method, matching the host/browser-node control authority exposed by that route. Thanks @RichardCao.
- Browser/profiles: allow local managed profiles to override `browser.executablePath`, so different profiles can launch different Chromium-based browsers. Thanks @nobrainer-tech.
- Agents/replay: repair displaced or missing tool results before strict provider replay, use Codex-compatible `aborted` outputs for OpenAI Responses history, and drop partial aborted/error transport turns before retries.
- Browser/startup: deduplicate concurrent lazy-start calls per profile so simultaneous browser tool requests no longer race into duplicate Chrome launches and `PortInUseError`. (#61772) Thanks @sukhdeepjohar.
- Browser/profiles: recover from stale Chromium `Singleton*` profile locks after crashes or host moves by clearing dead/foreign locks and retrying launch once. Thanks @seanc-dev.
- Browser/existing-session: keep Chrome MCP status probes transport-only and ephemeral, and retry stale cached Playwright attaches once so idle profile checks no longer poison the next real attach. (#57245) Thanks @josephbergvinson.
- Cron/exec: suppress automatic background exec completion wakes only for silent cron jobs with `delivery.mode="none"` while keeping webhook and announce runs observable. (#71391) Thanks @goldmar.
- Reply media: allow sandboxed replies to deliver OpenClaw-managed `media/outbound` and `media/tool-*` attachments without treating them as sandbox escapes, while keeping alias-escape checks on the managed media root. Fixes #71138. Thanks @mayor686, @truffle-dev, and @neeravmakwana.
- CLI/agent: keep `openclaw agent --json` stdout reserved for the JSON response by routing gateway, plugin, and embedded-fallback diagnostics to stderr before execution starts. Fixes #71319.
- Agents/Gemini: retry reasoning-only, empty, and planning-only Gemini turns instead of letting sessions silently stall. Fixes #71074. (#71362) Thanks @neeravmakwana.
- Providers/DeepSeek: add missing `reasoning_content` placeholders for replayed assistant tool-call turns when DeepSeek V4 thinking is enabled, so switching an existing session to `deepseek-v4-flash` or `deepseek-v4-pro` no longer trips the provider's 400 replay check. Fixes #71372. Thanks @yangyang1719.
- Exec approvals: allow bare command-name allowlist patterns to match PATH-resolved executable basenames without trusting `./tool` or absolute path-selected binaries. Fixes #71315. Thanks @chen-zhang-cs-code and @dengluozhang.
- Config/recovery: skip whole-file last-known-good rollback when invalidity is scoped to `plugins.entries.*`, preserving unrelated user settings during plugin schema or host-version skew. Fixes #71289. Thanks @jalehman.
- Agents/tools: keep resolved reply-run configs from being overwritten by stale runtime snapshots, and let empty web runtime metadata fall back to configured provider auto-detection so standard and queued turns expose the same tool set. Fixes #71355. Thanks @c-g14.
- Agents/TTS: pass the resolved shared config into the `tts` tool, so tool-triggered speech uses configured providers and voices instead of falling back to a fresh config load.
- Reply media: strip `MEDIA:` attachments from final replies when the same media already went out through block streaming, preventing duplicate Telegram voice notes and files. Fixes #65468. Thanks @aurora-openclaw.
- Agents/TTS: preserve voice media when a tool-generated reply is paired with an exact `NO_REPLY` sentinel, stripping the sentinel text instead of dropping the audio payload. Fixes #66092.
- Compaction: honor explicit `agents.defaults.compaction.keepRecentTokens` for manual `/compact`, re-distill safeguard summaries instead of snowballing previous summaries, and enable safeguard summary quality checks by default. Fixes #71357. Thanks @WhiteGiverMa.
- Sessions: honor configured `session.maintenance` settings during load-time maintenance instead of falling back to default entry caps. Fixes #71356. Thanks @comolago.
- Browser/sandbox: pass the resolved `browser.ssrfPolicy` into sandbox browser bridges and refresh cached bridges when the effective policy changes, so sandboxed browser navigation honors private-network opt-ins. Fixes #45153 and #57055. Thanks @jzakirov, @zuoanCo, and @kybrcore.
- Browser/proxy: keep Gateway/provider proxy environment variables from proxying the OpenClaw-managed browser, so `HTTP_PROXY` and `HTTPS_PROXY` no longer block ordinary browser navigation. Fixes #71358. Thanks @Sanjays2402.
- Agents/MCP: validate draft-2020-12 MCP tool output schemas with a draft-aware bundle-MCP client validator, so external MCP servers no longer fail catalog/tool execution with missing schema refs. Fixes #68772 and #70196. Thanks @mwiesen.
- Dashboard/Windows: open Control UI and OAuth URLs through the system URL handler without `cmd.exe` parsing or PATH-based `rundll32` lookup, and reject non-HTTP browser-open inputs. Fixes #71098. Thanks @Sanjays2402.
- Config/doctor: reject legacy `secretref-env:<ENV_VAR>` marker strings on SecretRef credential paths and migrate valid markers to structured env SecretRefs with `openclaw doctor --fix`. Fixes #51794. Thanks @halointellicore.
- Plugin SDK/browser: export the resolved browser tab-cleanup config type through the browser profile facade, keeping SDK subpath contracts aligned.
- Providers/OpenAI: separate API-key and Codex sign-in onboarding groups, and avoid replaying stale OpenAI Responses reasoning blocks after a model route switch.
- Providers/OpenAI-compatible: forward `prompt_cache_key` on Completions requests only for providers that opt in with `compat.supportsPromptCacheKey`, keeping default proxy payloads unchanged. Fixes #69272.
- Providers/OpenAI-compatible: skip null or non-object streaming chunks from custom providers instead of failing the turn after partial output. Fixes #51112.
- Providers/OpenAI-compatible: treat singular MLX-style `finish_reason: "tool_call"` as tool use instead of a provider error. Fixes #61499.
- Docs/TTS: clarify that legacy flat TTS provider config blocks are repaired by `openclaw doctor --fix`, not accepted by strict runtime schema on load. Fixes #56220.
- Plugins/OpenCode: strip unsupported disabled Responses reasoning payloads for OpenCode image understanding. Fixes #70252.
- Plugins/OpenCode/OpenCode Go: register image understanding metadata so the image tool is available for OpenCode catalog models with vision support. Fixes #70482 and #61789.
- Plugins/OpenCode Go: update the default Go catalog model to `opencode-go/kimi-k2.6`. Thanks @masrlinu.
- Providers/ElevenLabs: omit the MP3-only `Accept` header for PCM telephony synthesis, so Voice Call requests for `pcm_22050` no longer receive MP3 audio. Fixes #67340. Thanks @marcchabot.
- Providers/MiniMax TTS: truncate fractional pitch overrides before sending T2A requests, matching MiniMax's integer pitch contract while preserving fractional speed and volume. Fixes #62144.
- Providers/MiniMax TTS: transcode voice-note targets to Opus so Feishu/Telegram receive native voice messages instead of MP3 file attachments. Fixes #63540, #64134, and #70445.
- Providers/Microsoft TTS: keep allowlisted bundled speech providers discoverable even when another speech plugin has already registered, so Edge/Microsoft TTS is available alongside OpenAI. Fixes #62117 and #66850.
- Providers/Microsoft TTS: honor legacy `messages.tts.providers.edge` voice settings after normalizing Edge TTS to the Microsoft provider. Fixes #64153.
- Providers/OpenRouter: add an OpenRouter TTS provider using the OpenAI-compatible `/audio/speech` endpoint and `OPENROUTER_API_KEY`. Fixes #71268.
- macOS Talk Mode: retry failed local ElevenLabs stream playback through gateway `talk.speak` before falling back to the system voice, so configured ElevenLabs voices still play when streaming playback fails. Fixes #65662.
- Plugins/Voice Call: reap stale pre-answer calls by default, honor configured TTS timeouts for Twilio media-stream playback, and fail empty telephony audio instead of completing as silence. Fixes #42071; supersedes #60957. Thanks @Ryce and @sliekens.
- Plugins/Voice Call: fail fast when Twilio, Telnyx, or Plivo would fall back to a loopback/private webhook URL, so calls do not start with an unreachable callback endpoint. Thanks @artemgetmann.
- Plugins/Voice Call: resolve queued-but-not-yet-playing Twilio TTS entries when barge-in or stream teardown clears the playback queue, so callers awaiting `queueTts()` do not hang. Thanks @kevinWangSheng.
- Plugins/Voice Call: terminate expired restored call sessions with the provider and restart restored max-duration timers with only the remaining duration, preventing stale outbound retry loops after Gateway restarts. Fixes #48739. Thanks @mira-solari.
- Plugins/Voice Call: start provider STT after Telnyx outbound conversation greetings and pass configured Telnyx voice IDs through to the speak action. Fixes #56091. Thanks @Roshan.
- Skills: honor legacy `metadata.clawdbot` requirements and installer hints when `metadata.openclaw` is absent, so older skills no longer appear ready when required binaries are missing. Fixes #71323. Thanks @chen-zhang-cs-code.
- Browser/config: expand `~` in `browser.executablePath` before Chromium launch, so home-relative custom browser paths no longer fail with `ENOENT`. Fixes #67264. Thanks @Quratulain-bilal.
- Channels/streaming: keep Telegram tool-progress preview updates enabled by default to match released behavior, document `streaming.preview.toolProgress: false` for disabling only those status lines, and prevent preview progress text from triggering Telegram Markdown links, Discord mentions, or Slack mrkdwn mentions. Fixes #71320. Thanks @neeravmakwana.
- Gateway/sessions: copy the oversized `sessions.json` to a rotation backup before the atomic rewrite instead of renaming the live store away, so a crash during rotation keeps the existing session-to-transcript mapping authoritative. Fixes #68229. Thanks @jjjojoj.
- Providers/OpenAI-compatible: strip OpenAI-only Completions `store` from proxy payloads and allow `extra_body`/`extraBody` passthrough params for provider-specific request fields. Fixes #61826 and #69717.
- Discord/subagents: preserve thread-bound completion delivery by keeping the requester-agent announce path primary and falling back to direct thread sends only when the announce produces no visible output. (#71064) Thanks @DolencLuka.
- Discord/proxy: serialize proxied multipart attachment uploads with undici `FormData`, so Discord media sends work through configured REST proxies. (#71383) Thanks @TC500.
- Browser/tool: give Chrome MCP existing-session manage calls a longer default timeout, pass explicit tool timeouts through tab management, and recover stale selected-page MCP sessions instead of forcing a manual reset. Thanks @steipete.
- Browser/sandbox: clean up idle tracked tabs opened by primary-agent browser sessions, while preserving active tab reuse and lifecycle cleanup for subagents, cron, and ACP sessions. Fixes #71165. Thanks @dwbutler.
- Plugins/Voice Call: reuse the webhook runtime across in-process plugin contexts, avoiding `EADDRINUSE` when agent tools or CLI commands run while the Gateway already owns the voice webhook port. Fixes #58115. Thanks @sfbrian.
- Plugins/Voice Call: answer accepted Telnyx inbound Call Control legs on `call.initiated`, so webhooks that reach OpenClaw no longer leave the caller ringing until hangup. Fixes #58231 and #40131. Thanks @KonsultDigital.
- Plugins/Voice Call: coalesce concurrent webhook server starts on the same runtime instance, avoiding a second `listen()` bind when overlapping startup paths race. Thanks @education-01.
- Plugins/Voice Call: pin voice response sessions to `responseModel` before embedded agent runs, avoiding live-session model switch failures when the global default model differs. Fixes #60118. Thanks @xinbenlv.
- Plugins/Voice Call: add `agentId` for voice response generation, so phone calls can use a dedicated agent workspace instead of always routing through `main`. Fixes #42155. Thanks @TheOpie.
- Plugins/Voice Call: scope embedded voice response sandbox resolution to the selected voice agent, so implicit `main` voice sessions respect `agents.defaults.sandbox.mode: "off"` even when other agents define sandboxed Docker binds. Fixes #56367. Thanks @crpol.
- Media tools: honor the configured web-fetch SSRF policy for media understanding, image/music/video generation references, and PDF inputs, so explicit RFC2544 opt-ins cover WebChat OSS uploads without weakening defaults. Fixes #71300. (#71321) Thanks @neeravmakwana.
- Agents/TTS: suppress successful spoken transcripts from verbose chat tool output when structured voice media is already queued, while preserving text output for non-builtin tool-name collisions. Fixes #71282. Thanks @neeravmakwana.
- Plugins/Google Meet: reuse existing Meet tabs and active sessions across harmless URL query differences, avoiding duplicate Chrome windows when agents retry a join. Thanks @steipete.
- Plugins/Google Meet: tell agents to recover already-open Meet tabs after browser timeouts, and make the dev CLI release its build lock if compiler spawning fails. Thanks @steipete.
- Plugins/Google Meet: return structured manual-action details when browser-based meeting creation needs login or permissions, so agents can guide the operator without opening duplicate Meet tabs. Thanks @steipete.
- Plugins/CLI: provide Gateway-backed node inspection to plugin commands, so `googlemeet recover-tab` can inspect paired browser nodes from the terminal. Thanks @steipete.
- Cron/isolated sessions: clear stale runtime, lifecycle, auth, model, exec, heartbeat, usage, privilege, routing, and delivery artifacts when creating a fresh isolated run, and persist per-run session rows as snapshots so old base-session state no longer leaks into new cron executions. Thanks @vincentkoc.
- Gateway/sessions: recover main-agent turns interrupted by a gateway restart from stale transcript-lock evidence, avoiding stuck `status: "running"` sessions without broad post-boot transcript scans. Fixes #70555. Thanks @bitloi.
- Codex approvals: sanitize MCP elicitation approval titles, descriptions, and display parameters before forwarding them to OpenClaw approval prompts. (#71343) Thanks @Lucenx9.
- Codex approvals: keep command approval responses within Codex app-server `availableDecisions`, including deny/cancel fallbacks for prompts that do not offer `decline`. (#71338) Thanks @Lucenx9.
- Codex harness: reject same-thread app-server notifications without `turnId` or `turn.id` after a bound turn starts, preventing unscoped events from mutating or completing the active reply. (#71317) Thanks @Lucenx9.
- Plugins/Google Meet: include live Chrome-node readiness in `googlemeet setup` and document the Parallels recovery checks, so stale node tokens or disconnected VM browsers are visible before an agent opens a meeting. Thanks @steipete.
- Context engine: keep safeguard compaction checks active after context-engine windowing and for `ownsCompaction` engines, so large transcripts can compact before prompt submission instead of waiting for provider overflow. Fixes #71325. Thanks @steipete.
- Approvals: compact structured home-directory paths to `~` across Codex permission prompts and exec approval metadata without repeating them as a separate high-risk warning, while preserving filesystem root and wildcard host warnings. Thanks @steipete.
@@ -193,7 +74,6 @@ Docs: https://docs.openclaw.ai
- Telegram/model picker: show configured model display names when browsing models through provider buttons, matching typed `/models <provider>` output. Fixes #70560. (#71016) Thanks @iskim77.
- Plugins/runtime deps: stage bundled plugin runtime dependencies for packaged/global installs in an external runtime root and retain already staged deps across repairs, avoiding package-tree update races and npm pruning after upgrades. Thanks @steipete.
- Plugins/runtime deps: log bundled plugin runtime-dependency staging before synchronous npm installs start and include elapsed timing afterward, so first boot after upgrades no longer looks hung while dependencies are being repaired. Thanks @steipete.
- Memory/Bedrock: skip Bedrock during automatic memory embedding selection when AWS credentials are unavailable, so `memory_search` can fall back to lexical search instead of failing on the first embed call. Fixes #71143 via #71245. Thanks @bitloi.
- Agents/failover: forward embedded run abort signals into provider-owned model streams, cap implicit LLM idle watchdogs below long run timeouts, and mark 429 responses without usable retry timing as non-retryable so GitHub Copilot rate limits fail over or surface promptly instead of hanging until run timeout. Fixes #71120. Thanks @steipete.
- Plugins/Google Meet: make meeting creation join by default, with an explicit URL-only opt-out, so agents that create a Meet also enter it. Thanks @steipete.
- Telegram/polling: persist accepted update offsets before long-running handlers complete so poller restarts do not replay already-ingested updates, while keeping same-process retries for handler failures. Thanks @steipete.
@@ -214,7 +94,6 @@ Docs: https://docs.openclaw.ai
- Providers/Google: map `/think adaptive` to Gemini dynamic thinking instead of a fixed medium/high budget, using Gemini 3's provider default and Gemini 2.5's `thinkingBudget: -1`. Fixes #71316. Thanks @steipete.
- Providers/MiniMax: keep M2.7 chat model metadata text-only so image tool requests route through `MiniMax-VL-01` instead of the Anthropic-compatible chat endpoint. Fixes #71296. Thanks @ilker-cevikkaya.
- Discord/replies: run `message_sending` plugin hooks for Discord reply delivery, including DM targets, so plugins can transform or cancel outbound Discord replies consistently with other channels. Fixes #59350. (#71094) Thanks @wei840222.
- Discord/replies: preserve single-use native reply semantics across shared payload fallback, component, voice, and queued delivery paths, so explicit reply tags no longer consume implicit reply slots and chunked fallback sends reply only once. Thanks @steipete.
- Control UI/commands: carry provider-owned thinking option ids/labels in session rows and defaults so fresh sessions show and accept dynamic modes such as `adaptive`, `xhigh`, and `max`. Fixes #71269. Thanks @Young-Khalil.
- Image generation: make explicit `model=` overrides exact-only so failed `openai/gpt-image-2` requests no longer fall through to Gemini or other configured providers, and update `image_generate list` to mention OpenAI Codex OAuth as valid auth for `openai/gpt-image-2`. Fixes #71290 and #71231. Thanks @Young-Khalil and @steipete.
- Providers/GitHub Copilot: keep the plugin stream wrapper from claiming transport selection before OpenClaw picks a boundary-aware stream path, avoiding Pi's stale fallback Copilot headers on normal model turns. Thanks @steipete.
@@ -285,14 +164,6 @@ Docs: https://docs.openclaw.ai
- Plugins/Google Meet: report required manual actions for Chrome joins, use browser automation for Meet entry, and persist the private-WS node opt-in so paired-node realtime sessions keep their intended network policy. Thanks @steipete.
- Slack: route native stream fallback replies through the normal chunked sender so long buffered Slack Connect responses are not dropped or duplicated. (#71124) Thanks @martingarramon.
- WhatsApp: transcribe accepted voice notes before agent dispatch while keeping spoken transcripts out of command authorization. (#64120) Thanks @rogerdigital.
- Plugins/CLI: expose channel plugin CLI descriptors during discovery-mode plugin loads so snapshot registries keep channel commands visible without activating full runtimes. (#71309) Thanks @gumadeiras.
- WhatsApp: deliver media generated by tool-result replies while still suppressing text-only tool chatter. (#60968) Thanks @adaclaw.
- Config/agents: accept `agents.list[].contextTokens` in strict config validation so per-agent overrides survive hot reload, letting `/status` reflect the configured model window instead of the 200k fallback. Fixes #70692. (#71247) Thanks @statxc.
- Heartbeat: include async exec completion details in heartbeat prompts so command-finished notifications relay the actual output. (#71213) Thanks @GodsBoy.
- Memory search: apply session visibility and agent-to-agent policy to session transcript hits, and keep `corpus=sessions` ranking scoped to session collections before result limiting. (#70761) Thanks @nefainl.
- Agents/sessions: stop session write-lock timeouts from entering model failover, so local lock contention surfaces directly instead of cascading across providers. (#68700) Thanks @MonkeyLeeT.
- Auto-reply: run inbound reply delivery through `message_sending` hooks so plugins can transform or cancel generated replies before they are sent. (#70118) Thanks @jzakirov.
- CI/release-checks: pass workflow inputs and matrix values through step environment variables instead of embedding them directly into `run:` shell commands, reducing template-injection surface in the cross-OS release-check workflow. (#66884) Thanks @alexlomt.
## 2026.4.23

View File

@@ -9,14 +9,8 @@ enum ExecAllowlistMatcher {
for entry in entries {
switch ExecApprovalHelpers.validateAllowlistPattern(entry.pattern) {
case let .valid(pattern):
if ExecApprovalHelpers.patternHasPathSelector(pattern) {
let target = resolvedPath ?? rawExecutable
if self.matches(pattern: pattern, target: target) { return entry }
} else if pattern != "*",
!ExecApprovalHelpers.patternHasPathSelector(rawExecutable),
self.matchesExecutableBasename(pattern: pattern, resolution: resolution) {
return entry
}
let target = resolvedPath ?? rawExecutable
if self.matches(pattern: pattern, target: target) { return entry }
case .invalid:
continue
}
@@ -40,20 +34,6 @@ enum ExecAllowlistMatcher {
return matches
}
private static func matchesExecutableBasename(
pattern: String,
resolution: ExecCommandResolution) -> Bool
{
var candidates = Set<String>()
if !resolution.executableName.isEmpty {
candidates.insert(resolution.executableName)
}
if let resolvedPath = resolution.resolvedPath, !resolvedPath.isEmpty {
candidates.insert(URL(fileURLWithPath: resolvedPath).lastPathComponent)
}
return candidates.contains { self.matches(pattern: pattern, target: $0) }
}
private static func matches(pattern: String, target: String) -> Bool {
let trimmed = pattern.trimmingCharacters(in: .whitespacesAndNewlines)
guard !trimmed.isEmpty else { return false }

View File

@@ -616,17 +616,6 @@ enum ExecApprovalsStore {
let trimmedResolved = entry.lastResolvedPath?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
let normalizedResolved = trimmedResolved.isEmpty ? nil : trimmedResolved
if !ExecApprovalHelpers.patternHasPathSelector(trimmedPattern),
!trimmedResolved.isEmpty,
case let .valid(migratedPattern) = ExecApprovalHelpers.validateAllowlistPattern(trimmedResolved) {
return ExecAllowlistEntry(
id: entry.id,
pattern: migratedPattern,
lastUsedAt: entry.lastUsedAt,
lastUsedCommand: entry.lastUsedCommand,
lastResolvedPath: normalizedResolved)
}
switch ExecApprovalHelpers.validateAllowlistPattern(trimmedPattern) {
case let .valid(pattern):
return ExecAllowlistEntry(
@@ -735,10 +724,11 @@ enum ExecApprovalHelpers {
static func validateAllowlistPattern(_ pattern: String?) -> ExecAllowlistPatternValidation {
let trimmed = pattern?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
guard !trimmed.isEmpty else { return .invalid(.empty) }
guard self.containsPathComponent(trimmed) else { return .invalid(.missingPathComponent) }
return .valid(trimmed)
}
static func isValidAllowlistPattern(_ pattern: String?) -> Bool {
static func isPathPattern(_ pattern: String?) -> Bool {
switch self.validateAllowlistPattern(pattern) {
case .valid:
true
@@ -747,11 +737,6 @@ enum ExecApprovalHelpers {
}
}
static func isPathPattern(_ pattern: String?) -> Bool {
let trimmed = pattern?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
return self.patternHasPathSelector(trimmed)
}
static func parseDecision(_ raw: String?) -> ExecApprovalDecision? {
let trimmed = raw?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
guard !trimmed.isEmpty else { return nil }
@@ -774,7 +759,7 @@ enum ExecApprovalHelpers {
return pattern.isEmpty ? nil : pattern
}
static func patternHasPathSelector(_ pattern: String) -> Bool {
private static func containsPathComponent(_ pattern: String) -> Bool {
pattern.contains("/") || pattern.contains("~") || pattern.contains("\\")
}
}

View File

@@ -70,7 +70,6 @@ actor GatewayConnection {
case wizardStatus = "wizard.status"
case talkConfig = "talk.config"
case talkMode = "talk.mode"
case talkSpeak = "talk.speak"
case webLoginStart = "web.login.start"
case webLoginWait = "web.login.wait"
case channelsLogout = "channels.logout"

View File

@@ -105,7 +105,7 @@ struct SystemRunSettingsView: View {
.foregroundStyle(.secondary)
} else {
HStack(spacing: 8) {
TextField("Add command name or path glob", text: self.$newPattern)
TextField("Add allowlist path pattern (case-insensitive globs)", text: self.$newPattern)
.textFieldStyle(.roundedBorder)
Button("Add") {
if self.model.addEntry(self.newPattern) == nil {
@@ -113,10 +113,10 @@ struct SystemRunSettingsView: View {
}
}
.buttonStyle(.bordered)
.disabled(!self.model.isValidPattern(self.newPattern))
.disabled(!self.model.isPathPattern(self.newPattern))
}
Text("Bare names match PATH-resolved commands. Use a path glob for a specific binary.")
Text("Path patterns only. Basename entries like \"echo\" are ignored.")
.font(.footnote)
.foregroundStyle(.secondary)
if let validationMessage = self.model.allowlistValidationMessage {
@@ -424,8 +424,8 @@ final class ExecApprovalsSettingsModel {
self.entries.first(where: { $0.id == id })
}
func isValidPattern(_ pattern: String) -> Bool {
ExecApprovalHelpers.isValidAllowlistPattern(pattern)
func isPathPattern(_ pattern: String) -> Bool {
ExecApprovalHelpers.isPathPattern(pattern)
}
func refreshSkillBins(force: Bool = false) async {

View File

@@ -2,7 +2,6 @@ import AVFoundation
import Foundation
import OpenClawChatUI
import OpenClawKit
import OpenClawProtocol
import OSLog
import Speech
@@ -476,16 +475,7 @@ actor TalkModeRuntime {
self.ttsLogger
.error(
"talk TTS failed: \(error.localizedDescription, privacy: .public); " +
"retrying gateway talk.speak")
do {
try await self.playGatewayTalkSpeak(input: input)
return
} catch {
self.ttsLogger
.error(
"talk gateway TTS failed: \(error.localizedDescription, privacy: .public); " +
"falling back to system voice")
}
"falling back to system voice")
do {
try await self.playSystemVoice(input: input)
} catch {
@@ -730,42 +720,6 @@ actor TalkModeRuntime {
return await self.playMP3(stream: stream)
}
private func playGatewayTalkSpeak(input: TalkPlaybackInput) async throws {
let params = Self.makeTalkSpeakParams(
text: input.cleanedText,
voiceId: input.voiceId,
modelId: self.currentModelId ?? self.defaultModelId,
outputFormat: self.defaultOutputFormat,
directive: input.directive)
let result: TalkSpeakResult = try await GatewayConnection.shared.requestDecoded(
method: .talkSpeak,
params: params,
timeoutMs: max(30000, input.synthTimeoutSeconds * 1000 + 5000))
guard let audioData = Data(base64Encoded: result.audiobase64), !audioData.isEmpty else {
throw NSError(domain: "TalkSpeak", code: 1, userInfo: [
NSLocalizedDescriptionKey: "gateway talk.speak returned empty audio",
])
}
_ = await self.stopPCM()
_ = await self.stopMP3()
if self.interruptOnSpeech {
guard await self.prepareForPlayback(generation: input.generation) else { return }
}
await MainActor.run { TalkModeController.shared.updatePhase(.speaking) }
self.phase = .speaking
let playback = await self.playTalkAudio(data: audioData)
self.ttsLogger
.info(
"talk gateway audio provider=\(result.provider, privacy: .public) " +
"format=\(result.outputformat ?? "unknown", privacy: .public) " +
"finished=\(playback.finished, privacy: .public)")
if !playback.finished, playback.interruptedAt == nil {
throw NSError(domain: "TalkSpeak", code: 2, userInfo: [
NSLocalizedDescriptionKey: "gateway talk.speak audio playback failed",
])
}
}
private func playSystemVoice(input: TalkPlaybackInput) async throws {
self.ttsLogger.info("talk system voice start chars=\(input.cleanedText.count, privacy: .public)")
if self.interruptOnSpeech {
@@ -893,54 +847,6 @@ actor TalkModeRuntime {
}
extension TalkModeRuntime {
static func makeTalkSpeakParams(
text: String,
voiceId: String?,
modelId: String?,
outputFormat: String?,
directive: TalkDirective?) -> [String: AnyCodable]
{
var params: [String: AnyCodable] = ["text": AnyCodable(text)]
func addString(_ key: String, _ value: String?) {
let trimmed = value?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
guard !trimmed.isEmpty else { return }
params[key] = AnyCodable(trimmed)
}
addString("voiceId", voiceId)
addString("modelId", directive?.modelId ?? modelId)
addString("outputFormat", directive?.outputFormat ?? outputFormat)
if let speed = directive?.speed {
params["speed"] = AnyCodable(speed)
}
if let rateWPM = directive?.rateWPM {
params["rateWpm"] = AnyCodable(rateWPM)
}
if let stability = directive?.stability {
params["stability"] = AnyCodable(stability)
}
if let similarity = directive?.similarity {
params["similarity"] = AnyCodable(similarity)
}
if let style = directive?.style {
params["style"] = AnyCodable(style)
}
if let speakerBoost = directive?.speakerBoost {
params["speakerBoost"] = AnyCodable(speakerBoost)
}
if let seed = directive?.seed {
params["seed"] = AnyCodable(seed)
}
addString("normalize", directive?.normalize)
addString("language", directive?.language)
if let latencyTier = directive?.latencyTier {
params["latencyTier"] = AnyCodable(latencyTier)
}
return params
}
// MARK: - Audio playback (MainActor helpers)
@MainActor

View File

@@ -66,34 +66,22 @@ struct ExecAllowlistTests {
#expect(match?.pattern == entry.pattern)
}
@Test func `match accepts basename pattern for PATH resolved executable`() {
@Test func `match ignores basename pattern`() {
let entry = ExecAllowlistEntry(pattern: "rg")
let resolution = Self.homebrewRGResolution()
let match = ExecAllowlistMatcher.match(entries: [entry], resolution: resolution)
#expect(match?.pattern == entry.pattern)
#expect(match == nil)
}
@Test func `match accepts basename glob for PATH resolved executable`() {
let entry = ExecAllowlistEntry(pattern: "r?")
let resolution = Self.homebrewRGResolution()
let match = ExecAllowlistMatcher.match(entries: [entry], resolution: resolution)
#expect(match?.pattern == entry.pattern)
}
@Test func `match ignores basename for path selected executable`() {
@Test func `match ignores basename for relative executable`() {
let entry = ExecAllowlistEntry(pattern: "echo")
let relativeResolution = ExecCommandResolution(
let resolution = ExecCommandResolution(
rawExecutable: "./echo",
resolvedPath: "/tmp/oc-basename/echo",
executableName: "echo",
cwd: "/tmp/oc-basename")
let absoluteResolution = ExecCommandResolution(
rawExecutable: "/tmp/oc-basename/echo",
resolvedPath: "/tmp/oc-basename/echo",
executableName: "echo",
cwd: "/tmp/oc-basename")
#expect(ExecAllowlistMatcher.match(entries: [entry], resolution: relativeResolution) == nil)
#expect(ExecAllowlistMatcher.match(entries: [entry], resolution: absoluteResolution) == nil)
let match = ExecAllowlistMatcher.match(entries: [entry], resolution: resolution)
#expect(match == nil)
}
@Test func `match is case insensitive`() {

View File

@@ -33,13 +33,18 @@ struct ExecApprovalHelpersTests {
#expect(ExecApprovalHelpers.isPathPattern("/usr/bin/rg"))
#expect(ExecApprovalHelpers.isPathPattern(" ~/bin/rg "))
#expect(!ExecApprovalHelpers.isPathPattern("rg"))
#expect(ExecApprovalHelpers.isValidAllowlistPattern("rg"))
if case let .invalid(reason) = ExecApprovalHelpers.validateAllowlistPattern(" ") {
#expect(reason == .empty)
} else {
Issue.record("Expected empty pattern rejection")
}
if case let .invalid(reason) = ExecApprovalHelpers.validateAllowlistPattern("echo") {
#expect(reason == .missingPathComponent)
} else {
Issue.record("Expected basename pattern rejection")
}
}
@Test func `requires ask matches policy`() {

View File

@@ -31,7 +31,7 @@ struct ExecApprovalsStoreRefactorTests {
}
@Test
func `update allowlist accepts basename pattern`() async throws {
func `update allowlist reports rejected basename pattern`() async throws {
try await self.withTempStateDir { _ in
let rejected = ExecApprovalsStore.updateAllowlist(
agentId: "main",
@@ -39,10 +39,12 @@ struct ExecApprovalsStoreRefactorTests {
ExecAllowlistEntry(pattern: "echo"),
ExecAllowlistEntry(pattern: "/bin/echo"),
])
#expect(rejected.isEmpty)
#expect(rejected.count == 1)
#expect(rejected.first?.reason == .missingPathComponent)
#expect(rejected.first?.pattern == "echo")
let resolved = ExecApprovalsStore.resolve(agentId: "main")
#expect(resolved.allowlist.map(\.pattern) == ["echo", "/bin/echo"])
#expect(resolved.allowlist.map(\.pattern) == ["/bin/echo"])
}
}

View File

@@ -1,4 +1,3 @@
import OpenClawKit
import Speech
import Testing
@testable import OpenClaw
@@ -17,19 +16,23 @@ struct TalkModeRuntimeSpeechTests {
let elevenLabsPlan = TalkModeRuntime.playbackPlan(
provider: "elevenlabs",
apiKey: "key",
voiceId: "voice")
voiceId: "voice"
)
let missingKeyPlan = TalkModeRuntime.playbackPlan(
provider: "elevenlabs",
apiKey: nil,
voiceId: "voice")
voiceId: "voice"
)
let missingVoicePlan = TalkModeRuntime.playbackPlan(
provider: "elevenlabs",
apiKey: "key",
voiceId: nil)
voiceId: nil
)
let blankKeyPlan = TalkModeRuntime.playbackPlan(
provider: "elevenlabs",
apiKey: "",
voiceId: "voice")
voiceId: "voice"
)
let mlxPlan = TalkModeRuntime.playbackPlan(provider: "mlx", apiKey: nil, voiceId: nil)
let systemPlan = TalkModeRuntime.playbackPlan(provider: "system", apiKey: nil, voiceId: nil)
@@ -40,40 +43,4 @@ struct TalkModeRuntimeSpeechTests {
#expect(mlxPlan == .mlxThenSystemVoice)
#expect(systemPlan == .systemVoiceOnly)
}
@Test func `talk speak params carry resolved voice and directive overrides`() {
let params = TalkModeRuntime.makeTalkSpeakParams(
text: "hello",
voiceId: "voice-123",
modelId: "eleven_v3",
outputFormat: "mp3_44100_128",
directive: TalkDirective(
modelId: "eleven_turbo_v2_5",
speed: 1.1,
rateWPM: 180,
stability: 0.4,
similarity: 0.7,
style: 0.2,
speakerBoost: true,
seed: 42,
normalize: "auto",
language: "en",
outputFormat: "mp3_44100_128",
latencyTier: 3))
#expect(params["text"]?.value as? String == "hello")
#expect(params["voiceId"]?.value as? String == "voice-123")
#expect(params["modelId"]?.value as? String == "eleven_turbo_v2_5")
#expect(params["outputFormat"]?.value as? String == "mp3_44100_128")
#expect(params["speed"]?.value as? Double == 1.1)
#expect(params["rateWpm"]?.value as? Int == 180)
#expect(params["stability"]?.value as? Double == 0.4)
#expect(params["similarity"]?.value as? Double == 0.7)
#expect(params["style"]?.value as? Double == 0.2)
#expect(params["speakerBoost"]?.value as? Bool == true)
#expect(params["seed"]?.value as? Int == 42)
#expect(params["normalize"]?.value as? String == "auto")
#expect(params["language"]?.value as? String == "en")
#expect(params["latencyTier"]?.value as? Int == 3)
}
}

View File

@@ -1,4 +1,4 @@
13b68287fec00108ca66032120909a0eac797ed541e026357e175e3fce5bacdd config-baseline.json
77ee66fb3b2cde94b393712bc03a132b096cf601c193bde1fe42902eecb0b66b config-baseline.core.json
d72032762ab46b99480b57deb81130a0ab5b1401189cfbaf4f7fef4a063a7f6c config-baseline.channel.json
0d5ba81f0030bd39b7ae285096276cc18b150836c2252fd2217329fc6154e80e config-baseline.plugin.json
8f23e853ccde6cd021b84b32fe205f456f8516667683d16c9b56d6598f608989 config-baseline.json
037bf4a873587adb8349f531c0ad79cd4f90e01712f5aa5d8b4387be73538a7f config-baseline.core.json
22d7cd6d8279146b2d79c9531a55b80b52a2c99c81338c508104729154fdd02d config-baseline.channel.json
86f615b7d267b03888af0af7ccb3f8232a6b636f8a741d522ff425e46729ba81 config-baseline.plugin.json

View File

@@ -3,18 +3,6 @@
"source": "OpenClaw",
"target": "OpenClaw"
},
{
"source": "OpenAI",
"target": "OpenAI"
},
{
"source": "OpenAI provider",
"target": "OpenAI provider"
},
{
"source": "Status",
"target": "Status"
},
{
"source": "Gateway",
"target": "Gateway 网关"

View File

@@ -86,8 +86,6 @@ This fires ~56 times per month instead of 01 times per month. OpenClaw use
**Main session** jobs enqueue a system event and optionally wake the heartbeat (`--wake now` or `--wake next-heartbeat`). **Isolated** jobs run a dedicated agent turn with a fresh session. **Custom sessions** (`session:xxx`) persist context across runs, enabling workflows like daily standups that build on previous summaries.
For isolated jobs, “fresh session” means a new transcript/session id for each run. OpenClaw may carry safe preferences such as thinking/fast/verbose settings, labels, and explicit user-selected model/auth overrides, but it does not inherit ambient conversation context from an older cron row: channel/group routing, send or queue policy, elevation, origin, or ACP runtime binding. Use `current` or `session:<id>` when a recurring job should deliberately build on the same conversation context.
For isolated jobs, runtime teardown now includes best-effort browser cleanup for that cron session. Cleanup failures are ignored so the actual cron result still wins.
Isolated cron runs also dispose any bundled MCP runtime instances created for the job through the shared runtime-cleanup path. This matches how main-session and custom-session MCP clients are torn down, so isolated cron jobs do not leak stdio child processes or long-lived MCP connections across runs.
@@ -96,11 +94,6 @@ When isolated cron runs orchestrate subagents, delivery also prefers the final
descendant output over stale parent interim text. If descendants are still
running, OpenClaw suppresses that partial parent update instead of announcing it.
For text-only Discord announce targets, OpenClaw sends the canonical final
assistant text once instead of replaying both streamed/intermediate text payloads
and the final answer. Media and structured Discord payloads are still delivered
as separate payloads so attachments and components are not dropped.
### Payload options for isolated jobs
- `--message`: prompt text (required for isolated)
@@ -118,7 +111,7 @@ Model-selection precedence for isolated jobs is:
1. Gmail hook model override (when the run came from Gmail and that override is allowed)
2. Per-job payload `model`
3. User-selected stored cron session model override
3. Stored cron session model override
4. Agent/default model selection
Fast mode follows the resolved live selection too. If the selected model config
@@ -126,11 +119,10 @@ has `params.fastMode`, isolated cron uses that by default. A stored session
`fastMode` override still wins over config in either direction.
If an isolated run hits a live model-switch handoff, cron retries with the
switched provider/model and persists that live selection for the active run
before retrying. When the switch also carries a new auth profile, cron persists
that auth profile override for the active run too. Retries are bounded: after
the initial attempt plus 2 switch retries, cron aborts instead of looping
forever.
switched provider/model and persists that live selection before retrying. When
the switch also carries a new auth profile, cron persists that auth profile
override too. Retries are bounded: after the initial attempt plus 2 switch
retries, cron aborts instead of looping forever.
## Delivery and output

View File

@@ -267,9 +267,6 @@ Now create some channels on your Discord server and start chatting. Your agent c
- Guild channels are isolated session keys (`agent:<agentId>:discord:channel:<channelId>`).
- Group DMs are ignored by default (`channels.discord.dm.groupEnabled=false`).
- Native slash commands run in isolated command sessions (`agent:<agentId>:discord:slash:<userId>`), while still carrying `CommandTargetSessionKey` to the routed conversation session.
- Text-only cron/heartbeat announce delivery to Discord uses the final
assistant-visible answer once. Media and structured component payloads remain
multi-message when the agent emits multiple deliverable payloads.
## Forum channels

View File

@@ -430,12 +430,6 @@ Full configuration: [Gateway configuration](/gateway/configuration)
- ✅ Thread replies
- ✅ Media replies stay thread-aware when replying to a thread message
For `groupSessionScope: "group_topic"` and `"group_topic_sender"`, native
Feishu/Lark topic groups use the event `thread_id` (`omt_*`) as the canonical
topic session key. Normal group replies that OpenClaw turns into threads keep
using the reply root message ID (`om_*`) so the first turn and follow-up turn
stay in the same session.
---
## Related

View File

@@ -104,28 +104,6 @@ existing approval as-is and creates a fresh pending upgrade request. Use
`openclaw devices list` to compare the currently approved access with the newly
requested access before you approve.
### Optional trusted-CIDR node auto-approve
Device pairing remains manual by default. For tightly controlled node networks,
you can opt in to first-time node auto-approval with explicit CIDRs or exact IPs:
```json5
{
gateway: {
nodes: {
pairing: {
autoApproveCidrs: ["192.168.1.0/24"],
},
},
},
}
```
This only applies to fresh `role: node` pairing requests with no requested
scopes. Operator, browser, Control UI, and WebChat clients still require manual
approval. Role, scope, metadata, and public-key changes still require manual
approval.
### Node pairing state storage
Stored under `~/.openclaw/devices/`:

View File

@@ -208,7 +208,6 @@ Groups:
- Outbound text is chunked to `channels.signal.textChunkLimit` (default 4000).
- Optional newline chunking: set `channels.signal.chunkMode="newline"` to split on blank lines (paragraph boundaries) before length chunking.
- Attachments supported (base64 fetched from `signal-cli`).
- Voice-note attachments use the `signal-cli` filename as a MIME fallback when `contentType` is missing, so audio transcription can still classify AAC voice memos.
- Default media cap: `channels.signal.mediaMaxMb` (default 8).
- Use `channels.signal.ignoreAttachments` to skip downloading media.
- Group history context uses `channels.signal.historyLimit` (or `channels.signal.accounts.*.historyLimit`), falling back to `messages.groupChat.historyLimit`. Set `0` to disable (default 50).

View File

@@ -273,28 +273,9 @@ curl "https://api.telegram.org/bot<bot_token>/getUpdates"
- `channels.telegram.streaming` is `off | partial | block | progress` (default: `partial`)
- `progress` maps to `partial` on Telegram (compat with cross-channel naming)
- `streaming.preview.toolProgress` controls whether tool/progress updates reuse the same edited preview message (default: `true` when preview streaming is active)
- `streaming.preview.toolProgress` controls whether tool/progress updates reuse the same edited preview message (default: `true`). Set `false` to keep separate tool/progress messages.
- legacy `channels.telegram.streamMode` and boolean `streaming` values are auto-mapped
Tool-progress preview updates are the short "Working..." lines shown while tools run, for example command execution, file reads, planning updates, or patch summaries. Telegram keeps these enabled by default to match released OpenClaw behavior from `v2026.4.22` and later. To keep the edited preview for answer text but hide tool-progress lines, set:
```json
{
"channels": {
"telegram": {
"streaming": {
"mode": "partial",
"preview": {
"toolProgress": false
}
}
}
}
}
```
Use `streaming.mode: "off"` only when you want to disable Telegram preview edits entirely. Use `streaming.preview.toolProgress: false` when you only want to disable the tool-progress status lines.
For text-only replies:
- DM: OpenClaw keeps the same preview message and performs a final edit in place (no second message)

View File

@@ -361,7 +361,6 @@ When the linked self number is also present in `allowFrom`, WhatsApp self-chat s
<Accordion title="Outbound media behavior">
- supports image, video, audio (PTT voice-note), and document payloads
- reply payloads preserve `audioAsVoice`; WhatsApp sends audio media as Baileys PTT voice notes
- `audio/ogg` is rewritten to `audio/ogg; codecs=opus` for voice-note compatibility
- animated GIF playback is supported via `gifPlayback: true` on video sends
- captions are applied to the first media item when sending multi-media reply payloads

View File

@@ -79,7 +79,7 @@ gh workflow run duplicate-after-merge.yml \
| `android` | Android unit tests for both flavors plus one debug APK build | Android-relevant changes |
| `test-performance-agent` | Daily Codex slow-test optimization after trusted activity | Main CI success or manual dispatch |
## Fail-fast order
## Fail-Fast Order
Jobs are ordered so cheap checks fail before expensive ones run:
@@ -90,9 +90,8 @@ Jobs are ordered so cheap checks fail before expensive ones run:
Scope logic lives in `scripts/ci-changed-scope.mjs` and is covered by unit tests in `src/scripts/ci-changed-scope.test.ts`.
CI workflow edits validate the Node CI graph plus workflow linting, but do not force Windows, Android, or macOS native builds by themselves; those platform lanes stay scoped to platform source changes.
CI routing-only edits, selected cheap core-test fixture edits, and narrow plugin contract helper/test-routing edits use a fast Node-only manifest path: preflight, security, and a single `checks-fast-core` task. That path avoids build artifacts, Node 22 compatibility, channel contracts, full core shards, bundled-plugin shards, and additional guard matrices when the changed files are limited to the routing or helper surfaces that the fast task exercises directly.
Windows Node checks are scoped to Windows-specific process/path wrappers, npm/pnpm/UI runner helpers, package manager config, and the CI workflow surfaces that execute that lane; unrelated source, plugin, install-smoke, and test-only changes stay on the Linux Node lanes so they do not reserve a 16-vCPU Windows worker for coverage that is already exercised by the normal test shards.
The separate `install-smoke` workflow reuses the same scope script through its own `preflight` job. It splits smoke coverage into `run_fast_install_smoke` and `run_full_install_smoke`. Pull requests run the fast path for Docker/package surfaces, bundled plugin package/manifest changes, and core plugin/channel/gateway/Plugin SDK surfaces that the Docker smoke jobs exercise. Source-only bundled plugin changes, test-only edits, and docs-only edits do not reserve Docker workers. The fast path builds the root Dockerfile image once, checks the CLI, runs the agents delete shared-workspace CLI smoke, runs the container gateway-network e2e, verifies a bundled extension build arg, and runs the bounded bundled-plugin Docker profile under a 240-second aggregate command timeout with each scenario's Docker run capped separately. The full path keeps QR package install and installer Docker/update coverage for nightly scheduled runs, manual dispatches, workflow-call release checks, and pull requests that truly touch installer/package/Docker surfaces. `main` pushes, including merge commits, do not force the full path; when changed-scope logic would request full coverage on a push, the workflow keeps the fast Docker smoke and leaves the full install smoke to nightly or release validation. The slow Bun global install image-provider smoke is separately gated by `run_bun_global_install_smoke`; it runs on the nightly schedule and from the release checks workflow, and manual `install-smoke` dispatches can opt into it, but pull requests and `main` pushes do not run it. QR and installer Docker tests keep their own install-focused Dockerfiles. Local `test:docker:all` prebuilds one shared live-test image and one shared `scripts/e2e/Dockerfile` built-app image, then runs the live/E2E smoke lanes with a weighted scheduler and `OPENCLAW_SKIP_DOCKER_BUILD=1`; tune the default main-pool slot count of 10 with `OPENCLAW_DOCKER_ALL_PARALLELISM` and the provider-sensitive tail-pool slot count of 10 with `OPENCLAW_DOCKER_ALL_TAIL_PARALLELISM`. Heavy lane caps default to `OPENCLAW_DOCKER_ALL_LIVE_LIMIT=6`, `OPENCLAW_DOCKER_ALL_NPM_LIMIT=8`, and `OPENCLAW_DOCKER_ALL_SERVICE_LIMIT=7` so npm install and multi-service lanes do not overcommit Docker while lighter lanes still fill available slots. Lane starts are staggered by 2 seconds by default to avoid local Docker daemon create storms; override with `OPENCLAW_DOCKER_ALL_START_STAGGER_MS=0` or another millisecond value. The local aggregate preflights Docker, removes stale OpenClaw E2E containers, emits active-lane status, persists lane timings for longest-first ordering, and supports `OPENCLAW_DOCKER_ALL_DRY_RUN=1` for scheduler inspection. It stops scheduling new pooled lanes after the first failure by default, and each lane has a 120-minute fallback timeout overrideable with `OPENCLAW_DOCKER_ALL_LANE_TIMEOUT_MS`; selected live/tail lanes use tighter per-lane caps. The reusable live/E2E workflow mirrors the shared-image pattern by building and pushing one SHA-tagged GHCR Docker E2E image before the Docker matrix, then running the matrix with `OPENCLAW_SKIP_DOCKER_BUILD=1`. The scheduled live/E2E workflow runs the full release-path Docker suite daily. The bundled update matrix is split by update target so repeated npm update and doctor repair passes can shard with other bundled checks.
The separate `install-smoke` workflow reuses the same scope script through its own `preflight` job. It splits smoke coverage into `run_fast_install_smoke` and `run_full_install_smoke`. Pull requests run the fast path for Docker/package surfaces, bundled plugin package/manifest changes, and core plugin/channel/gateway/Plugin SDK surfaces that the Docker smoke jobs exercise. Source-only bundled plugin changes, test-only edits, and docs-only edits do not reserve Docker workers. The fast path builds the root Dockerfile image once, checks the CLI, runs the agents delete shared-workspace CLI smoke, runs the container gateway-network e2e, verifies a bundled extension build arg, and runs the bounded bundled-plugin Docker profile under a 120-second command timeout. The full path keeps QR package install and installer Docker/update coverage for nightly scheduled runs, manual dispatches, workflow-call release checks, and pull requests that truly touch installer/package/Docker surfaces. `main` pushes, including merge commits, do not force the full path; when changed-scope logic would request full coverage on a push, the workflow keeps the fast Docker smoke and leaves the full install smoke to nightly or release validation. The slow Bun global install image-provider smoke is separately gated by `run_bun_global_install_smoke`; it runs on the nightly schedule and from the release checks workflow, and manual `install-smoke` dispatches can opt into it, but pull requests and `main` pushes do not run it. QR and installer Docker tests keep their own install-focused Dockerfiles. Local `test:docker:all` prebuilds one shared live-test image and one shared `scripts/e2e/Dockerfile` built-app image, then runs the live/E2E smoke lanes with a weighted scheduler and `OPENCLAW_SKIP_DOCKER_BUILD=1`; tune the default main-pool slot count of 10 with `OPENCLAW_DOCKER_ALL_PARALLELISM` and the provider-sensitive tail-pool slot count of 10 with `OPENCLAW_DOCKER_ALL_TAIL_PARALLELISM`. Heavy lane caps default to `OPENCLAW_DOCKER_ALL_LIVE_LIMIT=6`, `OPENCLAW_DOCKER_ALL_NPM_LIMIT=8`, and `OPENCLAW_DOCKER_ALL_SERVICE_LIMIT=7` so npm install and multi-service lanes do not overcommit Docker while lighter lanes still fill available slots. Lane starts are staggered by 2 seconds by default to avoid local Docker daemon create storms; override with `OPENCLAW_DOCKER_ALL_START_STAGGER_MS=0` or another millisecond value. The local aggregate preflights Docker, removes stale OpenClaw E2E containers, emits active-lane status, persists lane timings for longest-first ordering, and supports `OPENCLAW_DOCKER_ALL_DRY_RUN=1` for scheduler inspection. It stops scheduling new pooled lanes after the first failure by default, and each lane has a 120-minute fallback timeout overrideable with `OPENCLAW_DOCKER_ALL_LANE_TIMEOUT_MS`; selected live/tail lanes use tighter per-lane caps. The reusable live/E2E workflow mirrors the shared-image pattern by building and pushing one SHA-tagged GHCR Docker E2E image before the Docker matrix, then running the matrix with `OPENCLAW_SKIP_DOCKER_BUILD=1`. The scheduled live/E2E workflow runs the full release-path Docker suite daily. The bundled update matrix is split by update target so repeated npm update and doctor repair passes can shard with other bundled checks.
Local changed-lane logic lives in `scripts/changed-lanes.mjs` and is executed by `scripts/check-changed.mjs`. That local gate is stricter about architecture boundaries than the broad CI platform scope: core production changes run core prod typecheck plus core tests, core test-only changes run only core test typecheck/tests, extension production changes run extension prod typecheck plus extension tests, and extension test-only changes run only extension test typecheck/tests. Public Plugin SDK or plugin-contract changes expand to extension validation because extensions depend on those core contracts. Release metadata-only version bumps run targeted version/config/root-dependency checks. Unknown root/config changes fail safe to all lanes.

View File

@@ -53,7 +53,6 @@ openclaw agent --agent ops --message "Run locally" --local
- Gateway mode falls back to the embedded agent when the Gateway request fails. Use `--local` to force embedded execution up front.
- `--local` still preloads the plugin registry first, so plugin-provided providers, tools, and channels stay available during embedded runs.
- `--channel`, `--reply-channel`, and `--reply-account` affect reply delivery, not session routing.
- `--json` keeps stdout reserved for the JSON response. Gateway, plugin, and embedded-fallback diagnostics are routed to stderr so scripts can parse stdout directly.
- When this command triggers `models.json` regeneration, SecretRef-managed provider credentials are persisted as non-secret markers (for example env var names, `secretref-env:ENV_VAR_NAME`, or `secretref-managed`), not resolved secret plaintext.
- Marker writes are source-authoritative: OpenClaw persists markers from the active source config snapshot, not from resolved runtime secret values.

View File

@@ -164,7 +164,6 @@ Navigate/click/type (ref-based UI automation):
```bash
openclaw browser navigate https://example.com
openclaw browser click <ref>
openclaw browser click-coords 120 340
openclaw browser type <ref> "hello"
openclaw browser press Enter
openclaw browser hover <ref>

View File

@@ -36,7 +36,6 @@ openclaw config --section gateway --section daemon
openclaw config schema
openclaw config get browser.executablePath
openclaw config set browser.executablePath "/usr/bin/google-chrome"
openclaw config set browser.profiles.work.executablePath "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
openclaw config set agents.defaults.heartbeat.every "2h"
openclaw config set agents.list[0].tools.exec.node "node-id-or-name"
openclaw config set agents.defaults.models '{"openai/gpt-5.4":{}}' --strict-json --merge
@@ -186,7 +185,7 @@ openclaw config set secrets.providers.vaultfile \
--strict-json
```
## Provider builder flags
## Provider Builder Flags
Provider builder targets must use `secrets.providers.<alias>` as the path.
@@ -279,7 +278,7 @@ Dry-run behavior:
- `skippedExecRefs`: number of exec refs skipped because `--allow-exec` was not set
- `errors`: structured schema/resolvability failures when `ok=false`
### JSON output shape
### JSON Output Shape
```json5
{
@@ -386,15 +385,6 @@ untrusted until they validate. Invalid direct edits can be restored from the
last-known-good backup during startup or hot reload. See
[Gateway troubleshooting](/gateway/troubleshooting#gateway-restored-last-known-good-config).
Whole-file recovery is reserved for globally broken config, such as parse
errors, root-level schema failures, legacy migration failures, or mixed plugin
and root failures. If validation fails only under `plugins.entries.<id>...`,
OpenClaw keeps the active `openclaw.json` in place and reports the plugin-local
issue instead of restoring `.last-good`. This prevents plugin schema changes or
`minHostVersion` skew from rolling back unrelated user settings such as models,
providers, auth profiles, channels, gateway exposure, tools, memory, browser, or
cron config.
## Subcommands
- `config file`: Print the active config file path (resolved from `OPENCLAW_CONFIG_PATH` or default location). The path should name a regular file, not a symlink.

View File

@@ -33,11 +33,6 @@ Note: `--session` supports `main`, `isolated`, `current`, and `session:<id>`.
Use `current` to bind to the active session at creation time, or `session:<id>` for
an explicit persistent session key.
Note: `--session isolated` creates a fresh transcript/session id for each run.
Safe preferences and explicit user-selected model/auth overrides can carry, but
ambient conversation context does not: channel/group routing, send/queue policy,
elevation, origin, and ACP runtime binding are reset for the new isolated run.
Note: for one-shot CLI jobs, offset-less `--at` datetimes are treated as UTC unless you also pass
`--tz <iana>`, which interprets that local wall-clock time in the given timezone.
@@ -64,17 +59,17 @@ model override with no explicit per-job fallback list no longer appends the
agent primary as a hidden extra retry target.
Note: isolated cron model precedence is Gmail-hook override first, then per-job
`--model`, then any user-selected stored cron-session model override, then the
normal agent/default selection.
`--model`, then any stored cron-session model override, then the normal
agent/default selection.
Note: isolated cron fast mode follows the resolved live model selection. Model
config `params.fastMode` applies by default, but a stored session `fastMode`
override still wins over config.
Note: if an isolated run throws `LiveSessionModelSwitchError`, cron persists the
switched provider/model (and switched auth profile override when present) for
the active run before retrying. The outer retry loop is bounded to 2 switch
retries after the initial attempt, then aborts instead of looping forever.
switched provider/model (and switched auth profile override when present) before
retrying. The outer retry loop is bounded to 2 switch retries after the initial
attempt, then aborts instead of looping forever.
Note: failure notifications use `delivery.failureDestination` first, then
global `cron.failureDestination`, and finally fall back to the job's primary

View File

@@ -66,12 +66,6 @@ request. Review the `Requested` vs `Approved` columns in `openclaw devices list`
or use `openclaw devices approve --latest` to preview the exact upgrade before
approving it.
If the Gateway is explicitly configured with
`gateway.nodes.pairing.autoApproveCidrs`, first-time `role: node` requests from
matching client IPs can be approved before they appear in this list. That policy
is disabled by default and never applies to operator/browser clients or upgrade
requests.
```
openclaw devices approve
openclaw devices approve <requestId>
@@ -133,8 +127,6 @@ Pass `--token` or `--password` explicitly. Missing explicit credentials is an er
- Token rotation returns a new token (sensitive). Treat it like a secret.
- These commands require `operator.pairing` (or `operator.admin`) scope.
- `gateway.nodes.pairing.autoApproveCidrs` is an opt-in Gateway policy for
fresh node device pairing only; it does not change CLI approval authority.
- Token rotation stays inside the approved pairing role set and approved scope
baseline for that device. A stray cached token entry does not grant a new
rotate target.

View File

@@ -376,9 +376,6 @@ Important behavior:
- embedded Pi exposes configured MCP tools in normal `coding` and `messaging`
tool profiles; `minimal` still hides them, and `tools.deny: ["bundle-mcp"]`
disables them explicitly
- session-scoped bundled MCP runtimes are reaped after `mcp.sessionIdleTtlMs`
milliseconds of idle time (default 10 minutes; set `0` to disable) and
one-shot embedded runs clean them up at run end
## Saved MCP server definitions

View File

@@ -50,10 +50,6 @@ Notes:
- `models list --all` includes bundled provider-owned static catalog rows even
when you have not authenticated with that provider yet. Those rows still show
as unavailable until matching auth is configured.
- `models list` keeps native model metadata and runtime caps distinct. In table
output, `Ctx` shows `contextTokens/contextWindow` when an effective runtime
cap differs from the native context window; JSON rows include `contextTokens`
when a provider exposes that cap.
- `models list --provider <id>` filters by provider id, such as `moonshot` or
`openai-codex`. It does not accept display labels from interactive provider
pickers, such as `Moonshot AI`.

View File

@@ -123,25 +123,6 @@ openclaw devices list
openclaw devices approve <requestId>
```
On tightly controlled node networks, the Gateway operator can explicitly opt in
to auto-approving first-time node pairing from trusted CIDRs:
```json5
{
gateway: {
nodes: {
pairing: {
autoApproveCidrs: ["192.168.1.0/24"],
},
},
},
}
```
This is disabled by default. It only applies to fresh `role: node` pairing with
no requested scopes. Operator/browser clients, Control UI, WebChat, and role,
scope, metadata, or public-key upgrades still require manual approval.
If the node retries pairing with changed auth details (role/scopes/public key),
the previous pending request is superseded and a new `requestId` is created.
Run `openclaw devices list` again before approval.

View File

@@ -42,9 +42,6 @@ filter to nodes that connected within a duration (e.g. `24h`, `7d`).
Approval note:
- `openclaw nodes pending` only needs pairing scope.
- `gateway.nodes.pairing.autoApproveCidrs` can skip the pending step only for
explicitly trusted, first-time `role: node` device pairing. It is off by
default and does not approve upgrades.
- `openclaw nodes approve <requestId>` inherits extra scope requirements from the
pending request:
- commandless request: pairing only

View File

@@ -2,7 +2,7 @@
summary: "CLI reference for `openclaw voicecall` (voice-call plugin command surface)"
read_when:
- You use the voice-call plugin and want the CLI entry points
- You want quick examples for `voicecall setup|smoke|call|continue|dtmf|status|tail|expose`
- You want quick examples for `voicecall call|continue|dtmf|status|tail|expose`
title: "Voicecall"
---
@@ -17,8 +17,6 @@ Primary doc:
## Common commands
```bash
openclaw voicecall setup
openclaw voicecall smoke
openclaw voicecall status --call-id <id>
openclaw voicecall call --to "+15555550123" --message "Hello" --mode notify
openclaw voicecall continue --call-id <id> --message "Any questions?"
@@ -26,25 +24,6 @@ openclaw voicecall dtmf --call-id <id> --digits "ww123456#"
openclaw voicecall end --call-id <id>
```
`setup` prints human-readable readiness checks by default. Use `--json` for
scripts:
```bash
openclaw voicecall setup --json
```
For external providers (`twilio`, `telnyx`, `plivo`), setup must resolve a public
webhook URL from `publicUrl`, a tunnel, or Tailscale exposure. A loopback/private
serve fallback is rejected because carriers cannot reach it.
`smoke` runs the same readiness checks. It will not place a real phone call
unless both `--to` and `--yes` are present:
```bash
openclaw voicecall smoke --to "+15555550123" # dry run
openclaw voicecall smoke --to "+15555550123" --yes # live notify call
```
## Exposing webhooks (Tailscale)
```bash

View File

@@ -14,7 +14,7 @@ the finished turn to OpenClaw.
Runtimes are easy to confuse with providers because both show up near model
configuration. They are different layers:
| Layer | Examples | What it means |
| Layer | Examples | What It Means |
| ------------- | ------------------------------------- | ------------------------------------------------------------------- |
| Provider | `openai`, `anthropic`, `openai-codex` | How OpenClaw authenticates, discovers models, and names model refs. |
| Model | `gpt-5.5`, `claude-opus-4-6` | The model selected for the agent turn. |
@@ -46,10 +46,6 @@ That means OpenClaw selects an OpenAI model ref, then asks the Codex app-server
runtime to run the embedded agent turn. It does not mean the channel, model
provider catalog, or OpenClaw session store becomes Codex.
For the OpenAI-family prefix split, see [OpenAI](/providers/openai) and
[Model providers](/concepts/model-providers). For the Codex runtime support
contract, see [Codex harness](/plugins/codex-harness#v1-support-contract).
## Runtime ownership
Different runtimes own different amounts of the loop.
@@ -88,16 +84,14 @@ OpenClaw chooses an embedded runtime after provider and model resolution:
Explicit plugin runtimes fail closed by default. For example,
`runtime: "codex"` means Codex or a clear selection error unless you set
`fallback: "pi"` in the same override scope. A runtime override does not inherit
a broader fallback setting, so an agent-level `runtime: "codex"` is not silently
routed back to PI just because defaults used `fallback: "pi"`.
`fallback: "pi"` in the same override scope.
## Compatibility contract
When a runtime is not PI, it should document what OpenClaw surfaces it supports.
Use this shape for runtime docs:
| Question | Why it matters |
| Question | Why It Matters |
| -------------------------------------- | ------------------------------------------------------------------------------------------------- |
| Who owns the model loop? | Determines where retries, tool continuation, and final answer decisions happen. |
| Who owns canonical thread history? | Determines whether OpenClaw can edit history or only mirror it. |
@@ -128,8 +122,6 @@ session systems.
## Related
- [Codex harness](/plugins/codex-harness)
- [OpenAI](/providers/openai)
- [Agent harness plugins](/plugins/sdk-agent-harness)
- [Agent loop](/concepts/agent-loop)
- [Models](/concepts/models)
- [Status](/cli/status)

View File

@@ -113,11 +113,6 @@ the summary:
/compact Focus on the API design decisions
```
When `agents.defaults.compaction.keepRecentTokens` is set, manual compaction
honors that Pi cut-point and keeps the recent tail in rebuilt context. Without
an explicit keep budget, manual compaction behaves as a hard checkpoint and
continues from the new summary alone.
## Using a different model
By default, compaction uses your agent's primary model. You can use a more

View File

@@ -6,7 +6,8 @@ read_when:
title: "Model providers"
---
Reference for **LLM/model providers** (not chat channels like WhatsApp/Telegram). For model selection rules, see [Models](/concepts/models).
This page covers **LLM/model providers** (not chat channels like WhatsApp/Telegram).
For model selection rules, see [/concepts/models](/concepts/models).
## Quick rules
@@ -19,8 +20,7 @@ Reference for **LLM/model providers** (not chat channels like WhatsApp/Telegram)
OpenAI API-key provider in PI, `openai-codex/<model>` uses Codex OAuth in PI,
and `openai/<model>` plus `agents.defaults.embeddedHarness.runtime: "codex"`
uses the native Codex app-server harness. See [OpenAI](/providers/openai)
and [Codex harness](/plugins/codex-harness). If the provider/runtime split is
confusing, read [Agent runtimes](/concepts/agent-runtimes) first.
and [Codex harness](/plugins/codex-harness).
- Plugin auto-enable follows that same boundary: `openai-codex/<model>` belongs
to the OpenAI plugin, while the Codex plugin is enabled by
`embeddedHarness.runtime: "codex"` or legacy `codex/<model>` refs.
@@ -30,9 +30,11 @@ Reference for **LLM/model providers** (not chat channels like WhatsApp/Telegram)
`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.
- GPT-5.5 is available through `openai-codex/gpt-5.5` in PI, the native
Codex app-server harness, and the public OpenAI API when the bundled PI
catalog exposes `openai/gpt-5.5` for your install.
- GPT-5.5 is currently available through subscription/OAuth routes:
`openai-codex/gpt-5.5` in PI or `openai/gpt-5.5` with the Codex app-server
harness. The direct API-key route for `openai/gpt-5.5` is supported once
OpenAI enables GPT-5.5 on the public API; until then use API-enabled models
such as `openai/gpt-5.4` for `OPENAI_API_KEY` setups.
## Plugin-owned provider behavior
@@ -71,10 +73,8 @@ OpenClaw ships with the piai catalog. These providers require **no**
- Provider: `openai`
- Auth: `OPENAI_API_KEY`
- Optional rotation: `OPENAI_API_KEYS`, `OPENAI_API_KEY_1`, `OPENAI_API_KEY_2`, plus `OPENCLAW_LIVE_OPENAI_KEY` (single override)
- Example models: `openai/gpt-5.5`, `openai/gpt-5.4`, `openai/gpt-5.4-mini`
- GPT-5.5 direct API support depends on the bundled PI catalog version for
your install; verify with `openclaw models list --provider openai` before
using `openai/gpt-5.5` without the Codex app-server runtime.
- Example models: `openai/gpt-5.4`, `openai/gpt-5.4-mini`
- GPT-5.5 direct API support is future-ready here once OpenAI exposes GPT-5.5 on the API
- CLI: `openclaw onboard --auth-choice openai-api-key`
- Default transport is `auto` (WebSocket-first, SSE fallback)
- Override per model via `agents.defaults.models["openai/<model>"].params.transport` (`"sse"`, `"websocket"`, or `"auto"`)
@@ -118,7 +118,6 @@ OpenClaw ships with the piai catalog. These providers require **no**
- Auth: OAuth (ChatGPT)
- PI model ref: `openai-codex/gpt-5.5`
- Native Codex app-server harness ref: `openai/gpt-5.5` with `agents.defaults.embeddedHarness.runtime: "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
@@ -131,9 +130,9 @@ OpenClaw ships with the piai catalog. These providers require **no**
`User-Agent`) are only attached on native Codex traffic to
`chatgpt.com/backend-api`, not generic OpenAI-compatible proxies
- 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`
- `openai-codex/gpt-5.5` keeps native `contextWindow = 1000000` and a 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.
- Use `openai-codex/gpt-5.5` when you want the Codex OAuth/subscription route; use `openai/gpt-5.5` when your API-key setup and local catalog expose the public API route.
- Current GPT-5.5 access uses this OAuth/subscription route until OpenAI enables GPT-5.5 on the public API.
```json5
{
@@ -157,14 +156,14 @@ OpenClaw ships with the piai catalog. These providers require **no**
- [Qwen Cloud](/providers/qwen): Qwen Cloud provider surface plus Alibaba DashScope and Coding Plan endpoint mapping
- [MiniMax](/providers/minimax): MiniMax Coding Plan OAuth or API key access
- [GLM models](/providers/glm): Z.AI Coding Plan or general API endpoints
- [GLM Models](/providers/glm): Z.AI Coding Plan or general API endpoints
### OpenCode
- Auth: `OPENCODE_API_KEY` (or `OPENCODE_ZEN_API_KEY`)
- Zen runtime provider: `opencode`
- Go runtime provider: `opencode-go`
- Example models: `opencode/claude-opus-4-6`, `opencode-go/kimi-k2.6`
- Example models: `opencode/claude-opus-4-6`, `opencode-go/kimi-k2.5`
- CLI: `openclaw onboard --auth-choice opencode-zen` or `openclaw onboard --auth-choice opencode-go`
```json5
@@ -267,7 +266,7 @@ See [/providers/kilocode](/providers/kilocode) for setup details.
Quirks worth knowing:
- **OpenRouter** applies its app-attribution headers and Anthropic `cache_control` markers only on verified `openrouter.ai` routes. DeepSeek, Moonshot, and ZAI refs are cache-TTL eligible for OpenRouter-managed prompt caching but do not receive Anthropic cache markers. As a proxy-style OpenAI-compatible path, it skips native-OpenAI-only shaping (`serviceTier`, Responses `store`, prompt-cache hints, OpenAI reasoning-compat). Gemini-backed refs keep proxy-Gemini thought-signature sanitation only.
- **OpenRouter** applies its app-attribution headers and Anthropic `cache_control` markers only on verified `openrouter.ai` routes. As a proxy-style OpenAI-compatible path, it skips native-OpenAI-only shaping (`serviceTier`, Responses `store`, prompt-cache hints, OpenAI reasoning-compat). Gemini-backed refs keep proxy-Gemini thought-signature sanitation only.
- **Kilo Gateway** Gemini-backed refs follow the same proxy-Gemini sanitation path; `kilocode/kilo/auto` and other proxy-reasoning-unsupported refs skip proxy reasoning injection.
- **MiniMax** API-key onboarding writes explicit text-only M2.7 chat model definitions; image understanding stays on the plugin-owned `MiniMax-VL-01` media provider.
- **xAI** uses the xAI Responses path. `/fast` or `params.fastMode: true` rewrites `grok-3`, `grok-3-mini`, `grok-4`, and `grok-4-0709` to their `*-fast` variants. `tool_stream` defaults on; disable via `agents.defaults.models["xai/<model>"].params.tool_stream=false`.
@@ -626,12 +625,9 @@ Notes:
- Recommended: set explicit values that match your proxy/model limits.
- For `api: "openai-completions"` on non-native endpoints (any non-empty `baseUrl` whose host is not `api.openai.com`), OpenClaw forces `compat.supportsDeveloperRole: false` to avoid provider 400 errors for unsupported `developer` roles.
- Proxy-style OpenAI-compatible routes also skip native OpenAI-only request
shaping: no `service_tier`, no Responses `store`, no Completions `store`, no
prompt-cache hints, no OpenAI reasoning-compat payload shaping, and no hidden
OpenClaw attribution headers.
- For OpenAI-compatible Completions proxies that need vendor-specific fields,
set `agents.defaults.models["provider/model"].params.extra_body` (or
`extraBody`) to merge extra JSON into the outbound request body.
shaping: no `service_tier`, no Responses `store`, no prompt-cache hints, no
OpenAI reasoning-compat payload shaping, and no hidden OpenClaw attribution
headers.
- If `baseUrl` is empty/omitted, OpenClaw keeps the default OpenAI behavior (which resolves to `api.openai.com`).
- For safety, an explicit `compat.supportsDeveloperRole: true` is still overridden on non-native `openai-completions` endpoints.
@@ -643,11 +639,11 @@ openclaw models set opencode/claude-opus-4-6
openclaw models list
```
See also: [Configuration](/gateway/configuration) for full configuration examples.
See also: [/gateway/configuration](/gateway/configuration) for full configuration examples.
## Related
- [Models](/concepts/models) — model configuration and aliases
- [Model failover](/concepts/model-failover) — fallback chains and retry behavior
- [Configuration reference](/gateway/config-agents#agent-defaults) — model config keys
- [Model Failover](/concepts/model-failover) — fallback chains and retry behavior
- [Configuration Reference](/gateway/config-agents#agent-defaults) — model config keys
- [Providers](/providers) — per-provider setup guides

View File

@@ -171,8 +171,8 @@ How to see what profile IDs exist:
Related docs:
- [Model failover](/concepts/model-failover) (rotation + cooldown rules)
- [Slash commands](/tools/slash-commands) (command surface)
- [/concepts/model-failover](/concepts/model-failover) (rotation + cooldown rules)
- [/tools/slash-commands](/tools/slash-commands) (command surface)
## Related

View File

@@ -7,6 +7,8 @@ read_when:
title: "Streaming and chunking"
---
# Streaming + chunking
OpenClaw has two separate streaming layers:
- **Block streaming (channels):** emit completed **blocks** as the assistant writes. These are normal channel messages (not token deltas).
@@ -52,19 +54,6 @@ Legend:
`message_end` still uses the chunker if the buffered text exceeds `maxChars`, so it can emit multiple chunks at the end.
### Media delivery with block streaming
`MEDIA:` directives are normal delivery metadata. When block streaming sends a
media block early, OpenClaw remembers that delivery for the turn. If the final
assistant payload repeats the same media URL, the final delivery strips the
duplicate media instead of sending the attachment again.
Exact duplicate final payloads are suppressed. If the final payload adds
distinct text around media that was already streamed, OpenClaw still sends the
new text while keeping the media single-delivery. This prevents duplicate voice
notes or files on channels such as Telegram when an agent emits `MEDIA:` during
streaming and the provider also includes it in the completed reply.
## Chunking algorithm (low/high bounds)
Block chunking is implemented by `EmbeddedBlockChunker`:
@@ -187,28 +176,9 @@ Preview streaming can also include **tool-progress** updates — short status li
Supported surfaces:
- **Discord**, **Slack**, and **Telegram** stream tool-progress into the live preview edit by default when preview streaming is active.
- Telegram has shipped with tool-progress preview updates enabled since `v2026.4.22`; keeping them enabled preserves that released behavior.
- **Discord**, **Slack**, and **Telegram** stream tool-progress into the live preview edit.
- **Mattermost** already folds tool activity into its single draft preview post (see above).
- Tool-progress edits follow the active preview streaming mode; they are skipped when preview streaming is `off` or when block streaming has taken over the message.
- To keep preview streaming but hide tool-progress lines, set `streaming.preview.toolProgress` to `false` for that channel. To disable preview edits entirely, set `streaming.mode` to `off`.
Example:
```json
{
"channels": {
"telegram": {
"streaming": {
"mode": "partial",
"preview": {
"toolProgress": false
}
}
}
}
}
```
## Related

View File

@@ -1165,7 +1165,6 @@
"plugins/hooks",
"plugins/sdk-channel-plugins",
"plugins/sdk-provider-plugins",
"plugins/compatibility",
"plugins/sdk-migration"
]
},

View File

@@ -349,12 +349,6 @@ When bundle MCP is enabled, OpenClaw:
If no MCP servers are enabled, OpenClaw still injects a strict config when a
backend opts into bundle MCP so background runs stay isolated.
Session-scoped bundled MCP runtimes are cached for reuse within a session, then
reaped after `mcp.sessionIdleTtlMs` milliseconds of idle time (default 10
minutes; set `0` to disable). One-shot embedded runs such as auth probes,
slug generation, and active-memory recall request cleanup at run end so stdio
children and Streamable HTTP/SSE streams do not outlive the run.
## Limitations
- **No direct OpenClaw tool calls.** OpenClaw does not inject tool calls into

View File

@@ -72,7 +72,6 @@ Disables automatic creation of workspace bootstrap files (`AGENTS.md`, `SOUL.md`
Controls when workspace bootstrap files are injected into the system prompt. Default: `"always"`.
- `"continuation-skip"`: safe continuation turns (after a completed assistant response) skip workspace bootstrap re-injection, reducing prompt size. Heartbeat runs and post-compaction retries still rebuild context.
- `"never"`: disable workspace bootstrap and context-file injection on every turn. Use this only for agents that fully own their prompt lifecycle (custom context engines, native runtimes that build their own context, or specialized bootstrap-free workflows). Heartbeat and compaction-recovery turns also skip injection.
```json5
{
@@ -364,13 +363,12 @@ Time format in system prompt. Default: `auto` (OS preference).
- `verboseDefault`: default verbose level for agents. Values: `"off"`, `"on"`, `"full"`. Default: `"off"`.
- `elevatedDefault`: default elevated-output level for agents. Values: `"off"`, `"on"`, `"ask"`, `"full"`. Default: `"on"`.
- `model.primary`: format `provider/model` (e.g. `openai/gpt-5.4` for API-key access or `openai-codex/gpt-5.5` for Codex OAuth). If you omit the provider, OpenClaw tries an alias first, then a unique configured-provider match for that exact model id, and only then falls back to the configured default provider (deprecated compatibility behavior, so prefer explicit `provider/model`). If that provider no longer exposes the configured default model, OpenClaw falls back to the first configured provider/model instead of surfacing a stale removed-provider default.
- `models`: the configured model catalog and allowlist for `/model`. Each entry can include `alias` (shortcut) and `params` (provider-specific, for example `temperature`, `maxTokens`, `cacheRetention`, `context1m`, `responsesServerCompaction`, `responsesCompactThreshold`, `extra_body`/`extraBody`).
- `models`: the configured model catalog and allowlist for `/model`. Each entry can include `alias` (shortcut) and `params` (provider-specific, for example `temperature`, `maxTokens`, `cacheRetention`, `context1m`, `responsesServerCompaction`, `responsesCompactThreshold`).
- Safe edits: use `openclaw config set agents.defaults.models '<json>' --strict-json --merge` to add entries. `config set` refuses replacements that would remove existing allowlist entries unless you pass `--replace`.
- Provider-scoped configure/onboarding flows merge selected provider models into this map and preserve unrelated providers already configured.
- For direct OpenAI Responses models, server-side compaction is enabled automatically. Use `params.responsesServerCompaction: false` to stop injecting `context_management`, or `params.responsesCompactThreshold` to override the threshold. See [OpenAI server-side compaction](/providers/openai#server-side-compaction-responses-api).
- `params`: global default provider parameters applied to all models. Set at `agents.defaults.params` (e.g. `{ cacheRetention: "long" }`).
- `params` merge precedence (config): `agents.defaults.params` (global base) is overridden by `agents.defaults.models["provider/model"].params` (per-model), then `agents.list[].params` (matching agent id) overrides by key. See [Prompt Caching](/reference/prompt-caching) for details.
- `params.extra_body`/`params.extraBody`: advanced pass-through JSON merged into `api: "openai-completions"` request bodies for OpenAI-compatible proxies. If it collides with generated request keys, the extra body wins; non-native completions routes still strip OpenAI-only `store` afterward.
- `embeddedHarness`: default low-level embedded agent runtime policy. Omitted runtime defaults to OpenClaw Pi. Use `runtime: "pi"` to force the built-in PI harness, `runtime: "auto"` to let registered plugin harnesses claim supported models, or a registered harness id such as `runtime: "codex"`. Set `fallback: "none"` to disable automatic PI fallback. Explicit plugin runtimes such as `codex` fail closed by default unless you set `fallback: "pi"` in the same override scope. 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.
@@ -401,7 +399,7 @@ Codex app-server harness. For the mental model, see
- `fallback`: `"pi"` or `"none"`. In `runtime: "auto"`, omitted fallback defaults to `"pi"` so old configs can keep using PI when no plugin harness claims a run. In explicit plugin runtime mode, such as `runtime: "codex"`, omitted fallback defaults to `"none"` so a missing harness fails instead of silently using PI. Runtime overrides do not inherit fallback from a broader scope; set `fallback: "pi"` alongside the explicit runtime when you intentionally want that compatibility fallback. Selected plugin harness failures always surface directly.
- Environment overrides: `OPENCLAW_AGENT_RUNTIME=<id|auto|pi>` overrides `runtime`; `OPENCLAW_AGENT_HARNESS_FALLBACK=pi|none` overrides fallback for that process.
- For Codex-only deployments, set `model: "openai/gpt-5.5"` and `embeddedHarness.runtime: "codex"`. You may also set `embeddedHarness.fallback: "none"` explicitly for readability; it is the default for explicit plugin runtimes.
- 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 sessions with transcript history but no recorded pin are treated as PI-pinned. `/status` reports the effective runtime, for example `Runtime: OpenClaw Pi Default` or `Runtime: OpenAI Codex`.
- 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 sessions with transcript history but no recorded pin are treated as PI-pinned. `/status` shows non-PI harness ids such as `codex` next to `Fast`.
- This only controls the embedded chat harness. 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`):
@@ -543,10 +541,8 @@ Periodic heartbeat runs.
provider: "my-provider", // id of a registered compaction provider plugin (optional)
timeoutSeconds: 900,
reserveTokensFloor: 24000,
keepRecentTokens: 50000,
identifierPolicy: "strict", // strict | off | custom
identifierInstructions: "Preserve deployment IDs, ticket IDs, and host:port pairs exactly.", // used when identifierPolicy=custom
qualityGuard: { enabled: true, maxRetries: 1 },
postCompactionSections: ["Session Startup", "Red Lines"], // [] disables reinjection
model: "openrouter/anthropic/claude-sonnet-4-6", // optional compaction-only model override
notifyUser: true, // send brief notices when compaction starts and completes (default: false)
@@ -565,10 +561,8 @@ Periodic heartbeat runs.
- `mode`: `default` or `safeguard` (chunked summarization for long histories). See [Compaction](/concepts/compaction).
- `provider`: id of a registered compaction provider plugin. When set, the provider's `summarize()` is called instead of built-in LLM summarization. Falls back to built-in on failure. Setting a provider forces `mode: "safeguard"`. See [Compaction](/concepts/compaction).
- `timeoutSeconds`: maximum seconds allowed for a single compaction operation before OpenClaw aborts it. Default: `900`.
- `keepRecentTokens`: Pi cut-point budget for keeping the most recent transcript tail verbatim. Manual `/compact` honors this when explicitly set; otherwise manual compaction is a hard checkpoint.
- `identifierPolicy`: `strict` (default), `off`, or `custom`. `strict` prepends built-in opaque identifier retention guidance during compaction summarization.
- `identifierInstructions`: optional custom identifier-preservation text used when `identifierPolicy=custom`.
- `qualityGuard`: retry-on-malformed-output checks for safeguard summaries. Enabled by default in safeguard mode; set `enabled: false` to skip the audit.
- `postCompactionSections`: optional AGENTS.md H2/H3 section names to re-inject after compaction. Defaults to `["Session Startup", "Red Lines"]`; set `[]` to disable reinjection. When unset or explicitly set to that default pair, older `Every Session`/`Safety` headings are also accepted as a legacy fallback.
- `model`: optional `provider/model-id` override for compaction summarization only. Use this when the main session should keep one model but compaction summaries should run on another; when unset, compaction uses the session's primary model.
- `notifyUser`: when `true`, sends brief notices to the user when compaction starts and when it completes (for example, "Compacting context..." and "Compaction complete"). Disabled by default to keep compaction silent.
@@ -1263,35 +1257,28 @@ Batches rapid text-only messages from the same sender into a single agent turn.
maxTextLength: 4000,
timeoutMs: 30000,
prefsPath: "~/.openclaw/settings/tts.json",
providers: {
elevenlabs: {
apiKey: "elevenlabs_api_key",
baseUrl: "https://api.elevenlabs.io",
voiceId: "voice_id",
modelId: "eleven_multilingual_v2",
seed: 42,
applyTextNormalization: "auto",
languageCode: "en",
voiceSettings: {
stability: 0.5,
similarityBoost: 0.75,
style: 0.0,
useSpeakerBoost: true,
speed: 1.0,
},
},
microsoft: {
voice: "en-US-AvaMultilingualNeural",
lang: "en-US",
outputFormat: "audio-24khz-48kbitrate-mono-mp3",
},
openai: {
apiKey: "openai_api_key",
baseUrl: "https://api.openai.com/v1",
model: "gpt-4o-mini-tts",
voice: "alloy",
elevenlabs: {
apiKey: "elevenlabs_api_key",
baseUrl: "https://api.elevenlabs.io",
voiceId: "voice_id",
modelId: "eleven_multilingual_v2",
seed: 42,
applyTextNormalization: "auto",
languageCode: "en",
voiceSettings: {
stability: 0.5,
similarityBoost: 0.75,
style: 0.0,
useSpeakerBoost: true,
speed: 1.0,
},
},
openai: {
apiKey: "openai_api_key",
baseUrl: "https://api.openai.com/v1",
model: "gpt-4o-mini-tts",
voice: "alloy",
},
},
},
}
@@ -1301,9 +1288,8 @@ Batches rapid text-only messages from the same sender into a single agent turn.
- `summaryModel` overrides `agents.defaults.model.primary` for auto-summary.
- `modelOverrides` is enabled by default; `modelOverrides.allowProvider` defaults to `false` (opt-in).
- API keys fall back to `ELEVENLABS_API_KEY`/`XI_API_KEY` and `OPENAI_API_KEY`.
- Bundled speech providers are plugin-owned. If `plugins.allow` is set, include each TTS provider plugin you want to use, for example `microsoft` for Edge TTS. The legacy `edge` provider id is accepted as an alias for `microsoft`.
- `providers.openai.baseUrl` overrides the OpenAI TTS endpoint. Resolution order is config, then `OPENAI_TTS_BASE_URL`, then `https://api.openai.com/v1`.
- When `providers.openai.baseUrl` points to a non-OpenAI endpoint, OpenClaw treats it as an OpenAI-compatible TTS server and relaxes model/voice validation.
- `openai.baseUrl` overrides the OpenAI TTS endpoint. Resolution order is config, then `OPENAI_TTS_BASE_URL`, then `https://api.openai.com/v1`.
- When `openai.baseUrl` points to a non-OpenAI endpoint, OpenClaw treats it as an OpenAI-compatible TTS server and relaxes model/voice validation.
---

View File

@@ -415,7 +415,7 @@ OpenClaw uses the built-in model catalog. Add custom providers via `models.provi
- `request.allowPrivateNetwork`: when `true`, allow HTTPS to `baseUrl` when DNS resolves to private, CGNAT, or similar ranges, via the provider HTTP fetch guard (operator opt-in for trusted self-hosted OpenAI-compatible endpoints). WebSocket uses the same `request` for headers/TLS but not that fetch SSRF gate. Default `false`.
- `models.providers.*.models`: explicit provider model catalog entries.
- `models.providers.*.models.*.contextWindow`: native model context window metadata.
- `models.providers.*.models.*.contextTokens`: optional runtime context cap. Use this when you want a smaller effective context budget than the model's native `contextWindow`; `openclaw models list` shows both values when they differ.
- `models.providers.*.models.*.contextTokens`: optional runtime context cap. Use this when you want a smaller effective context budget than the model's native `contextWindow`.
- `models.providers.*.models.*.compat.supportsDeveloperRole`: optional compatibility hint. For `api: "openai-completions"` with a non-empty non-native `baseUrl` (host not `api.openai.com`), OpenClaw forces this to `false` at runtime. Empty/omitted `baseUrl` keeps default OpenAI behavior.
- `models.providers.*.models.*.compat.requiresStringContent`: optional compatibility hint for string-only OpenAI-compatible chat endpoints. When `true`, OpenClaw flattens pure text `messages[].content` arrays into plain strings before sending the request.
- `plugins.entries.amazon-bedrock.config.discovery`: Bedrock auto-discovery settings root.

View File

@@ -501,28 +501,6 @@ Save to `~/.openclaw/openclaw.json` and you can DM the bot from that number.
}
```
### Trusted node network auto-approval
Keep device pairing manual unless you control the network path. For a dedicated
lab or tailnet subnet, you can opt in to first-time node device auto-approval
with exact CIDRs or IPs:
```json5
{
gateway: {
nodes: {
pairing: {
autoApproveCidrs: ["192.168.1.0/24", "fd00:1234:5678::/64"],
},
},
},
}
```
This remains off when unset. It only applies to fresh `role: node` pairing with
no requested scopes. Operator/browser clients and role, scope, metadata, or
public-key upgrades still require manual approval.
### Secure DM mode (shared inbox / multi-user DMs)
If more than one person can DM your bot (multiple entries in `allowFrom`, pairing approvals for multiple people, or `dmPolicy: "open"`), enable **secure DM mode** so DMs from different senders dont share one context by default:

View File

@@ -8,7 +8,7 @@ read_when:
Core config reference for `~/.openclaw/openclaw.json`. For a task-oriented overview, see [Configuration](/gateway/configuration).
Covers the main OpenClaw config surfaces and links out when a subsystem has its own deeper reference. Channel- and plugin-owned command catalogs and deep memory/QMD knobs live on their own pages rather than on this one.
This page covers the main OpenClaw config surfaces and links out when a subsystem has its own deeper reference. It does **not** try to inline every channel/plugin-owned command catalog or every deep memory/QMD knob on one page.
Code truth:
@@ -19,7 +19,7 @@ Code truth:
Dedicated deep references:
- [Memory configuration reference](/reference/memory-config) for `agents.defaults.memorySearch.*`, `memory.qmd.*`, `memory.citations`, and dreaming config under `plugins.entries.memory-core.config.dreaming`
- [Slash commands](/tools/slash-commands) for the current built-in + bundled command catalog
- [Slash Commands](/tools/slash-commands) for the current built-in + bundled command catalog
- owning channel/plugin pages for channel-specific command surfaces
Config format is **JSON5** (comments + trailing commas allowed). All fields are optional — OpenClaw uses safe defaults when omitted.
@@ -51,44 +51,6 @@ Tool policy, experimental toggles, provider-backed tool config, and custom
provider / base-URL setup moved to a dedicated page — see
[Configuration — tools and custom providers](/gateway/config-tools).
## MCP
OpenClaw-managed MCP server definitions live under `mcp.servers` and are
consumed by embedded Pi and other runtime adapters. The `openclaw mcp list`,
`show`, `set`, and `unset` commands manage this block without connecting to the
target server during config edits.
```json5
{
mcp: {
// Optional. Default: 600000 ms (10 minutes). Set 0 to disable idle eviction.
sessionIdleTtlMs: 600000,
servers: {
docs: {
command: "npx",
args: ["-y", "@modelcontextprotocol/server-fetch"],
},
remote: {
url: "https://example.com/mcp",
transport: "streamable-http", // streamable-http | sse
headers: {
Authorization: "Bearer ${MCP_REMOTE_TOKEN}",
},
},
},
},
}
```
- `mcp.servers`: named stdio or remote MCP server definitions for runtimes that
expose configured MCP tools.
- `mcp.sessionIdleTtlMs`: idle TTL for session-scoped bundled MCP runtimes.
One-shot embedded runs request run-end cleanup; this TTL is the backstop for
long-lived sessions and future callers.
See [MCP](/cli/mcp#openclaw-as-an-mcp-client-registry) and
[CLI backends](/gateway/cli-backends#bundle-mcp-overlays) for runtime behavior.
## Skills
```json5
@@ -205,19 +167,9 @@ See [Plugins](/tools/plugin).
// hostnameAllowlist: ["*.example.com", "example.com"],
// allowedHostnames: ["localhost"],
},
tabCleanup: {
enabled: true,
idleMinutes: 120,
maxTabsPerSession: 8,
sweepMinutes: 5,
},
profiles: {
openclaw: { cdpPort: 18800, color: "#FF4500" },
work: {
cdpPort: 18801,
color: "#0066CC",
executablePath: "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
},
work: { cdpPort: 18801, color: "#0066CC" },
user: { driver: "existing-session", attachOnly: true, color: "#00AA00" },
brave: {
driver: "existing-session",
@@ -238,9 +190,6 @@ See [Plugins](/tools/plugin).
```
- `evaluateEnabled: false` disables `act:evaluate` and `wait --fn`.
- `tabCleanup` reclaims tracked primary-agent tabs after idle time or when a
session exceeds its cap. Set `idleMinutes: 0` or `maxTabsPerSession: 0` to
disable those individual cleanup modes.
- `ssrfPolicy.dangerouslyAllowPrivateNetwork` is disabled when unset, so browser navigation stays strict by default.
- Set `ssrfPolicy.dangerouslyAllowPrivateNetwork: true` only when you intentionally trust private-network browser navigation.
- In strict mode, remote CDP profile endpoints (`profiles.*.cdpUrl`) are subject to the same private-network blocking during reachability/discovery checks.
@@ -260,11 +209,7 @@ See [Plugins](/tools/plugin).
`responsebody`, PDF export, download interception, or batch actions.
- Local managed `openclaw` profiles auto-assign `cdpPort` and `cdpUrl`; only
set `cdpUrl` explicitly for remote CDP.
- Local managed profiles can set `executablePath` to override the global
`browser.executablePath` for that profile. Use this to run one profile in
Chrome and another in Brave.
- Auto-detect order: default browser if Chromium-based → Chrome → Brave → Edge → Chromium → Chrome Canary.
- `browser.executablePath` accepts `~` for your OS home directory.
- Control service: loopback only (port derived from `gateway.port`, default `18791`).
- `extraArgs` appends extra launch flags to local Chromium startup (for example
`--disable-gpu`, window sizing, or debug flags).
@@ -335,14 +280,6 @@ See [Plugins](/tools/plugin).
trustedProxies: ["10.0.0.1"],
// Optional. Default false.
allowRealIpFallback: false,
nodes: {
pairing: {
// Optional. Default unset/disabled.
autoApproveCidrs: ["192.168.1.0/24", "fd00:1234:5678::/64"],
},
allowCommands: ["canvas.navigate"],
denyCommands: ["system.run"],
},
tools: {
// Additional /tools/invoke HTTP denies
deny: ["browser"],
@@ -405,8 +342,6 @@ See [Plugins](/tools/plugin).
- If `gateway.auth.token` / `gateway.auth.password` is explicitly configured via SecretRef and unresolved, resolution fails closed (no remote fallback masking).
- `trustedProxies`: reverse proxy IPs that terminate TLS or inject forwarded-client headers. Only list proxies you control. Loopback entries are still valid for same-host proxy/local-detection setups (for example Tailscale Serve or a local reverse proxy), but they do **not** make loopback requests eligible for `gateway.auth.mode: "trusted-proxy"`.
- `allowRealIpFallback`: when `true`, the gateway accepts `X-Real-IP` if `X-Forwarded-For` is missing. Default `false` for fail-closed behavior.
- `gateway.nodes.pairing.autoApproveCidrs`: optional CIDR/IP allowlist for auto-approving first-time node device pairing with no requested scopes. It is disabled when unset. This does not auto-approve operator/browser/Control UI/WebChat pairing, and it does not auto-approve role, scope, metadata, or public-key upgrades.
- `gateway.nodes.allowCommands` / `gateway.nodes.denyCommands`: global allow/deny shaping for declared node commands after pairing and allowlist evaluation.
- `gateway.tools.deny`: extra tool names blocked for HTTP `POST /tools/invoke` (extends default deny list).
- `gateway.tools.allow`: remove tool names from the default HTTP deny list.
@@ -896,7 +831,6 @@ Notes:
- `otel.sampleRate`: trace sampling rate `0``1`.
- `otel.flushIntervalMs`: periodic telemetry flush interval in ms.
- `otel.captureContent`: opt-in raw content capture for OTEL span attributes. Defaults to off. Boolean `true` captures non-system message/tool content; the object form lets you enable `inputMessages`, `outputMessages`, `toolInputs`, `toolOutputs`, and `systemPrompt` explicitly.
- `OPENCLAW_OTEL_PRELOADED=1`: environment toggle for hosts that already registered a global OpenTelemetry SDK. OpenClaw then skips plugin-owned SDK startup/shutdown while keeping diagnostic listeners active.
- `cacheTrace.enabled`: log cache trace snapshots for embedded runs (default: `false`).
- `cacheTrace.filePath`: output path for cache trace JSONL (default: `$OPENCLAW_STATE_DIR/logs/cache-trace.jsonl`).
- `cacheTrace.includeMessages` / `includePrompt` / `includeSystem`: control what is included in cache trace output (all default: `true`).

View File

@@ -91,10 +91,6 @@ as `.clobbered.*`, restores the last-known-good copy, and logs the recovery
reason. The next agent turn also receives a system-event warning so the main
agent does not blindly rewrite the restored config. Promotion to last-known-good
is skipped when a candidate contains redacted secret placeholders such as `***`.
When every validation issue is scoped to `plugins.entries.<id>...`, OpenClaw
does not perform whole-file recovery. It keeps the current config active and
surfaces the plugin-local failure so a plugin schema or host-version mismatch
cannot roll back unrelated user settings.
## Common tasks
@@ -508,10 +504,6 @@ config writes use the same schema gate before writing; destructive clobbers such
as dropping `gateway.mode` or shrinking the file by more than half are rejected
and saved as `.rejected.*` for inspection.
Plugin-local validation failures are the exception: if all issues are under
`plugins.entries.<id>...`, reload keeps the current config and reports the plugin
issue instead of restoring `.last-good`.
If you see `Config auto-restored from last-known-good` or
`config reload restored last-known-good config` in logs, inspect the matching
`.clobbered.*` file next to `openclaw.json`, fix the rejected payload, then run

View File

@@ -172,11 +172,9 @@ Current migrations:
- `routing.agentToAgent``tools.agentToAgent`
- `routing.transcribeAudio``tools.media.audio.models`
- `messages.tts.<provider>` (`openai`/`elevenlabs`/`microsoft`/`edge`) → `messages.tts.providers.<provider>`
- `messages.tts.provider: "edge"` and `messages.tts.providers.edge``messages.tts.provider: "microsoft"` and `messages.tts.providers.microsoft`
- `channels.discord.voice.tts.<provider>` (`openai`/`elevenlabs`/`microsoft`/`edge`) → `channels.discord.voice.tts.providers.<provider>`
- `channels.discord.accounts.<id>.voice.tts.<provider>` (`openai`/`elevenlabs`/`microsoft`/`edge`) → `channels.discord.accounts.<id>.voice.tts.providers.<provider>`
- `plugins.entries.voice-call.config.tts.<provider>` (`openai`/`elevenlabs`/`microsoft`/`edge`) → `plugins.entries.voice-call.config.tts.providers.<provider>`
- `plugins.entries.voice-call.config.tts.provider: "edge"` and `plugins.entries.voice-call.config.tts.providers.edge``provider: "microsoft"` and `providers.microsoft`
- `plugins.entries.voice-call.config.provider: "log"``"mock"`
- `plugins.entries.voice-call.config.twilio.from``plugins.entries.voice-call.config.fromNumber`
- `plugins.entries.voice-call.config.streaming.sttProvider``plugins.entries.voice-call.config.streaming.provider`

View File

@@ -265,9 +265,6 @@ Use `accountId` to target a specific account on multi-account channels like Tele
send chat output to, and it is disabled by `typingMode: "never"`.
- Heartbeat-only replies do **not** keep the session alive; the last `updatedAt`
is restored so idle expiry behaves normally.
- Control UI and WebChat history hide heartbeat prompts and OK-only
acknowledgments. The underlying session transcript can still contain those
turns for audit/replay.
- Detached [background tasks](/automation/tasks) can enqueue a system event and wake heartbeat when the main session should notice something quickly. That wake does not make the heartbeat run a background task.
## Visibility controls

View File

@@ -115,34 +115,6 @@ The macOS app can optionally attempt a **silent approval** when:
If silent approval fails, it falls back to the normal “Approve/Reject” prompt.
## Trusted-CIDR device auto-approval
WS device pairing for `role: node` remains manual by default. For private
node networks where the Gateway already trusts the network path, operators can
opt in with explicit CIDRs or exact IPs:
```json5
{
gateway: {
nodes: {
pairing: {
autoApproveCidrs: ["192.168.1.0/24"],
},
},
},
}
```
Security boundary:
- Disabled when `gateway.nodes.pairing.autoApproveCidrs` is unset.
- No blanket LAN or private-network auto-approve mode exists.
- Only fresh `role: node` device pairing with no requested scopes is eligible.
- Operator, browser, Control UI, and WebChat clients stay manual.
- Role, scope, metadata, and public-key upgrades stay manual.
- Same-host loopback trusted-proxy header paths are not eligible because that
path can be spoofed by local callers.
## Metadata-upgrade auto-approval
When an already paired device reconnects with only non-sensitive metadata

View File

@@ -111,7 +111,6 @@ Use this as the quick model when triaging risk:
| `canvas.eval` / browser evaluate | Intentional operator capability when enabled | "Any JS eval primitive is automatically a vuln in this trust model" |
| Local TUI `!` shell | Explicit operator-triggered local execution | "Local shell convenience command is remote injection" |
| Node pairing and node commands | Operator-level remote execution on paired devices | "Remote device control should be treated as untrusted user access by default" |
| `gateway.nodes.pairing.autoApproveCidrs` | Opt-in trusted-network node enrollment policy | "A disabled-by-default allowlist is an automatic pairing vulnerability" |
## Not vulnerabilities by design
@@ -134,12 +133,6 @@ a real boundary bypass is demonstrated:
approval layer for `system.run`, when the real execution boundary is still
the gateway's global node command policy plus the node's own exec
approvals.
- Reports that treat configured `gateway.nodes.pairing.autoApproveCidrs` as a
vulnerability by itself. This setting is disabled by default, requires
explicit CIDR/IP entries, only applies to first-time `role: node` pairing with
no requested scopes, and does not auto-approve operator/browser/Control UI,
WebChat, role upgrades, scope upgrades, metadata changes, public-key changes,
or same-host loopback trusted-proxy header paths.
- "Missing per-user authorization" findings that treat `sessionKey` as an
auth token.
@@ -360,12 +353,6 @@ gateway:
When `trustedProxies` is configured, the Gateway uses `X-Forwarded-For` to determine the client IP. `X-Real-IP` is ignored by default unless `gateway.allowRealIpFallback: true` is explicitly set.
Trusted proxy headers do not make node device pairing automatically trusted.
`gateway.nodes.pairing.autoApproveCidrs` is a separate, disabled-by-default
operator policy. Even when enabled, loopback-source trusted-proxy header paths
are excluded from node auto-approval because local callers can forge those
headers.
Good reverse proxy behavior (overwrite incoming forwarding headers):
```nginx

View File

@@ -55,9 +55,9 @@ Fix options:
Related:
- [Anthropic](/providers/anthropic)
- [Token use and costs](/reference/token-use)
- [Why am I seeing HTTP 429 from Anthropic?](/help/faq-first-run#why-am-i-seeing-http-429-ratelimiterror-from-anthropic)
- [/providers/anthropic](/providers/anthropic)
- [/reference/token-use](/reference/token-use)
- [/help/faq-first-run#why-am-i-seeing-http-429-ratelimiterror-from-anthropic](/help/faq-first-run#why-am-i-seeing-http-429-ratelimiterror-from-anthropic)
## Local OpenAI-compatible backend passes direct probes but agent runs fail
@@ -110,9 +110,9 @@ Fix options:
Related:
- [Local models](/gateway/local-models)
- [Configuration](/gateway/configuration)
- [OpenAI-compatible endpoints](/gateway/configuration-reference#openai-compatible-endpoints)
- [/gateway/local-models](/gateway/local-models)
- [/gateway/configuration](/gateway/configuration)
- [/gateway/configuration-reference#openai-compatible-endpoints](/gateway/configuration-reference#openai-compatible-endpoints)
## No replies
@@ -140,9 +140,9 @@ Common signatures:
Related:
- [Channel troubleshooting](/channels/troubleshooting)
- [Pairing](/channels/pairing)
- [Groups](/channels/groups)
- [/channels/troubleshooting](/channels/troubleshooting)
- [/channels/pairing](/channels/pairing)
- [/channels/groups](/channels/groups)
## Dashboard control ui connectivity
@@ -223,11 +223,11 @@ If `openclaw devices rotate` / `revoke` / `remove` is denied unexpectedly:
Related:
- [Control UI](/web/control-ui)
- [Configuration](/gateway/configuration) (gateway auth modes)
- [Trusted proxy auth](/gateway/trusted-proxy-auth)
- [Remote access](/gateway/remote)
- [Devices](/cli/devices)
- [/web/control-ui](/web/control-ui)
- [/gateway/configuration](/gateway/configuration) (gateway auth modes)
- [/gateway/trusted-proxy-auth](/gateway/trusted-proxy-auth)
- [/gateway/remote](/gateway/remote)
- [/cli/devices](/cli/devices)
## Gateway service not running
@@ -258,9 +258,9 @@ Common signatures:
Related:
- [Background exec and process tool](/gateway/background-process)
- [Configuration](/gateway/configuration)
- [Doctor](/gateway/doctor)
- [/gateway/background-process](/gateway/background-process)
- [/gateway/configuration](/gateway/configuration)
- [/gateway/doctor](/gateway/doctor)
## Gateway restored last-known-good config
@@ -287,9 +287,6 @@ What happened:
- OpenClaw preserved the rejected payload as `.clobbered.*`.
- The active config was restored from the last validated last-known-good copy.
- The next main-agent turn is warned not to blindly rewrite the rejected config.
- If all validation issues were under `plugins.entries.<id>...`, OpenClaw would
not restore the whole file. Plugin-local failures stay loud while unrelated
user settings remain in the active config.
Inspect and repair:
@@ -318,10 +315,10 @@ Fix options:
Related:
- [Configuration: strict validation](/gateway/configuration#strict-validation)
- [Configuration: hot reload](/gateway/configuration#config-hot-reload)
- [Config](/cli/config)
- [Doctor](/gateway/doctor)
- [/gateway/configuration#strict-validation](/gateway/configuration#strict-validation)
- [/gateway/configuration#config-hot-reload](/gateway/configuration#config-hot-reload)
- [/cli/config](/cli/config)
- [/gateway/doctor](/gateway/doctor)
## Gateway probe warnings
@@ -348,9 +345,9 @@ Common signatures:
Related:
- [Gateway](/cli/gateway)
- [Multiple gateways on the same host](/gateway#multiple-gateways-same-host)
- [Remote access](/gateway/remote)
- [/cli/gateway](/cli/gateway)
- [/gateway#multiple-gateways-same-host](/gateway#multiple-gateways-same-host)
- [/gateway/remote](/gateway/remote)
## Channel connected messages not flowing
@@ -378,10 +375,10 @@ Common signatures:
Related:
- [Channel troubleshooting](/channels/troubleshooting)
- [WhatsApp](/channels/whatsapp)
- [Telegram](/channels/telegram)
- [Discord](/channels/discord)
- [/channels/troubleshooting](/channels/troubleshooting)
- [/channels/whatsapp](/channels/whatsapp)
- [/channels/telegram](/channels/telegram)
- [/channels/discord](/channels/discord)
## Cron and heartbeat delivery
@@ -413,9 +410,9 @@ Common signatures:
Related:
- [Scheduled tasks: troubleshooting](/automation/cron-jobs#troubleshooting)
- [Scheduled tasks](/automation/cron-jobs)
- [Heartbeat](/gateway/heartbeat)
- [/automation/cron-jobs#troubleshooting](/automation/cron-jobs#troubleshooting)
- [/automation/cron-jobs](/automation/cron-jobs)
- [/gateway/heartbeat](/gateway/heartbeat)
## Node paired tool fails
@@ -444,9 +441,9 @@ Common signatures:
Related:
- [Node troubleshooting](/nodes/troubleshooting)
- [Nodes](/nodes/index)
- [Exec approvals](/tools/exec-approvals)
- [/nodes/troubleshooting](/nodes/troubleshooting)
- [/nodes/index](/nodes/index)
- [/tools/exec-approvals](/tools/exec-approvals)
## Browser tool fails
@@ -492,8 +489,8 @@ Common signatures:
Related:
- [Browser troubleshooting](/tools/browser-linux-troubleshooting)
- [Browser (OpenClaw-managed)](/tools/browser)
- [/tools/browser-linux-troubleshooting](/tools/browser-linux-troubleshooting)
- [/tools/browser](/tools/browser)
## If you upgraded and something suddenly broke
@@ -566,9 +563,9 @@ openclaw gateway restart
Related:
- [Gateway-owned pairing](/gateway/pairing)
- [Authentication](/gateway/authentication)
- [Background exec and process tool](/gateway/background-process)
- [/gateway/pairing](/gateway/pairing)
- [/gateway/authentication](/gateway/authentication)
- [/gateway/background-process](/gateway/background-process)
## Related

View File

@@ -13,35 +13,6 @@ For quick start, QA runners, unit/integration suites, and Docker flows, see
suites: model matrix, CLI backends, ACP, and media-provider live tests, plus
credential handling.
## Live: local profile smoke commands
Source `~/.profile` before ad hoc live checks so provider keys and local tool
paths match your shell:
```bash
source ~/.profile
```
Safe media smoke:
```bash
pnpm openclaw infer tts convert --local --json \
--text "OpenClaw live smoke." \
--output /tmp/openclaw-live-smoke.mp3
```
Safe voice-call readiness smoke:
```bash
pnpm openclaw voicecall setup --json
pnpm openclaw voicecall smoke --to "+15555550123"
```
`voicecall smoke` is a dry run unless `--yes` is also present. Use `--yes` only
when you intentionally want to place a real notify call. For Twilio, Telnyx, and
Plivo, a successful readiness check requires a public webhook URL; local-only
loopback/private fallbacks are rejected by design.
## Live: Android node capability sweep
- Test: `src/gateway/android-node.capabilities.live.test.ts`
@@ -149,7 +120,7 @@ openclaw models list --json
- `OPENCLAW_LIVE_CLI_BACKEND_MODEL="codex-cli/gpt-5.2"`
- `OPENCLAW_LIVE_CLI_BACKEND_COMMAND="/full/path/to/codex"`
- `OPENCLAW_LIVE_CLI_BACKEND_ARGS='["exec","--json","--color","never","--sandbox","read-only","--skip-git-repo-check"]'`
- `OPENCLAW_LIVE_CLI_BACKEND_IMAGE_PROBE=1` to send a real image attachment (paths are injected into the prompt). Docker recipes default this off unless explicitly requested.
- `OPENCLAW_LIVE_CLI_BACKEND_IMAGE_PROBE=1` to send a real image attachment (paths are injected into the prompt).
- `OPENCLAW_LIVE_CLI_BACKEND_IMAGE_ARG="--image"` to pass image file paths as CLI args instead of prompt injection.
- `OPENCLAW_LIVE_CLI_BACKEND_IMAGE_MODE="repeat"` (or `"list"`) to control how image args are passed when `IMAGE_ARG` is set.
- `OPENCLAW_LIVE_CLI_BACKEND_RESUME_PROBE=1` to send a second turn and validate resume flow.

View File

@@ -7,20 +7,22 @@ read_when:
title: "Nix"
---
Install OpenClaw declaratively with **[nix-openclaw](https://github.com/openclaw/nix-openclaw)** — a batteries-included Home Manager module.
# Nix Installation
Install OpenClaw declaratively with **[nix-openclaw](https://github.com/openclaw/nix-openclaw)** -- a batteries-included Home Manager module.
<Info>
The [nix-openclaw](https://github.com/openclaw/nix-openclaw) repo is the source of truth for Nix installation. This page is a quick overview.
</Info>
## What you get
## What You Get
- Gateway + macOS app + tools (whisper, spotify, cameras) -- all pinned
- Launchd service that survives reboots
- Plugin system with declarative config
- Instant rollback: `home-manager switch --rollback`
## Quick start
## Quick Start
<Steps>
<Step title="Install Determinate Nix">
@@ -48,7 +50,7 @@ The [nix-openclaw](https://github.com/openclaw/nix-openclaw) repo is the source
See the [nix-openclaw README](https://github.com/openclaw/nix-openclaw) for full module options and examples.
## Nix-mode runtime behavior
## Nix Mode Runtime Behavior
When `OPENCLAW_NIX_MODE=1` is set (automatic with nix-openclaw), OpenClaw enters a deterministic mode that disables auto-install flows.
@@ -80,18 +82,6 @@ OpenClaw reads JSON5 config from `OPENCLAW_CONFIG_PATH` and stores mutable data
| `OPENCLAW_STATE_DIR` | `~/.openclaw` |
| `OPENCLAW_CONFIG_PATH` | `$OPENCLAW_STATE_DIR/openclaw.json` |
### Service PATH discovery
The launchd/systemd gateway service auto-discovers Nix-profile binaries so
plugins and tools that shell out to `nix`-installed executables work without
manual PATH setup:
- When `NIX_PROFILES` is set, every entry is added to the service PATH in
right-to-left precedence (matches Nix shell precedence — rightmost wins).
- When `NIX_PROFILES` is unset, `~/.nix-profile/bin` is added as a fallback.
This applies to both macOS launchd and Linux systemd service environments.
## Related
- [nix-openclaw](https://github.com/openclaw/nix-openclaw) -- full setup guide

View File

@@ -307,10 +307,6 @@ Notes:
- Set `headers` when your collector requires auth.
- Environment variables supported: `OTEL_EXPORTER_OTLP_ENDPOINT`,
`OTEL_SERVICE_NAME`, `OTEL_EXPORTER_OTLP_PROTOCOL`.
- Set `OPENCLAW_OTEL_PRELOADED=1` when another preload or host process already
registered the global OpenTelemetry SDK. In that mode the plugin does not start
or shut down its own SDK, but it still wires OpenClaw diagnostic listeners and
honors `diagnostics.otel.traces`, `metrics`, and `logs`.
### Exported metrics (names + types)
@@ -393,8 +389,6 @@ classes you opted into.
`OTEL_EXPORTER_OTLP_ENDPOINT`.
- If the endpoint already contains `/v1/traces` or `/v1/metrics`, it is used as-is.
- If the endpoint already contains `/v1/logs`, it is used as-is for logs.
- `OPENCLAW_OTEL_PRELOADED=1` reuses an externally registered OpenTelemetry SDK
for traces/metrics instead of starting a plugin-owned NodeSDK.
- `diagnostics.otel.logs` enables OTLP log export for the main logger output.
### Log export behavior

View File

@@ -117,25 +117,6 @@ openclaw devices reject <requestId>
Pairing details: [Pairing](/channels/pairing).
Optional: if the Android node always connects from a tightly controlled subnet,
you can opt in to first-time node auto-approval with explicit CIDRs or exact IPs:
```json5
{
gateway: {
nodes: {
pairing: {
autoApproveCidrs: ["192.168.1.0/24"],
},
},
},
}
```
This is disabled by default. It applies only to fresh `role: node` pairing with
no requested scopes. Operator/browser pairing and any role, scope, metadata, or
public-key change still require manual approval.
### 5) Verify the node is connected
- Via nodes status:

View File

@@ -44,25 +44,6 @@ If the app retries pairing with changed auth details (role/scopes/public key),
the previous pending request is superseded and a new `requestId` is created.
Run `openclaw devices list` again before approval.
Optional: if the iOS node always connects from a tightly controlled subnet, you
can opt in to first-time node auto-approval with explicit CIDRs or exact IPs:
```json5
{
gateway: {
nodes: {
pairing: {
autoApproveCidrs: ["192.168.1.0/24"],
},
},
},
}
```
This is disabled by default. It applies only to fresh `role: node` pairing with
no requested scopes. Operator/browser pairing and any role, scope, metadata, or
public-key change still require manual approval.
4. Verify connection:
```bash

View File

@@ -102,7 +102,7 @@ Example:
Notes:
- `allowlist` entries are glob patterns for resolved binary paths, or bare command names for PATH-invoked commands.
- `allowlist` entries are glob patterns for resolved binary paths.
- Raw shell command text that contains shell control or expansion syntax (`&&`, `||`, `;`, `|`, `` ` ``, `$`, `<`, `>`, `(`, `)`) is treated as an allowlist miss and requires explicit approval (or allowlisting the shell binary).
- Choosing “Always Allow” in the prompt adds that command to the allowlist.
- `system.run` environment overrides are filtered (drops `PATH`, `DYLD_*`, `LD_*`, `NODE_OPTIONS`, `PYTHON*`, `PERL*`, `RUBYOPT`, `SHELLOPTS`, `PS4`) and then merged with the apps environment.

View File

@@ -148,12 +148,9 @@ reserve root command names before parsing.
The important design boundary:
- manifest/config validation should work from **manifest/schema metadata**
- discovery + config validation should work from **manifest/schema metadata**
without executing plugin code
- native capability discovery may load trusted plugin entry code to build a
non-activating registry snapshot
- native runtime behavior comes from the plugin module's `register(api)` path
with `api.registrationMode === "full"`
That split lets OpenClaw validate config, explain missing/disabled plugins, and
build UI/schema hints before the full runtime is active.

View File

@@ -26,7 +26,7 @@ These are in-process OpenClaw hooks, not Codex `hooks.json` command hooks:
- `before_prompt_build`
- `before_compaction`, `after_compaction`
- `llm_input`, `llm_output`
- `before_tool_call`, `after_tool_call`
- `after_tool_call`
- `before_message_write` for mirrored transcript records
- `agent_end`
@@ -36,9 +36,6 @@ result is returned to Codex. This is separate from the public
`tool_result_persist` plugin hook, which transforms OpenClaw-owned transcript
tool-result writes.
For the plugin hook semantics themselves, see [Plugin hooks](/plugins/hooks)
and [Plugin guard behavior](/tools/plugin).
The harness is off by default. New configs should keep OpenAI model refs
canonical as `openai/gpt-*` and explicitly force
`embeddedHarness.runtime: "codex"` or `OPENCLAW_AGENT_RUNTIME=codex` when they
@@ -372,19 +369,9 @@ To opt in to Codex guardian-reviewed approvals, set `appServer.mode:
}
```
Guardian mode uses Codex's native auto-review approval path. When Codex asks to
leave the sandbox, write outside the workspace, or add permissions like network
access, Codex routes that approval request to the native reviewer instead of a
human prompt. The reviewer applies Codex's risk framework and approves or denies
the specific request. Use Guardian when you want more guardrails than YOLO mode
but still need unattended agents to make progress.
Guardian is a native Codex approval reviewer. When Codex asks to leave the sandbox, write outside the workspace, or add permissions like network access, Codex routes that approval request to a reviewer subagent instead of a human prompt. The reviewer applies Codex's risk framework and approves or denies the specific request. Use Guardian when you want more guardrails than YOLO mode but still need unattended agents to make progress.
The `guardian` preset expands to `approvalPolicy: "on-request"`,
`approvalsReviewer: "auto_review"`, and `sandbox: "workspace-write"`.
Individual policy fields still override `mode`, so advanced deployments can mix
the preset with explicit choices. The older `guardian_subagent` reviewer value is
still accepted as a compatibility alias, but new configs should use
`auto_review`.
The `guardian` preset expands to `approvalPolicy: "on-request"`, `approvalsReviewer: "guardian_subagent"`, and `sandbox: "workspace-write"`. Individual policy fields still override `mode`, so advanced deployments can mix the preset with explicit choices.
For an already-running app-server, use WebSocket transport:
@@ -410,20 +397,20 @@ For an already-running app-server, use WebSocket transport:
Supported `appServer` fields:
| Field | Default | Meaning |
| ------------------- | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| `transport` | `"stdio"` | `"stdio"` spawns Codex; `"websocket"` connects to `url`. |
| `command` | `"codex"` | Executable for stdio transport. |
| `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. |
| `requestTimeoutMs` | `60000` | Timeout for app-server control-plane calls. |
| `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. |
| Field | Default | Meaning |
| ------------------- | ---------------------------------------- | --------------------------------------------------------------------------------------------------------- |
| `transport` | `"stdio"` | `"stdio"` spawns Codex; `"websocket"` connects to `url`. |
| `command` | `"codex"` | Executable for stdio transport. |
| `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. |
| `requestTimeoutMs` | `60000` | Timeout for app-server control-plane calls. |
| `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 `"guardian_subagent"` to let Codex Guardian review prompts. |
| `serviceTier` | unset | Optional Codex app-server service tier: `"fast"`, `"flex"`, or `null`. Invalid legacy values are ignored. |
The older environment variables still work as fallbacks for local testing when
the matching config field is unset:
@@ -490,7 +477,7 @@ Guardian-reviewed Codex approvals:
appServer: {
mode: "guardian",
approvalPolicy: "on-request",
approvalsReviewer: "auto_review",
approvalsReviewer: "guardian_subagent",
sandbox: "workspace-write",
},
},
@@ -566,11 +553,9 @@ The Codex harness has three hook layers:
| Codex native hooks | Codex | Low-level Codex lifecycle and native tool policy from Codex config. |
OpenClaw does not use project or global Codex `hooks.json` files to route
OpenClaw plugin behavior. For the supported native tool and permission bridge,
OpenClaw injects per-thread Codex config for `PreToolUse`, `PostToolUse`, and
`PermissionRequest`. Other Codex hooks such as `SessionStart`,
`UserPromptSubmit`, and `Stop` remain Codex-level controls; they are not exposed
as OpenClaw plugin hooks in the v1 contract.
OpenClaw plugin behavior. Codex native hooks are useful for Codex-owned
operations such as shell policy, native tool result review, stop handling, and
native compaction/model lifecycle, but they are not the OpenClaw plugin API.
For OpenClaw dynamic tools, OpenClaw executes the tool after Codex asks for the
call, so OpenClaw fires the plugin and middleware behavior it owns in the
@@ -579,9 +564,10 @@ OpenClaw can mirror selected events, but it cannot rewrite the native Codex
thread unless Codex exposes that operation through app-server or native hook
callbacks.
Compaction and LLM lifecycle projections come from Codex app-server
notifications and OpenClaw adapter state, not native Codex hook commands.
OpenClaw's `before_compaction`, `after_compaction`, `llm_input`, and
When newer Codex app-server builds expose native compaction and model lifecycle
hook events, OpenClaw should version-gate that protocol support and map the
events into the existing OpenClaw hook contract where the semantics are honest.
Until then, OpenClaw's `before_compaction`, `after_compaction`, `llm_input`, and
`llm_output` events are adapter-level observations, not byte-for-byte captures
of Codex's internal request or compaction payloads.
@@ -597,31 +583,31 @@ around that boundary.
Supported in Codex runtime v1:
| Surface | Support | Why |
| --------------------------------------- | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| OpenAI model loop through Codex | Supported | Codex app-server owns the OpenAI turn, native thread resume, and native tool continuation. |
| OpenClaw channel routing and delivery | Supported | Telegram, Discord, Slack, WhatsApp, iMessage, and other channels stay outside the model runtime. |
| OpenClaw dynamic tools | Supported | Codex asks OpenClaw to execute these tools, so OpenClaw stays in the execution path. |
| Prompt and context plugins | Supported | OpenClaw builds prompt overlays and projects context into the Codex turn before starting or resuming the thread. |
| Context engine lifecycle | Supported | Assemble, ingest or after-turn maintenance, and context-engine compaction coordination run for Codex turns. |
| Dynamic tool hooks | Supported | `before_tool_call`, `after_tool_call`, and tool-result middleware run around OpenClaw-owned dynamic tools. |
| Lifecycle hooks | Supported as adapter observations | `llm_input`, `llm_output`, `agent_end`, `before_compaction`, and `after_compaction` fire with honest Codex-mode payloads. |
| Native shell and patch block or observe | Supported through the native hook relay | Codex `PreToolUse` and `PostToolUse` are relayed for the committed native tool surfaces. Blocking is supported; argument rewriting is not. |
| Native permission policy | Supported through the native hook relay | Codex `PermissionRequest` can be routed through OpenClaw policy where the runtime exposes it. |
| App-server trajectory capture | Supported | OpenClaw records the request it sent to app-server and the app-server notifications it receives. |
| Surface | Support | Why |
| --------------------------------------- | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| OpenAI model loop through Codex | Supported | Codex app-server owns the OpenAI turn, native thread resume, and native tool continuation. |
| OpenClaw channel routing and delivery | Supported | Telegram, Discord, Slack, WhatsApp, iMessage, and other channels stay outside the model runtime. |
| OpenClaw dynamic tools | Supported | Codex asks OpenClaw to execute these tools, so OpenClaw stays in the execution path. |
| Prompt and context plugins | Supported | OpenClaw builds prompt overlays and projects context into the Codex turn before starting or resuming the thread. |
| Context engine lifecycle | Supported | Assemble, ingest or after-turn maintenance, and context-engine compaction coordination run for Codex turns. |
| Dynamic tool hooks | Supported | `before_tool_call`, `after_tool_call`, and tool-result middleware run around OpenClaw-owned dynamic tools. |
| Lifecycle hooks | Supported as adapter observations | `llm_input`, `llm_output`, `agent_end`, `before_compaction`, and `after_compaction` fire with honest Codex-mode payloads. |
| Native shell and patch block or observe | Supported through the native hook relay | Codex `PreToolUse` and `PostToolUse` are relayed for supported Codex-native tools. Blocking is supported; argument rewriting is not. |
| Native permission policy | Supported through the native hook relay | Codex `PermissionRequest` can be routed through OpenClaw policy where the runtime exposes it. |
| App-server trajectory capture | Supported | OpenClaw records the request it sent to app-server and the app-server notifications it receives. |
Not supported in Codex runtime v1:
| Surface | V1 boundary | Future path |
| --------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
| Native tool argument mutation | Codex native pre-tool hooks can block, but OpenClaw does not rewrite Codex-native tool arguments. | Requires Codex hook/schema support for replacement tool input. |
| Editable Codex-native transcript history | Codex owns canonical native thread history. OpenClaw owns a mirror and can project future context, but should not mutate unsupported internals. | Add explicit Codex app-server APIs if native thread surgery is needed. |
| `tool_result_persist` for Codex-native tool records | That hook transforms OpenClaw-owned transcript writes, not Codex-native tool records. | Could mirror transformed records, but canonical rewrite needs Codex support. |
| Rich native compaction metadata | OpenClaw observes compaction start and completion, but does not receive a stable kept/dropped list, token delta, or summary payload. | Needs richer Codex compaction events. |
| Compaction intervention | Current OpenClaw compaction hooks are notification-level in Codex mode. | Add Codex pre/post compaction hooks if plugins need to veto or rewrite native compaction. |
| Stop or final-answer gating | Codex has native stop hooks, but OpenClaw does not expose final-answer gating as a v1 plugin contract. | Future opt-in primitive with loop and timeout safeguards. |
| Native MCP hook parity as a committed v1 surface | The relay is generic, but OpenClaw has not version-gated and tested native MCP pre/post hook behavior end to end. | Add OpenClaw MCP relay tests and docs once the supported app-server protocol floor covers those payloads. |
| Byte-for-byte model API request capture | OpenClaw can capture app-server requests and notifications, but Codex core builds the final OpenAI API request internally. | Needs a Codex model-request tracing event or debug API. |
| Surface | V1 Boundary | Future Path |
| --------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
| Native tool argument mutation | Codex native pre-tool hooks can block, but OpenClaw does not rewrite Codex-native tool arguments. | Requires Codex hook/schema support for replacement tool input. |
| Editable Codex-native transcript history | Codex owns canonical native thread history. OpenClaw owns a mirror and can project future context, but should not mutate unsupported internals. | Add explicit Codex app-server APIs if native thread surgery is needed. |
| `tool_result_persist` for Codex-native tool records | That hook transforms OpenClaw-owned transcript writes, not Codex-native tool records. | Could mirror transformed records, but canonical rewrite needs Codex support. |
| Rich native compaction metadata | OpenClaw observes compaction start and completion, but does not receive a stable kept/dropped list, token delta, or summary payload. | Needs richer Codex compaction events. |
| Compaction intervention | Current OpenClaw compaction hooks are notification-level in Codex mode. | Add Codex pre/post compaction hooks if plugins need to veto or rewrite native compaction. |
| Stop or final-answer gating | Codex has native stop hooks, but OpenClaw does not expose final-answer gating as a v1 plugin contract. | Future opt-in primitive with loop and timeout safeguards. |
| Native MCP hook parity | Codex owns MCP execution, and full pre/post hook payload parity depends on Codex MCP handler support. | Add Codex MCP hook payloads, then relay them through the same native hook path. |
| Byte-for-byte model API request capture | OpenClaw can capture app-server requests and notifications, but Codex core builds the final OpenAI API request internally. | Needs a Codex model-request tracing event or debug API. |
## Tools, media, and compaction
@@ -631,11 +617,6 @@ OpenClaw still builds the tool list and receives dynamic tool results from the
harness. Text, images, video, music, TTS, approvals, and messaging-tool output
continue through the normal OpenClaw delivery path.
The native hook relay is intentionally generic, but the v1 support contract is
limited to the Codex-native tool and permission paths that OpenClaw tests. Do not
assume every future Codex hook event is an OpenClaw plugin surface until the
runtime contract names it.
Codex MCP tool approval elicitations are routed through OpenClaw's plugin
approval flow when Codex marks `_meta.codex_approval_kind` as
`"mcp_tool_call"`. Codex `request_user_input` prompts are sent back to the
@@ -663,11 +644,9 @@ 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 with
`embeddedHarness.runtime: "codex"` (or a legacy `codex/*` ref), enable
`plugins.entries.codex.enabled`, and check whether `plugins.allow` excludes
`codex`.
**Codex does not appear in `/model`:** enable `plugins.entries.codex.enabled`,
select an `openai/gpt-*` model with `embeddedHarness.runtime: "codex"` (or a
legacy `codex/*` ref), and check whether `plugins.allow` excludes `codex`.
**OpenClaw uses PI instead of Codex:** `runtime: "auto"` can still use PI as the
compatibility backend when no Codex harness claims the run. Set
@@ -693,11 +672,8 @@ turn for that agent must be a Codex-supported OpenAI model.
## Related
- [Agent harness plugins](/plugins/sdk-agent-harness)
- [Agent Harness Plugins](/plugins/sdk-agent-harness)
- [Agent runtimes](/concepts/agent-runtimes)
- [Model providers](/concepts/model-providers)
- [OpenAI provider](/providers/openai)
- [Status](/cli/status)
- [Plugin hooks](/plugins/hooks)
- [Configuration reference](/gateway/configuration-reference)
- [Model Providers](/concepts/model-providers)
- [Configuration Reference](/gateway/configuration-reference)
- [Testing](/help/testing-live#live-codex-app-server-harness-smoke)

View File

@@ -1,98 +0,0 @@
---
summary: "Plugin compatibility contracts, deprecation metadata, and migration expectations"
title: "Plugin compatibility"
read_when:
- You maintain an OpenClaw plugin
- You see a plugin compatibility warning
- You are planning a plugin SDK or manifest migration
---
OpenClaw keeps older plugin contracts wired through named compatibility
adapters before removing them. This protects existing bundled and external
plugins while the SDK, manifest, setup, config, and agent runtime contracts
evolve.
## Compatibility registry
Plugin compatibility contracts are tracked in the core registry at
`src/plugins/compat/registry.ts`.
Each record has:
- a stable compatibility code
- status: `active`, `deprecated`, `removal-pending`, or `removed`
- owner: SDK, config, setup, channel, provider, plugin execution, agent runtime,
or core
- introduction and deprecation dates when applicable
- replacement guidance
- docs, diagnostics, and tests that cover the old and new behavior
The registry is the source for maintainer planning and future plugin inspector
checks. If a plugin-facing behavior changes, add or update the compatibility
record in the same change that adds the adapter.
## Plugin inspector package
The plugin inspector should live outside the core OpenClaw repo as a separate
package/repository backed by the versioned compatibility and manifest
contracts.
The day-one CLI should be:
```sh
openclaw-plugin-inspector ./my-plugin
```
It should emit:
- manifest/schema validation
- the contract compatibility version being checked
- install/source metadata checks
- cold-path import checks
- deprecation and compatibility warnings
Use `--json` for stable machine-readable output in CI annotations. OpenClaw
core should expose contracts and fixtures the inspector can consume, but should
not publish the inspector binary from the main `openclaw` package.
## Deprecation policy
OpenClaw should not remove a documented plugin contract in the same release
that introduces its replacement.
The migration sequence is:
1. Add the new contract.
2. Keep the old behavior wired through a named compatibility adapter.
3. Emit diagnostics or warnings when plugin authors can act.
4. Document the replacement and timeline.
5. Test both old and new paths.
6. Wait through the announced migration window.
7. Remove only with explicit breaking-release approval.
Deprecated records must include a warning start date, replacement, docs link,
and target removal date when known.
## Current compatibility areas
Current compatibility records include:
- legacy broad SDK imports such as `openclaw/plugin-sdk/compat`
- legacy hook-only plugin shapes and `before_agent_start`
- bundled plugin allowlist and enablement behavior
- legacy provider/channel env-var manifest metadata
- activation hints that are being replaced by manifest contribution ownership
- `embeddedHarness` and `agent-harness` naming aliases while public naming moves
toward `agentRuntime`
- generated bundled channel config metadata fallback while registry-first
`channelConfigs` metadata lands
New plugin code should prefer the replacement listed in the registry and in the
specific migration guide. Existing plugins can keep using a compatibility path
until the docs, diagnostics, and release notes announce a removal window.
## Release notes
Release notes should include upcoming plugin deprecations with target dates and
links to migration docs. That warning needs to happen before a compatibility
path moves to `removal-pending` or `removed`.

View File

@@ -119,10 +119,6 @@ openclaw googlemeet create --no-join
the OpenClaw Chrome profile on the node to already be signed in to Google.
Browser automation handles Meet's own first-run microphone prompt; that prompt
is not treated as a Google login failure.
Join and create flows also try to reuse an existing Meet tab before opening a
new one. Matching ignores harmless URL query strings such as `authuser`, so an
agent retry should focus the already-open meeting instead of creating a second
Chrome tab.
The command/tool output includes a `source` field (`api` or `browser`) so agents
can explain which path was used. `create` joins the new meeting by default and
@@ -145,20 +141,12 @@ For an observe-only/browser-control join, set `"mode": "transcribe"`. That does
not start the duplex realtime model bridge, so it will not talk back into the
meeting.
During realtime sessions, `google_meet` status includes browser and audio bridge
health such as `inCall`, `manualActionRequired`, `providerConnected`,
`realtimeReady`, `audioInputActive`, `audioOutputActive`, last input/output
timestamps, byte counters, and bridge closed state. If a safe Meet page prompt
appears, browser automation handles it when it can. Login, host admission, and
browser/OS permission prompts are reported as manual action with a reason and
message for the agent to relay.
Chrome joins as the signed-in Chrome profile. In Meet, pick `BlackHole 2ch` for
the microphone/speaker path used by OpenClaw. For clean duplex audio, use
separate virtual devices or a Loopback-style graph; a single BlackHole device is
enough for a first smoke test but can echo.
### Local gateway + Parallels Chrome
### Local Gateway + Parallels Chrome
You do **not** need a full OpenClaw Gateway or model API key inside a macOS VM
just to make the VM own Chrome. Run the Gateway and agent locally, then run a
@@ -437,51 +425,8 @@ OAuth is optional for creating a Meet link because `googlemeet create` can fall
back to browser automation. Configure OAuth when you want official API create,
space resolution, or Meet Media API preflight checks.
Google Meet API access uses user OAuth: create a Google Cloud OAuth client,
request the required scopes, authorize a Google account, then store the
resulting refresh token in the Google Meet plugin config or provide the
`OPENCLAW_GOOGLE_MEET_*` environment variables.
OAuth does not replace the Chrome join path. Chrome and Chrome-node transports
still join through a signed-in Chrome profile, BlackHole/SoX, and a connected
node when you use browser participation. OAuth is only for the official Google
Meet API path: create meeting spaces, resolve spaces, and run Meet Media API
preflight checks.
### Create Google credentials
In Google Cloud Console:
1. Create or select a Google Cloud project.
2. Enable **Google Meet REST API** for that project.
3. Configure the OAuth consent screen.
- **Internal** is simplest for a Google Workspace organization.
- **External** works for personal/test setups; while the app is in Testing,
add each Google account that will authorize the app as a test user.
4. Add the scopes OpenClaw requests:
- `https://www.googleapis.com/auth/meetings.space.created`
- `https://www.googleapis.com/auth/meetings.space.readonly`
- `https://www.googleapis.com/auth/meetings.conference.media.readonly`
5. Create an OAuth client ID.
- Application type: **Web application**.
- Authorized redirect URI:
```text
http://localhost:8085/oauth2callback
```
6. Copy the client ID and client secret.
`meetings.space.created` is required by Google Meet `spaces.create`.
`meetings.space.readonly` lets OpenClaw resolve Meet URLs/codes to spaces.
`meetings.conference.media.readonly` is for Meet Media API preflight and media
work; Google may require Developer Preview enrollment for actual Media API use.
If you only need browser-based Chrome joins, skip OAuth entirely.
### Mint the refresh token
Configure `oauth.clientId` and optionally `oauth.clientSecret`, or pass them as
environment variables, then run:
Google Meet API access uses a personal OAuth client first. Configure
`oauth.clientId` and optionally `oauth.clientSecret`, then run:
```bash
openclaw googlemeet auth login --json
@@ -491,116 +436,11 @@ The command prints an `oauth` config block with a refresh token. It uses PKCE,
localhost callback on `http://localhost:8085/oauth2callback`, and a manual
copy/paste flow with `--manual`.
Examples:
```bash
OPENCLAW_GOOGLE_MEET_CLIENT_ID="your-client-id" \
OPENCLAW_GOOGLE_MEET_CLIENT_SECRET="your-client-secret" \
openclaw googlemeet auth login --json
```
Use manual mode when the browser cannot reach the local callback:
```bash
OPENCLAW_GOOGLE_MEET_CLIENT_ID="your-client-id" \
OPENCLAW_GOOGLE_MEET_CLIENT_SECRET="your-client-secret" \
openclaw googlemeet auth login --json --manual
```
The JSON output includes:
```json
{
"oauth": {
"clientId": "your-client-id",
"clientSecret": "your-client-secret",
"refreshToken": "refresh-token",
"accessToken": "access-token",
"expiresAt": 1770000000000
},
"scope": "..."
}
```
Store the `oauth` object under the Google Meet plugin config:
```json5
{
plugins: {
entries: {
"google-meet": {
enabled: true,
config: {
oauth: {
clientId: "your-client-id",
clientSecret: "your-client-secret",
refreshToken: "refresh-token",
},
},
},
},
},
}
```
Prefer environment variables when you do not want the refresh token in config.
If both config and environment values are present, the plugin resolves config
first and then environment fallback.
The OAuth consent includes Meet space creation, Meet space read access, and Meet
conference media read access. If you authenticated before meeting creation
support existed, rerun `openclaw googlemeet auth login --json` so the refresh
token has the `meetings.space.created` scope.
### Verify OAuth with doctor
Run the OAuth doctor when you want a fast, non-secret health check:
```bash
openclaw googlemeet doctor --oauth --json
```
This does not load the Chrome runtime or require a connected Chrome node. It
checks that OAuth config exists and that the refresh token can mint an access
token. The JSON report includes only status fields such as `ok`, `configured`,
`tokenSource`, `expiresAt`, and check messages; it does not print the access
token, refresh token, or client secret.
Common results:
| Check | Meaning |
| -------------------- | --------------------------------------------------------------------------------------- |
| `oauth-config` | `oauth.clientId` plus `oauth.refreshToken`, or a cached access token, is present. |
| `oauth-token` | The cached access token is still valid, or the refresh token minted a new access token. |
| `meet-spaces-get` | Optional `--meeting` check resolved an existing Meet space. |
| `meet-spaces-create` | Optional `--create-space` check created a new Meet space. |
To prove Google Meet API enablement and `spaces.create` scope as well, run the
side-effecting create check:
```bash
openclaw googlemeet doctor --oauth --create-space --json
openclaw googlemeet create --no-join --json
```
`--create-space` creates a throwaway Meet URL. Use it when you need to confirm
that the Google Cloud project has the Meet API enabled and that the authorized
account has the `meetings.space.created` scope.
To prove read access for an existing meeting space:
```bash
openclaw googlemeet doctor --oauth --meeting https://meet.google.com/abc-defg-hij --json
openclaw googlemeet resolve-space --meeting https://meet.google.com/abc-defg-hij
```
`doctor --oauth --meeting` and `resolve-space` prove read access to an existing
space that the authorized Google account can access. A `403` from these checks
usually means the Google Meet REST API is disabled, the consented refresh token
is missing the required scope, or the Google account cannot access that Meet
space. A refresh-token error means rerun `openclaw googlemeet auth login
--json` and store the new `oauth` block.
No OAuth credentials are needed for the browser fallback. In that mode, Google
auth comes from the signed-in Chrome profile on the selected node, not from
OpenClaw config.
@@ -628,37 +468,6 @@ Run preflight before media work:
openclaw googlemeet preflight --meeting https://meet.google.com/abc-defg-hij
```
List meeting artifacts and attendance after Meet has created conference records:
```bash
openclaw googlemeet artifacts --meeting https://meet.google.com/abc-defg-hij
openclaw googlemeet attendance --meeting https://meet.google.com/abc-defg-hij
```
If you already know the conference record id, address it directly:
```bash
openclaw googlemeet artifacts --conference-record conferenceRecords/abc123 --json
openclaw googlemeet attendance --conference-record conferenceRecords/abc123 --json
```
Write a readable report:
```bash
openclaw googlemeet artifacts --conference-record conferenceRecords/abc123 \
--format markdown --output meet-artifacts.md
openclaw googlemeet attendance --conference-record conferenceRecords/abc123 \
--format markdown --output meet-attendance.md
```
`artifacts` returns conference record metadata plus participant, recording,
transcript, structured transcript-entry, and smart-note resource metadata when
Google exposes it for the meeting. Use `--no-transcript-entries` to skip
entry lookup for large meetings. `attendance` expands participants into
participant-session rows with join/leave timestamps. These commands use the Meet
REST API only; Google Docs/Drive document body download is intentionally out of
scope because that requires separate Google Docs/Drive access.
Create a fresh Meet space:
```bash
@@ -691,30 +500,6 @@ Example JSON output from the browser fallback:
}
```
If the browser fallback hits Google login or a Meet permission blocker before it
can create the URL, the Gateway method returns a failed response and the
`google_meet` tool returns structured details instead of a plain string:
```json
{
"source": "browser",
"error": "google-login-required: Sign in to Google in the OpenClaw browser profile, then retry meeting creation.",
"manualActionRequired": true,
"manualActionReason": "google-login-required",
"manualActionMessage": "Sign in to Google in the OpenClaw browser profile, then retry meeting creation.",
"browser": {
"nodeId": "ba0f4e4bc...",
"targetId": "tab-1",
"browserUrl": "https://accounts.google.com/signin",
"browserTitle": "Sign in - Google Accounts"
}
}
```
When an agent sees `manualActionRequired: true`, it should report the
`manualActionMessage` plus the browser node/tab context and stop opening new
Meet tabs until the operator completes the browser step.
Example JSON output from API create:
```json
@@ -1091,10 +876,6 @@ to the pinned Chrome node browser. Confirm:
- For browser fallback: retries reuse an existing `https://meet.google.com/new`
or Google account prompt tab before opening a new tab. If an agent times out,
retry the tool call rather than manually opening another Meet tab.
- For browser fallback: if the tool returns `manualActionRequired: true`, use
the returned `browser.nodeId`, `browser.targetId`, `browserUrl`, and
`manualActionMessage` to guide the operator. Do not retry in a loop until that
action is complete.
- For browser fallback: if Meet shows "Do you want people to hear you in the
meeting?", leave the tab open. OpenClaw should click **Use microphone** or, for
create-only fallback, **Continue without microphone** through browser
@@ -1107,7 +888,7 @@ Check the realtime path:
```bash
openclaw googlemeet setup
openclaw googlemeet doctor
openclaw googlemeet status
```
Use `mode: "realtime"` for listen/talk-back. `mode: "transcribe"` intentionally
@@ -1122,28 +903,6 @@ Also verify:
- Meet microphone and speaker are routed through the virtual audio path used by
OpenClaw.
`googlemeet doctor [session-id]` prints the session, node, in-call state,
manual action reason, realtime provider connection, `realtimeReady`, audio
input/output activity, last audio timestamps, byte counters, and browser URL.
Use `googlemeet status [session-id]` when you need the raw JSON. Use
`googlemeet doctor --oauth` when you need to verify Google Meet OAuth refresh
without exposing tokens; add `--meeting` or `--create-space` when you need a
Google Meet API proof as well.
If an agent timed out and you can see a Meet tab already open, inspect that tab
without opening another one:
```bash
openclaw googlemeet recover-tab
openclaw googlemeet recover-tab https://meet.google.com/abc-defg-hij
```
The equivalent tool action is `recover_current_tab`. It focuses and inspects an
existing Meet tab on the configured Chrome node. It does not open a new tab or
create a new session; it reports the current blocker, such as login, admission,
permissions, or audio-choice state. The CLI command talks to the configured
Gateway, so the Gateway must be running and the Chrome node must be connected.
### Twilio setup checks fail
`twilio-voice-call-plugin` fails when `voice-call` is not allowed or not enabled.
@@ -1163,21 +922,6 @@ Then restart or reload the Gateway and run:
```bash
openclaw googlemeet setup
openclaw voicecall setup
openclaw voicecall smoke
```
`voicecall smoke` is readiness-only by default. To dry-run a specific number:
```bash
openclaw voicecall smoke --to "+15555550123"
```
Only add `--yes` when you intentionally want to place a live outbound notify
call:
```bash
openclaw voicecall smoke --to "+15555550123" --yes
```
### Twilio call starts but never enters the meeting

View File

@@ -190,11 +190,6 @@ Use message hooks for channel-level routing and delivery policy:
- `message_sending`: rewrite `content` or return `{ cancel: true }`.
- `message_sent`: observe final success or failure.
For audio-only TTS replies, `content` may contain the hidden spoken transcript
even when the channel payload has no visible text/caption. Rewriting that
`content` updates the hook-visible transcript only; it is not rendered as a
media caption.
Message hook contexts expose stable correlation fields when available:
`ctx.sessionKey`, `ctx.runId`, `ctx.messageId`, `ctx.senderId`, `ctx.trace`,
`ctx.traceId`, `ctx.spanId`, `ctx.parentSpanId`, and `ctx.callDepth`. Prefer

View File

@@ -143,7 +143,6 @@ or npm install metadata. Those belong in your plugin code and `package.json`.
| `providers` | No | `string[]` | Provider ids owned by this plugin. |
| `providerDiscoveryEntry` | No | `string` | Lightweight provider-discovery module path, relative to the plugin root, for manifest-scoped provider catalog metadata that can be loaded without activating the full plugin runtime. |
| `modelSupport` | No | `object` | Manifest-owned shorthand model-family metadata used to auto-load the plugin before runtime. |
| `modelCatalog` | No | `object` | Declarative model catalog metadata for providers owned by this plugin. This is the control-plane contract for future read-only listing, onboarding, model pickers, aliases, and suppression without loading plugin runtime. |
| `providerEndpoints` | No | `object[]` | Manifest-owned endpoint host/baseUrl metadata for provider routes that core must classify before provider runtime loads. |
| `cliBackends` | No | `string[]` | CLI inference backend ids owned by this plugin. Used for startup auto-activation from explicit config refs. |
| `syntheticAuthRefs` | No | `string[]` | Provider or CLI backend refs whose plugin-owned synthetic auth hook should be probed during cold model discovery before runtime loads. |
@@ -584,105 +583,6 @@ Fields:
| `modelPrefixes` | `string[]` | Prefixes matched with `startsWith` against shorthand model ids. |
| `modelPatterns` | `string[]` | Regex sources matched against shorthand model ids after profile suffix removal. |
## modelCatalog reference
Use `modelCatalog` when OpenClaw should know provider model metadata before
loading plugin runtime. This is the manifest-owned source for fixed catalog
rows, provider aliases, suppression rules, and discovery mode. Runtime refresh
still belongs in provider runtime code, but the manifest tells core when runtime
is required.
```json
{
"providers": ["openai"],
"modelCatalog": {
"providers": {
"openai": {
"baseUrl": "https://api.openai.com/v1",
"api": "openai-responses",
"models": [
{
"id": "gpt-5.4",
"name": "GPT-5.4",
"input": ["text", "image"],
"reasoning": true,
"contextWindow": 256000,
"maxTokens": 128000,
"cost": {
"input": 1.25,
"output": 10,
"cacheRead": 0.125
},
"status": "available",
"tags": ["default"]
}
]
}
},
"aliases": {
"azure-openai-responses": {
"provider": "openai",
"api": "azure-openai-responses"
}
},
"suppressions": [
{
"provider": "azure-openai-responses",
"model": "gpt-5.3-codex-spark",
"reason": "not available on Azure OpenAI Responses"
}
],
"discovery": {
"openai": "static"
}
}
}
```
Top-level fields:
| Field | Type | What it means |
| -------------- | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| `providers` | `Record<string, object>` | Catalog rows for provider ids owned by this plugin. Keys should also appear in top-level `providers`. |
| `aliases` | `Record<string, object>` | Provider aliases that should resolve to an owned provider for catalog or suppression planning. |
| `suppressions` | `object[]` | Model rows from another source that this plugin suppresses for a provider-specific reason. |
| `discovery` | `Record<string, "static" \| "refreshable" \| "runtime">` | Whether the provider catalog can be read from manifest metadata, refreshed into cache, or requires runtime. |
Provider fields:
| Field | Type | What it means |
| --------- | ------------------------ | ----------------------------------------------------------------- |
| `baseUrl` | `string` | Optional default base URL for models in this provider catalog. |
| `api` | `ModelApi` | Optional default API adapter for models in this provider catalog. |
| `headers` | `Record<string, string>` | Optional static headers that apply to this provider catalog. |
| `models` | `object[]` | Required model rows. Rows without an `id` are ignored. |
Model fields:
| Field | Type | What it means |
| --------------- | -------------------------------------------------------------- | --------------------------------------------------------------------------- |
| `id` | `string` | Provider-local model id, without the `provider/` prefix. |
| `name` | `string` | Optional display name. |
| `api` | `ModelApi` | Optional per-model API override. |
| `baseUrl` | `string` | Optional per-model base URL override. |
| `headers` | `Record<string, string>` | Optional per-model static headers. |
| `input` | `Array<"text" \| "image" \| "document">` | Modalities the model accepts. |
| `reasoning` | `boolean` | Whether the model exposes reasoning behavior. |
| `contextWindow` | `number` | Native provider context window. |
| `contextTokens` | `number` | Optional effective runtime context cap when different from `contextWindow`. |
| `maxTokens` | `number` | Maximum output tokens when known. |
| `cost` | `object` | Optional USD per million token pricing, including optional `tieredPricing`. |
| `compat` | `object` | Optional compatibility flags matching OpenClaw model config compatibility. |
| `status` | `"available"` \| `"preview"` \| `"deprecated"` \| `"disabled"` | Listing status. Suppress only when the row must not appear at all. |
| `statusReason` | `string` | Optional reason shown with non-available status. |
| `replaces` | `string[]` | Older provider-local model ids this model supersedes. |
| `replacedBy` | `string` | Replacement provider-local model id for deprecated rows. |
| `tags` | `string[]` | Stable tags used by pickers and filters. |
Do not put runtime-only data in `modelCatalog`. If a provider needs account
state, an API request, or local process discovery to know the complete model
set, declare that provider as `refreshable` or `runtime` in `discovery`.
Legacy top-level capability keys are deprecated. Use `openclaw doctor --fix` to
move `speechProviders`, `realtimeTranscriptionProviders`,
`realtimeVoiceProviders`, `mediaUnderstandingProviders`,

View File

@@ -152,14 +152,6 @@ paths and should return the channel metadata, setup-safe config adapter, status
adapter, and channel secret target metadata needed for those summaries. Do not
start clients, listeners, or transport runtimes from the setup entry.
Keep the main channel entry import path narrow too. Discovery can evaluate the
entry and the channel plugin module to register capabilities without activating
the channel. Files such as `channel-plugin-api.ts` should export the channel
plugin object without importing setup wizards, transport clients, socket
listeners, subprocess launchers, or service startup modules. Put those runtime
pieces in modules loaded from `registerFull(...)`, runtime setters, or lazy
capability adapters.
`createOptionalChannelSetupWizard`, `DEFAULT_ACCOUNT_ID`,
`createTopLevelChannelDmPolicy`, `setSetupChannelEnabled`, and
`splitSetupEntries`
@@ -474,14 +466,6 @@ should use `resolveInboundMentionDecision({ facts, policy })`.
You can also pass raw adapter objects instead of the declarative options
if you need full control.
Raw outbound adapters may define a `chunker(text, limit, ctx)` function.
The optional `ctx.formatting` carries delivery-time formatting decisions
such as `maxLinesPerMessage`; apply it before sending so reply threading
and chunk boundaries are resolved once by shared outbound delivery.
Send contexts also include `replyToIdSource` (`implicit` or `explicit`)
when a native reply target was resolved, so payload helpers can preserve
explicit reply tags without consuming an implicit single-use reply slot.
</Accordion>
</Step>

View File

@@ -121,16 +121,11 @@ export default defineChannelPluginEntry({
- `setRuntime` is called during registration so you can store the runtime reference
(typically via `createPluginRuntimeStore`). It is skipped during CLI metadata
capture.
- `registerCliMetadata` runs during `api.registrationMode === "cli-metadata"`,
`api.registrationMode === "discovery"`, and
`api.registrationMode === "full"`.
- `registerCliMetadata` runs during both `api.registrationMode === "cli-metadata"`
and `api.registrationMode === "full"`.
Use it as the canonical place for channel-owned CLI descriptors so root help
stays non-activating, discovery snapshots include static command metadata, and
normal CLI command registration remains compatible with full plugin loads.
- Discovery registration is non-activating, not import-free. OpenClaw may
evaluate the trusted plugin entry and channel plugin module to build the
snapshot, so keep top-level imports side-effect-free and put sockets,
clients, workers, and services behind `"full"`-only paths.
stays non-activating while normal CLI command registration remains compatible
with full plugin loads.
- `registerFull` only runs when `api.registrationMode === "full"`. It is skipped
during setup-only loading.
- Like `definePluginEntry`, `configSchema` can be a lazy factory and OpenClaw
@@ -202,24 +197,19 @@ setter before the full channel entry loads.
`api.registrationMode` tells your plugin how it was loaded:
| Mode | When | What to register |
| ----------------- | --------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `"full"` | Normal gateway startup | Everything |
| `"discovery"` | Read-only capability discovery | Channel registration plus static CLI descriptors; entry code may load, but skip sockets, workers, clients, and services |
| `"setup-only"` | Disabled/unconfigured channel | Channel registration only |
| `"setup-runtime"` | Setup flow with runtime available | Channel registration plus only the lightweight runtime needed before the full entry loads |
| `"cli-metadata"` | Root help / CLI metadata capture | CLI descriptors only |
| Mode | When | What to register |
| ----------------- | --------------------------------- | ----------------------------------------------------------------------------------------- |
| `"full"` | Normal gateway startup | Everything |
| `"setup-only"` | Disabled/unconfigured channel | Channel registration only |
| `"setup-runtime"` | Setup flow with runtime available | Channel registration plus only the lightweight runtime needed before the full entry loads |
| `"cli-metadata"` | Root help / CLI metadata capture | CLI descriptors only |
`defineChannelPluginEntry` handles this split automatically. If you use
`definePluginEntry` directly for a channel, check mode yourself:
```typescript
register(api) {
if (
api.registrationMode === "cli-metadata" ||
api.registrationMode === "discovery" ||
api.registrationMode === "full"
) {
if (api.registrationMode === "cli-metadata" || api.registrationMode === "full") {
api.registerCli(/* ... */);
if (api.registrationMode === "cli-metadata") return;
}
@@ -232,13 +222,6 @@ register(api) {
}
```
Discovery mode builds a non-activating registry snapshot. It may still evaluate
the plugin entry and the channel plugin object so OpenClaw can register channel
capabilities and static CLI descriptors. Treat module evaluation in discovery as
trusted but lightweight: no network clients, subprocesses, listeners, database
connections, background workers, credential reads, or other live runtime side
effects at top level.
Treat `"setup-runtime"` as the window where setup-only startup surfaces must
exist without re-entering the full bundled channel runtime. Good fits are
channel registration, setup-safe HTTP routes, setup-safe gateway methods, and
@@ -251,10 +234,6 @@ For CLI registrars specifically:
want OpenClaw to lazy-load the real CLI module on first invocation
- make sure those descriptors cover every top-level command root exposed by the
registrar
- keep descriptor command names to letters, numbers, hyphen, and underscore,
starting with a letter or number; OpenClaw rejects descriptor names outside
that shape and strips terminal control sequences from descriptions before
rendering help
- use `commands` alone only for eager compatibility paths
## Plugin shapes

View File

@@ -262,7 +262,7 @@ releases.
| `plugin-sdk/inbound-reply-dispatch` | Inbound reply helpers | Shared record-and-dispatch helpers |
| `plugin-sdk/messaging-targets` | Messaging target parsing | Target parsing/matching helpers |
| `plugin-sdk/outbound-media` | Outbound media helpers | Shared outbound media loading |
| `plugin-sdk/outbound-runtime` | Outbound runtime helpers | Outbound delivery, identity/send delegate, session, formatting, and payload planning helpers |
| `plugin-sdk/outbound-runtime` | Outbound runtime helpers | Outbound identity/send delegate and payload planning helpers |
| `plugin-sdk/thread-bindings-runtime` | Thread-binding helpers | Thread-binding lifecycle and adapter helpers |
| `plugin-sdk/agent-media-payload` | Legacy media payload helpers | Agent media payload builder for legacy field layouts |
| `plugin-sdk/channel-runtime` | Deprecated compatibility shim | Legacy channel runtime utilities only |

View File

@@ -122,8 +122,8 @@ await api.runtime.subagent.deleteSession({
### `api.runtime.nodes`
List connected nodes and invoke a node-host command from Gateway-loaded plugin
code or from plugin CLI commands. Use this when a plugin owns local work on a
paired device, for example a browser or audio bridge on another Mac.
code. Use this when a plugin owns local work on a paired device, for example a
browser or audio bridge on another Mac.
```typescript
const { nodes } = await api.runtime.nodes.list({ connected: true });
@@ -136,9 +136,7 @@ const result = await api.runtime.nodes.invoke({
});
```
Inside the Gateway this runtime is in-process. In plugin CLI commands it calls
the configured Gateway over RPC, so commands such as `openclaw googlemeet
recover-tab` can inspect paired nodes from the terminal. Node commands still go
This runtime is only available inside the Gateway. Node commands still go
through normal Gateway node pairing, command allowlists, and node-local command
handling.
@@ -462,6 +460,6 @@ Beyond `api.runtime`, the API object also provides:
## Related
- [SDK overview](/plugins/sdk-overview) subpath reference
- [SDK entry points](/plugins/sdk-entrypoints) `definePluginEntry` options
- [Plugin internals](/plugins/architecture) capability model and registry
- [SDK Overview](/plugins/sdk-overview) -- subpath reference
- [SDK Entry Points](/plugins/sdk-entrypoints) -- `definePluginEntry` options
- [Plugin Internals](/plugins/architecture) -- capability model and registry

View File

@@ -566,6 +566,6 @@ startup installs; keep using the explicit plugin installer.
## Related
- [SDK entry points](/plugins/sdk-entrypoints) `definePluginEntry` and `defineChannelPluginEntry`
- [Plugin manifest](/plugins/manifest) full manifest schema reference
- [Building plugins](/plugins/building-plugins) step-by-step getting started guide
- [SDK Entry Points](/plugins/sdk-entrypoints) -- `definePluginEntry` and `defineChannelPluginEntry`
- [Plugin Manifest](/plugins/manifest) -- full manifest schema reference
- [Building Plugins](/plugins/building-plugins) -- step-by-step getting started guide

View File

@@ -50,7 +50,7 @@ For the plugin authoring guide, see [Plugin SDK overview](/plugins/sdk-overview)
| `plugin-sdk/inbound-reply-dispatch` | Shared inbound record-and-dispatch helpers |
| `plugin-sdk/messaging-targets` | Target parsing/matching helpers |
| `plugin-sdk/outbound-media` | Shared outbound media loading helpers |
| `plugin-sdk/outbound-runtime` | Outbound delivery, identity, send delegate, session, formatting, and payload planning helpers |
| `plugin-sdk/outbound-runtime` | Outbound identity, send delegate, and payload planning helpers |
| `plugin-sdk/poll-runtime` | Narrow poll normalization helpers |
| `plugin-sdk/thread-bindings-runtime` | Thread-binding lifecycle and adapter helpers |
| `plugin-sdk/agent-media-payload` | Legacy agent media payload builder |
@@ -265,7 +265,7 @@ For the plugin authoring guide, see [Plugin SDK overview](/plugins/sdk-overview)
<Accordion title="Reserved bundled-helper subpaths">
| Family | Current subpaths | Intended use |
| --- | --- | --- |
| Browser | `plugin-sdk/browser-cdp`, `plugin-sdk/browser-config-runtime`, `plugin-sdk/browser-config-support`, `plugin-sdk/browser-control-auth`, `plugin-sdk/browser-node-runtime`, `plugin-sdk/browser-profiles`, `plugin-sdk/browser-security-runtime`, `plugin-sdk/browser-setup-tools`, `plugin-sdk/browser-support` | Bundled browser plugin support helpers. `browser-profiles` exports `resolveBrowserConfig`, `resolveProfile`, `ResolvedBrowserConfig`, `ResolvedBrowserProfile`, and `ResolvedBrowserTabCleanupConfig` for the normalized `browser.tabCleanup` shape. `browser-support` remains the compatibility barrel. |
| Browser | `plugin-sdk/browser-cdp`, `plugin-sdk/browser-config-runtime`, `plugin-sdk/browser-config-support`, `plugin-sdk/browser-control-auth`, `plugin-sdk/browser-node-runtime`, `plugin-sdk/browser-profiles`, `plugin-sdk/browser-security-runtime`, `plugin-sdk/browser-setup-tools`, `plugin-sdk/browser-support` | Bundled browser plugin support helpers (`browser-support` remains the compatibility barrel) |
| Matrix | `plugin-sdk/matrix`, `plugin-sdk/matrix-helper`, `plugin-sdk/matrix-runtime-heavy`, `plugin-sdk/matrix-runtime-shared`, `plugin-sdk/matrix-runtime-surface`, `plugin-sdk/matrix-surface`, `plugin-sdk/matrix-thread-bindings` | Bundled Matrix helper/runtime surface |
| Line | `plugin-sdk/line`, `plugin-sdk/line-core`, `plugin-sdk/line-runtime`, `plugin-sdk/line-surface` | Bundled LINE helper/runtime surface |
| IRC | `plugin-sdk/irc`, `plugin-sdk/irc-surface` | Bundled IRC helper surface |

View File

@@ -6,6 +6,8 @@ read_when:
title: "Voice call plugin"
---
# Voice Call (plugin)
Voice calls for OpenClaw via a plugin. Supports outbound notifications and
multi-turn conversations with inbound policies.
@@ -139,36 +141,6 @@ Set config under `plugins.entries.voice-call.config`:
}
```
Check setup before testing with a real provider:
```bash
openclaw voicecall setup
```
The default output is readable in chat logs and terminal sessions. It checks
whether the plugin is enabled, the provider and credentials are present, webhook
exposure is configured, and only one audio mode is active. Use
`openclaw voicecall setup --json` for scripts.
For Twilio, Telnyx, and Plivo, setup must resolve to a public webhook URL. If the
configured `publicUrl`, tunnel URL, Tailscale URL, or serve fallback resolves to
loopback or private network space, setup fails instead of starting a provider
that cannot receive real carrier webhooks.
For a no-surprises smoke test, run:
```bash
openclaw voicecall smoke
openclaw voicecall smoke --to "+15555550123"
```
The second command is still a dry run. Add `--yes` to place a short outbound
notify call:
```bash
openclaw voicecall smoke --to "+15555550123" --yes
```
Notes:
- Twilio/Telnyx require a **publicly reachable** webhook URL.
@@ -476,14 +448,11 @@ streaming speech on calls. You can override it under the plugin config with the
Notes:
- Legacy `tts.<provider>` keys inside plugin config (`openai`, `elevenlabs`, `microsoft`, `edge`) are repaired by `openclaw doctor --fix`; committed config should use `tts.providers.<provider>`.
- Legacy `tts.<provider>` keys inside plugin config (`openai`, `elevenlabs`, `microsoft`, `edge`) are auto-migrated to `tts.providers.<provider>` on load. Prefer the `providers` shape in committed config.
- **Microsoft speech is ignored for voice calls** (telephony audio needs PCM; the current Microsoft transport does not expose telephony PCM output).
- Core TTS is used when Twilio media streaming is enabled; otherwise calls fall back to provider native voices.
- If a Twilio media stream is already active, Voice Call does not fall back to TwiML `<Say>`. If telephony TTS is unavailable in that state, the playback request fails instead of mixing two playback paths.
- When telephony TTS falls back to a secondary provider, Voice Call logs a warning with the provider chain (`from`, `to`, `attempts`) for debugging.
- When Twilio barge-in or stream teardown clears the pending TTS queue, queued
playback requests settle instead of hanging callers that are awaiting playback
completion.
### More examples
@@ -595,9 +564,6 @@ For outbound `conversation` calls, first-message handling is tied to live playba
- Barge-in queue clear and auto-response are suppressed only while the initial greeting is actively speaking.
- If initial playback fails, the call returns to `listening` and the initial message remains queued for retry.
- Initial playback for Twilio streaming starts on stream connect without extra delay.
- Barge-in aborts active playback and clears queued-but-not-yet-playing Twilio
TTS entries. Cleared entries resolve as skipped, so follow-up response logic
can continue without waiting on audio that will never play.
- Realtime voice conversations use the realtime stream's own opening turn. Voice Call does not post a legacy `<Say>` TwiML update for that initial message, so outbound `<Connect><Stream>` sessions stay attached.
### Twilio stream disconnect grace

View File

@@ -90,12 +90,6 @@ back on the follow-up request. OpenClaw handles this inside the DeepSeek plugin,
so normal multi-turn tool use works with `deepseek/deepseek-v4-flash` and
`deepseek/deepseek-v4-pro`.
If you switch an existing session from another OpenAI-compatible provider to a
DeepSeek V4 model, older assistant tool-call turns may not have native
DeepSeek `reasoning_content`. OpenClaw fills that missing field for DeepSeek V4
thinking requests so the provider can accept the replayed tool-call history
without requiring `/new`.
When thinking is disabled in OpenClaw (including the UI **None** selection),
OpenClaw sends DeepSeek `thinking: { type: "disabled" }` and strips replayed
`reasoning_content` from the outgoing history. This keeps disabled-thinking

View File

@@ -267,7 +267,6 @@ To use Google as the default TTS provider:
google: {
model: "gemini-3.1-flash-tts-preview",
voiceName: "Kore",
audioProfile: "Speak professionally with a calm tone.",
},
},
},
@@ -275,14 +274,9 @@ To use Google as the default TTS provider:
}
```
Gemini API TTS uses natural-language prompting for style control. Set
`audioProfile` to prepend a reusable style prompt before the spoken text. Set
`speakerName` when your prompt text refers to a named speaker.
Gemini API TTS also accepts expressive square-bracket audio tags in the text,
such as `[whispers]` or `[laughs]`. To keep tags out of the visible chat reply
while sending them to TTS, put them inside a `[[tts:text]]...[[/tts:text]]`
block:
Gemini API TTS accepts expressive square-bracket audio tags in the text, such as
`[whispers]` or `[laughs]`. To keep tags out of the visible chat reply while
sending them to TTS, put them inside a `[[tts:text]]...[[/tts:text]]` block:
```text
Here is the clean reply text.

View File

@@ -244,29 +244,6 @@ exposed separately through the plugin-owned `MiniMax-VL-01` media provider.
See [Image Generation](/tools/image-generation) for shared tool parameters, provider selection, and failover behavior.
</Note>
### Text-to-speech
The bundled `minimax` plugin registers MiniMax T2A v2 as a speech provider for
`messages.tts`.
- Default TTS model: `speech-2.8-hd`
- Default voice: `English_expressive_narrator`
- Normal audio attachments stay MP3.
- Voice-note targets such as Feishu and Telegram are transcoded from MiniMax
MP3 to 48kHz Opus with `ffmpeg`, because the Feishu/Lark file API only
accepts `file_type: "opus"` for native audio messages.
- MiniMax T2A accepts fractional `speed` and `vol`, but `pitch` is sent as an
integer; OpenClaw truncates fractional `pitch` values before the API request.
| Setting | Env var | Default | Description |
| ---------------------------------------- | ---------------------- | ----------------------------- | -------------------------------- |
| `messages.tts.providers.minimax.baseUrl` | `MINIMAX_API_HOST` | `https://api.minimax.io` | MiniMax T2A API host. |
| `messages.tts.providers.minimax.model` | `MINIMAX_TTS_MODEL` | `speech-2.8-hd` | TTS model id. |
| `messages.tts.providers.minimax.voiceId` | `MINIMAX_TTS_VOICE_ID` | `English_expressive_narrator` | Voice id used for speech output. |
| `messages.tts.providers.minimax.speed` | | `1.0` | Playback speed, `0.5..2.0`. |
| `messages.tts.providers.minimax.vol` | | `1.0` | Volume, `(0, 10]`. |
| `messages.tts.providers.minimax.pitch` | | `0` | Integer pitch shift, `-12..12`. |
### Music generation
The bundled `minimax` plugin also registers music generation through the shared

View File

@@ -15,10 +15,6 @@ OpenAI provides developer APIs for GPT models. OpenClaw supports three OpenAI-fa
OpenAI explicitly supports subscription OAuth usage in external tools and workflows like OpenClaw.
Provider, model, runtime, and channel are separate layers. If those labels are
getting mixed together, read [Agent runtimes](/concepts/agent-runtimes) before
changing config.
## Quick choice
| Goal | Use | Notes |
@@ -100,9 +96,7 @@ Choose your preferred auth method and follow the setup steps.
<Note>
`openai/*` is the direct OpenAI API-key route unless you explicitly force
the Codex app-server harness. GPT-5.5 itself is currently subscription/OAuth
only; use `openai-codex/*` for Codex OAuth through the default PI runner, or
use `openai/gpt-5.5` with `embeddedHarness.runtime: "codex"` for native
Codex app-server execution.
only; use `openai-codex/*` for Codex OAuth through the default PI runner.
</Note>
### Config example
@@ -795,8 +789,6 @@ the Server-side compaction accordion below.
**Proxy/compatible routes:**
- Use looser compat behavior
- Strip Completions `store` from non-native `openai-completions` payloads
- Accept advanced `params.extra_body`/`params.extraBody` pass-through JSON for OpenAI-compatible Completions proxies
- Do not force strict tool schemas or native-only headers
Azure OpenAI uses native transport and compat behavior but does not receive the hidden attribution headers.

View File

@@ -48,7 +48,7 @@ As of the bundled pi catalog, the provider includes:
</Step>
<Step title="Set a Go model as default">
```bash
openclaw config set agents.defaults.model.primary "opencode-go/kimi-k2.6"
openclaw config set agents.defaults.model.primary "opencode-go/kimi-k2.5"
```
</Step>
<Step title="Verify models are available">
@@ -80,7 +80,7 @@ As of the bundled pi catalog, the provider includes:
```json5
{
env: { OPENCODE_API_KEY: "YOUR_API_KEY_HERE" }, // pragma: allowlist secret
agents: { defaults: { model: { primary: "opencode-go/kimi-k2.6" } } },
agents: { defaults: { model: { primary: "opencode-go/kimi-k2.5" } } },
}
```

View File

@@ -66,7 +66,7 @@ as one OpenCode setup.
</Step>
<Step title="Set a Go model as the default">
```bash
openclaw config set agents.defaults.model.primary "opencode-go/kimi-k2.6"
openclaw config set agents.defaults.model.primary "opencode-go/kimi-k2.5"
```
</Step>
<Step title="Verify models are available">
@@ -102,7 +102,7 @@ as one OpenCode setup.
| Property | Value |
| ---------------- | ------------------------------------------------------------------------ |
| Runtime provider | `opencode-go` |
| Example models | `opencode-go/kimi-k2.6`, `opencode-go/glm-5`, `opencode-go/minimax-m2.5` |
| Example models | `opencode-go/kimi-k2.5`, `opencode-go/glm-5`, `opencode-go/minimax-m2.5` |
## Advanced configuration

View File

@@ -79,32 +79,6 @@ OpenRouter can also back the `image_generate` tool. Use an OpenRouter image mode
OpenClaw sends image requests to OpenRouter's chat completions image API with `modalities: ["image", "text"]`. Gemini image models receive supported `aspectRatio` and `resolution` hints through OpenRouter's `image_config`.
## Text-to-speech
OpenRouter can also be used as a TTS provider through its OpenAI-compatible
`/audio/speech` endpoint.
```json5
{
messages: {
tts: {
auto: "always",
provider: "openrouter",
providers: {
openrouter: {
model: "hexgrad/kokoro-82m",
voice: "af_alloy",
responseFormat: "mp3",
},
},
},
},
}
```
If `messages.tts.providers.openrouter.apiKey` is omitted, TTS reuses
`models.providers.openrouter.apiKey`, then `OPENROUTER_API_KEY`.
## Authentication and headers
OpenRouter uses a Bearer token with your API key under the hood.

View File

@@ -16,7 +16,7 @@ cache values still take precedence over transcript fallback values.
Why this matters: lower token cost, faster responses, and more predictable performance for long-running sessions. Without caching, repeated prompts pay the full prompt cost on every turn even when most input did not change.
The sections below cover every cache-related knob that affects prompt reuse and token cost.
This page covers all cache-related knobs that affect prompt reuse and token cost.
Provider references:
@@ -105,7 +105,6 @@ Per-agent heartbeat is supported at `agents.list[].heartbeat`.
- Prompt caching is automatic on supported recent models. OpenClaw does not need to inject block-level cache markers.
- OpenClaw uses `prompt_cache_key` to keep cache routing stable across turns and uses `prompt_cache_retention: "24h"` only when `cacheRetention: "long"` is selected on direct OpenAI hosts.
- OpenAI-compatible Completions providers receive `prompt_cache_key` only when their model config explicitly sets `compat.supportsPromptCacheKey: true`; `cacheRetention: "none"` still suppresses it.
- OpenAI responses expose cached prompt tokens via `usage.prompt_tokens_details.cached_tokens` (or `input_tokens_details.cached_tokens` on Responses API events). OpenClaw maps that to `cacheRead`.
- OpenAI does not expose a separate cache-write token counter, so `cacheWrite` stays `0` on OpenAI paths even when the provider is warming a cache.
- OpenAI returns useful tracing and rate-limit headers such as `x-request-id`, `openai-processing-ms`, and `x-ratelimit-*`, but cache-hit accounting should come from the usage payload, not from headers.
@@ -123,7 +122,7 @@ Per-agent heartbeat is supported at `agents.list[].heartbeat`.
- Anthropic Claude model refs (`amazon-bedrock/*anthropic.claude*`) support explicit `cacheRetention` pass-through.
- Non-Anthropic Bedrock models are forced to `cacheRetention: "none"` at runtime.
### OpenRouter models
### OpenRouter Anthropic models
For `openrouter/anthropic/*` model refs, OpenClaw injects Anthropic
`cache_control` on system/developer prompt blocks to improve prompt-cache
@@ -131,16 +130,6 @@ reuse only when the request is still targeting a verified OpenRouter route
(`openrouter` on its default endpoint, or any provider/base URL that resolves
to `openrouter.ai`).
For `openrouter/deepseek/*`, `openrouter/moonshot*/*`, and `openrouter/zai/*`
model refs, `contextPruning.mode: "cache-ttl"` is allowed because OpenRouter
handles provider-side prompt caching automatically. OpenClaw does not inject
Anthropic `cache_control` markers into those requests.
DeepSeek cache construction is best-effort and can take a few seconds. An
immediate follow-up may still show `cached_tokens: 0`; verify with a repeated
same-prefix request after a short delay and use `usage.prompt_tokens_details.cached_tokens`
as the cache-hit signal.
If you repoint the model at an arbitrary OpenAI-compatible proxy URL, OpenClaw
stops injecting those OpenRouter-specific Anthropic cache markers.
@@ -348,9 +337,9 @@ Defaults:
Related docs:
- [Anthropic](/providers/anthropic)
- [Token use and costs](/reference/token-use)
- [Session pruning](/concepts/session-pruning)
- [Gateway configuration reference](/gateway/configuration-reference)
- [Token Use and Costs](/reference/token-use)
- [Session Pruning](/concepts/session-pruning)
- [Gateway Configuration Reference](/gateway/configuration-reference)
## Related

View File

@@ -15,11 +15,6 @@ Assistant output can carry a small set of delivery/render directives:
These directives are separate. `MEDIA:` and reply/voice tags remain delivery metadata; `[embed ...]` is the web-only rich render path.
When block streaming is enabled, `MEDIA:` remains single-delivery metadata for a
turn. If the same media URL is sent in a streamed block and repeated in the final
assistant payload, OpenClaw delivers the attachment once and strips the duplicate
from the final payload.
## `[embed ...]`
`[embed ...]` is the only agent-facing rich render syntax for the Control UI.
@@ -39,7 +34,7 @@ Rules:
- The web UI strips the shortcode from visible text and renders the embed inline.
- `MEDIA:` is not an embed alias and should not be used for rich embed rendering.
## Stored rendering shape
## Stored Rendering Shape
The normalized/stored assistant content block is a structured `canvas` item:

View File

@@ -114,7 +114,6 @@ Notes:
- Auth-profile plan targets require `agentId`.
- Plan entries target `profiles.*.key` / `profiles.*.token` and write sibling refs (`keyRef` / `tokenRef`).
- Auth-profile refs are included in runtime resolution and audit coverage.
- In `openclaw.json`, SecretRefs must use structured objects such as `{"source":"env","provider":"default","id":"DISCORD_BOT_TOKEN"}`. Legacy `secretref-env:<ENV_VAR>` marker strings are rejected on SecretRef credential paths; run `openclaw doctor --fix` to migrate valid markers.
- OAuth policy guard: `auth.profiles.<id>.mode = "oauth"` cannot be combined with SecretRef inputs for that profile. Startup/reload and auth-profile resolution fail fast when this policy is violated.
- For SecretRef-managed model providers, generated `agents/*/agent/models.json` entries persist non-secret markers (not resolved secret values) for `apiKey`/header surfaces.
- Marker persistence is source-authoritative: OpenClaw writes markers from the active source config snapshot (pre-resolution), not from resolved runtime secret values.

View File

@@ -7,7 +7,9 @@ read_when:
title: "Session management deep dive"
---
This page explains how OpenClaw manages sessions end-to-end:
# Session Management & Compaction (Deep Dive)
This document explains how OpenClaw manages sessions end-to-end:
- **Session routing** (how inbound messages map to a `sessionKey`)
- **Session store** (`sessions.json`) and what it tracks
@@ -19,12 +21,12 @@ This page explains how OpenClaw manages sessions end-to-end:
If you want a higher-level overview first, start with:
- [Session management](/concepts/session)
- [Compaction](/concepts/compaction)
- [Memory overview](/concepts/memory)
- [Memory search](/concepts/memory-search)
- [Session pruning](/concepts/session-pruning)
- [Transcript hygiene](/reference/transcript-hygiene)
- [/concepts/session](/concepts/session)
- [/concepts/compaction](/concepts/compaction)
- [/concepts/memory](/concepts/memory)
- [/concepts/memory-search](/concepts/memory-search)
- [/concepts/session-pruning](/concepts/session-pruning)
- [/reference/transcript-hygiene](/reference/transcript-hygiene)
---
@@ -101,14 +103,6 @@ Isolated cron runs also create session entries/transcripts, and they have dedica
- `cron.sessionRetention` (default `24h`) prunes old isolated cron run sessions from the session store (`false` disables).
- `cron.runLog.maxBytes` + `cron.runLog.keepLines` prune `~/.openclaw/cron/runs/<jobId>.jsonl` files (defaults: `2_000_000` bytes and `2000` lines).
When cron force-creates a new isolated run session, it sanitizes the previous
`cron:<jobId>` session entry before writing the new row. It carries safe
preferences such as thinking/fast/verbose settings, labels, and explicit
user-selected model/auth overrides. It drops ambient conversation context such
as channel/group routing, send or queue policy, elevation, origin, and ACP
runtime binding so a fresh isolated run cannot inherit stale delivery or
runtime authority from an older run.
---
## Session keys (`sessionKey`)
@@ -273,10 +267,6 @@ OpenClaw also enforces a safety floor for embedded runs:
- Default floor is `20000` tokens.
- Set `agents.defaults.compaction.reserveTokensFloor: 0` to disable the floor.
- If its already higher, OpenClaw leaves it alone.
- Manual `/compact` honors an explicit `agents.defaults.compaction.keepRecentTokens`
and keeps Pi's recent-tail cut point. Without an explicit keep budget,
manual compaction remains a hard checkpoint and rebuilt context starts from
the new summary.
Why: leave enough headroom for multi-turn “housekeeping” (like memory writes) before compaction becomes unavoidable.
@@ -293,10 +283,6 @@ Plugins can register a compaction provider via `registerCompactionProvider()` on
- Setting a `provider` forces `mode: "safeguard"`.
- Providers receive the same compaction instructions and identifier-preservation policy as the built-in path.
- The safeguard still preserves recent-turn and split-turn suffix context after provider output.
- Built-in safeguard summarization re-distills prior summaries with new messages
instead of preserving the full previous summary verbatim.
- Safeguard mode enables summary quality audits by default; set
`qualityGuard.enabled: false` to skip retry-on-malformed-output behavior.
- If the provider fails or returns an empty result, OpenClaw falls back to built-in LLM summarization automatically.
- Abort/timeout signals are re-thrown (not swallowed) to respect caller cancellation.

View File

@@ -27,7 +27,7 @@ Scope includes:
If you need transcript storage details, see:
- [Session management deep dive](/reference/session-management-compaction)
- [/reference/session-management-compaction](/reference/session-management-compaction)
---
@@ -112,11 +112,11 @@ external end-user instructions.
**OpenAI / OpenAI Codex**
- Image sanitization only.
- Drop orphaned reasoning signatures (standalone reasoning items without a following content block) for OpenAI Responses/Codex transcripts, and drop replayable OpenAI reasoning after a model route switch.
- Drop orphaned reasoning signatures (standalone reasoning items without a following content block) for OpenAI Responses/Codex transcripts.
- No tool call id sanitization.
- Tool result pairing repair may move real matched outputs and synthesize Codex-style `aborted` outputs for missing tool calls.
- No tool result pairing repair.
- No turn validation or reordering.
- Missing OpenAI Responses-family tool outputs are synthesized as `aborted` to match Codex replay normalization.
- No synthetic tool results.
- No thought signature stripping.
**Google (Generative AI / Gemini CLI / Antigravity)**

View File

@@ -8,8 +8,8 @@ title: "ACP agents — setup"
---
For the overview, operator runbook, and concepts, see [ACP agents](/tools/acp-agents).
The sections below cover acpx harness config, plugin setup for the MCP bridges, and permission configuration.
This page covers acpx harness config, plugin setup for the MCP bridges, and
permission configuration.
## acpx harness support (current)
@@ -235,9 +235,8 @@ Restart the gateway after changing this value.
### Health probe agent configuration
The bundled `acpx` plugin probes one harness agent while deciding whether the
embedded runtime backend is ready. If `acp.allowedAgents` is set, it defaults to
the first allowed agent; otherwise it defaults to `codex`. If your deployment
needs a different ACP agent for health checks, set the probe agent explicitly:
embedded runtime backend is ready. It defaults to `codex`. If your deployment
uses a different default ACP agent, set the probe agent to the same id:
```bash
openclaw config set plugins.entries.acpx.config.probeAgent claude

View File

@@ -66,9 +66,7 @@ injects a per-turn native hook relay so plugin hooks can block
`PermissionRequest` events through OpenClaw approvals. The v1 relay is
deliberately conservative: it does not mutate Codex-native tool arguments,
rewrite Codex thread records, or gate final answers/Stop hooks. Use explicit
ACP only when you want the ACP runtime/session model. The embedded Codex support
boundary is documented in the
[Codex harness v1 support contract](/plugins/codex-harness#v1-support-contract).
ACP only when you want the ACP runtime/session model.
Natural-language triggers that should route to the ACP runtime:

View File

@@ -165,7 +165,6 @@ openclaw browser responsebody "**/api" --max-chars 5000
openclaw browser navigate https://example.com
openclaw browser resize 1280 720
openclaw browser click 12 --double # or e12 for role refs
openclaw browser click-coords 120 340 # viewport coordinates
openclaw browser type 23 "hello" --submit
openclaw browser press Enter
openclaw browser hover 44
@@ -213,7 +212,7 @@ openclaw browser set device "iPhone 14"
Notes:
- `upload` and `dialog` are **arming** calls; run them before the click/press that triggers the chooser/dialog.
- `click`/`type`/etc require a `ref` from `snapshot` (numeric `12` or role ref `e12`). CSS selectors are intentionally not supported for actions. Use `click-coords` when the visible viewport position is the only reliable target.
- `click`/`type`/etc require a `ref` from `snapshot` (numeric `12` or role ref `e12`). CSS selectors are intentionally not supported for actions.
- Download, trace, and upload paths are constrained to OpenClaw temp roots: `/tmp/openclaw{,/downloads,/uploads}` (fallback: `${os.tmpdir()}/openclaw/...`).
- `upload` can also set file inputs directly via `--input-ref` or `--element`.

View File

@@ -25,16 +25,6 @@ chromium-browser is already the newest version (2:1snap1-0ubuntu2).
This is NOT a real browser - it's just a wrapper.
Other common Linux launch failures:
- `The profile appears to be in use by another Chromium process` means Chrome
found stale `Singleton*` lock files in the managed profile directory. OpenClaw
removes those locks and retries once when the lock points at a dead or
different-host process.
- `Missing X server or $DISPLAY` means OpenClaw is trying to launch a visible
browser on a host without a desktop session. Use `browser.headless: true`,
start `Xvfb`, or run OpenClaw in a real desktop session.
### Solution 1: Install Google Chrome (Recommended)
Install the official Google Chrome `.deb` package, which is not sandboxed by snap:

View File

@@ -129,12 +129,6 @@ Browser settings live in `~/.openclaw/openclaw.json`.
// cdpUrl: "http://127.0.0.1:18792", // legacy single-profile override
remoteCdpTimeoutMs: 1500, // remote CDP HTTP timeout (ms)
remoteCdpHandshakeTimeoutMs: 3000, // remote CDP WebSocket handshake timeout (ms)
tabCleanup: {
enabled: true, // default: true
idleMinutes: 120, // set 0 to disable idle cleanup
maxTabsPerSession: 8, // set 0 to disable the per-session cap
sweepMinutes: 5,
},
defaultProfile: "openclaw",
color: "#FF4500",
headless: false,
@@ -143,12 +137,7 @@ Browser settings live in `~/.openclaw/openclaw.json`.
executablePath: "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser",
profiles: {
openclaw: { cdpPort: 18800, color: "#FF4500" },
work: {
cdpPort: 18801,
color: "#0066CC",
headless: true,
executablePath: "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
},
work: { cdpPort: 18801, color: "#0066CC", headless: true },
user: {
driver: "existing-session",
attachOnly: true,
@@ -173,7 +162,6 @@ Browser settings live in `~/.openclaw/openclaw.json`.
- Control service binds to loopback on a port derived from `gateway.port` (default `18791` = gateway + 2). Overriding `gateway.port` or `OPENCLAW_GATEWAY_PORT` shifts the derived ports in the same family.
- Local `openclaw` profiles auto-assign `cdpPort`/`cdpUrl`; set those only for remote CDP. `cdpUrl` defaults to the managed local CDP port when unset.
- `remoteCdpTimeoutMs` applies to remote (non-loopback) CDP HTTP reachability checks; `remoteCdpHandshakeTimeoutMs` applies to remote CDP WebSocket handshakes.
- `tabCleanup` is best-effort cleanup for tabs opened by primary-agent browser sessions. Subagent, cron, and ACP lifecycle cleanup still closes their explicit tracked tabs at session end; primary sessions keep active tabs reusable, then close idle or excess tracked tabs in the background.
</Accordion>
@@ -181,8 +169,6 @@ Browser settings live in `~/.openclaw/openclaw.json`.
- Browser navigation and open-tab are SSRF-guarded before navigation and best-effort re-checked on the final `http(s)` URL afterwards.
- In strict SSRF mode, remote CDP endpoint discovery and `/json/version` probes (`cdpUrl`) are checked too.
- Gateway/provider `HTTP_PROXY`, `HTTPS_PROXY`, `ALL_PROXY`, and `NO_PROXY` environment variables do not automatically proxy the OpenClaw-managed browser. Managed Chrome launches direct by default so provider proxy settings do not weaken browser SSRF checks.
- To proxy the managed browser itself, pass explicit Chrome proxy flags through `browser.extraArgs`, such as `--proxy-server=...` or `--proxy-pac-url=...`. Strict SSRF mode blocks explicit browser proxy routing unless private-network browser access is intentionally enabled.
- `browser.ssrfPolicy.dangerouslyAllowPrivateNetwork` is off by default; enable only when private-network browser access is intentionally trusted.
- `browser.ssrfPolicy.allowPrivateNetwork` remains supported as a legacy alias.
@@ -192,7 +178,6 @@ Browser settings live in `~/.openclaw/openclaw.json`.
- `attachOnly: true` means never launch a local browser; only attach if one is already running.
- `headless` can be set globally or per local managed profile. Per-profile values override `browser.headless`, so one locally launched profile can stay headless while another remains visible.
- `executablePath` can be set globally or per local managed profile. Per-profile values override `browser.executablePath`, so different managed profiles can launch different Chromium-based browsers.
- `color` (top-level and per-profile) tints the browser UI so you can see which profile is active.
- Default profile is `openclaw` (managed standalone). Use `defaultProfile: "user"` to opt into the signed-in user browser.
- Auto-detect order: system default browser if Chromium-based; otherwise Chrome → Brave → Edge → Chromium → Chrome Canary.
@@ -207,11 +192,10 @@ Browser settings live in `~/.openclaw/openclaw.json`.
If your **system default** browser is Chromium-based (Chrome/Brave/Edge/etc),
OpenClaw uses it automatically. Set `browser.executablePath` to override
auto-detection. `~` expands to your OS home directory:
auto-detection:
```bash
openclaw config set browser.executablePath "/usr/bin/google-chrome"
openclaw config set browser.profiles.work.executablePath "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
```
Or set it in config, per platform:
@@ -246,10 +230,6 @@ Or set it in config, per platform:
</Tab>
</Tabs>
Per-profile `executablePath` only affects local managed profiles that OpenClaw
launches. `existing-session` profiles attach to an already-running browser
instead, and remote CDP profiles use the browser behind `cdpUrl`.
## Local vs remote control
- **Local control (default):** the Gateway starts the loopback control service and can launch a local browser.
@@ -257,9 +237,6 @@ instead, and remote CDP profiles use the browser behind `cdpUrl`.
- **Remote CDP:** set `browser.profiles.<name>.cdpUrl` (or `browser.cdpUrl`) to
attach to a remote Chromium-based browser. In this case, OpenClaw will not launch a local browser.
- `headless` only affects local managed profiles that OpenClaw launches. It does not restart or change existing-session or remote CDP browsers.
- `executablePath` follows the same local managed profile rule. Changing it on a
running local managed profile marks that profile for restart/reconcile so the
next launch uses the new binary.
Stopping behavior differs by profile mode:
@@ -427,7 +404,7 @@ Defaults:
All control endpoints accept `?profile=<name>`; the CLI uses `--browser-profile`.
## Existing session via Chrome DevTools MCP
## Existing-session via Chrome DevTools MCP
OpenClaw can also attach to a running Chromium-based browser profile through the
official Chrome DevTools MCP server. This reuses the tabs and login state
@@ -529,7 +506,7 @@ Notes:
Compared to the managed `openclaw` profile, existing-session drivers are more constrained:
- **Screenshots** — page captures and `--ref` element captures work; CSS `--element` selectors do not. `--full-page` cannot combine with `--ref` or `--element`. Playwright is not required for page or ref-based element screenshots.
- **Actions** — `click`, `type`, `hover`, `scrollIntoView`, `drag`, and `select` require snapshot refs (no CSS selectors). `click-coords` clicks visible viewport coordinates and does not require a snapshot ref. `click` is left-button only. `type` does not support `slowly=true`; use `fill` or `press`. `press` does not support `delayMs`. `type`, `hover`, `scrollIntoView`, `drag`, `select`, `fill`, and `evaluate` do not support per-call timeouts. `select` accepts a single value.
- **Actions** — `click`, `type`, `hover`, `scrollIntoView`, `drag`, and `select` require snapshot refs (no CSS selectors). `click` is left-button only. `type` does not support `slowly=true`; use `fill` or `press`. `press` does not support `delayMs`. `type`, `hover`, `scrollIntoView`, `drag`, `select`, `fill`, and `evaluate` do not support per-call timeouts. `select` accepts a single value.
- **Wait / upload / dialog** — `wait --url` supports exact, substring, and glob patterns; `wait --load networkidle` is not supported. Upload hooks require `ref` or `inputRef`, one file at a time, no CSS `element`. Dialog hooks do not support timeout overrides.
- **Managed-only features** — batch actions, PDF export, download interception, and `responsebody` still require the managed browser path.

View File

@@ -102,13 +102,13 @@ automatically.
### Safe bins versus allowlist
| Topic | `tools.exec.safeBins` | Allowlist (`exec-approvals.json`) |
| ---------------- | ------------------------------------------------------ | ---------------------------------------------------------------------------------- |
| Goal | Auto-allow narrow stdin filters | Explicitly trust specific executables |
| Match type | Executable name + safe-bin argv policy | Resolved executable path glob, or bare command-name glob for PATH-invoked commands |
| Argument scope | Restricted by safe-bin profile and literal-token rules | Path match only; arguments are otherwise your responsibility |
| Typical examples | `head`, `tail`, `tr`, `wc` | `jq`, `python3`, `node`, `ffmpeg`, custom CLIs |
| Best use | Low-risk text transforms in pipelines | Any tool with broader behavior or side effects |
| Topic | `tools.exec.safeBins` | Allowlist (`exec-approvals.json`) |
| ---------------- | ------------------------------------------------------ | ------------------------------------------------------------ |
| Goal | Auto-allow narrow stdin filters | Explicitly trust specific executables |
| Match type | Executable name + safe-bin argv policy | Resolved executable path glob pattern |
| Argument scope | Restricted by safe-bin profile and literal-token rules | Path match only; arguments are otherwise your responsibility |
| Typical examples | `head`, `tail`, `tr`, `wc` | `jq`, `python3`, `node`, `ffmpeg`, custom CLIs |
| Best use | Low-risk text transforms in pipelines | Any tool with broader behavior or side effects |
Configuration location:

View File

@@ -248,17 +248,13 @@ This is defense-in-depth for interpreter loaders that do not map cleanly to one
## Allowlist (per agent)
Allowlists are **per agent**. If multiple agents exist, switch which agent youre
editing in the macOS app. Patterns are glob matches.
Patterns can be resolved binary path globs or bare command-name globs. Bare names
match only commands invoked through PATH, so `rg` can match `/opt/homebrew/bin/rg`
when the command is `rg`, but not `./rg` or `/tmp/rg`. Use a path glob when you
want to trust one specific binary location.
editing in the macOS app. Patterns are **case-insensitive glob matches**.
Patterns should resolve to **binary paths** (basename-only entries are ignored).
Legacy `agents.default` entries are migrated to `agents.main` on load.
Shell chains such as `echo ok && pwd` still need every top-level segment to satisfy allowlist rules.
Examples:
- `rg`
- `~/Projects/**/bin/peekaboo`
- `~/.local/bin/*`
- `/opt/homebrew/bin/rg`

View File

@@ -175,13 +175,11 @@ only path.
## Allowlist + safe bins
Manual allowlist enforcement matches resolved binary path globs and bare command-name
globs. Bare names match only commands invoked through PATH, so `rg` can match
`/opt/homebrew/bin/rg` when the command is `rg`, but not `./rg` or `/tmp/rg`.
When `security=allowlist`, shell commands are auto-allowed only if every pipeline
segment is allowlisted or a safe bin. Chaining (`;`, `&&`, `||`) and redirections
are rejected in allowlist mode unless every top-level segment satisfies the
allowlist (including safe bins). Redirections remain unsupported.
Manual allowlist enforcement matches **resolved binary paths only** (no basename matches). When
`security=allowlist`, shell commands are auto-allowed only if every pipeline segment is
allowlisted or a safe bin. Chaining (`;`, `&&`, `||`) and redirections are rejected in
allowlist mode unless every top-level segment satisfies the allowlist (including safe bins).
Redirections remain unsupported.
Durable `allow-always` trust does not bypass that rule: a chained command still requires every
top-level segment to match.

View File

@@ -368,22 +368,18 @@ public contract.
`api.registrationMode` tells a plugin why its entry is being loaded:
| Mode | Meaning |
| --------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `full` | Runtime activation. Register tools, hooks, services, commands, routes, and other live side effects. |
| `discovery` | Read-only capability discovery. Register providers and metadata; trusted plugin entry code may load, but skip live side effects. |
| `setup-only` | Channel setup metadata loading through a lightweight setup entry. |
| `setup-runtime` | Channel setup loading that also needs the runtime entry. |
| `cli-metadata` | CLI command metadata collection only. |
| Mode | Meaning |
| --------------- | ------------------------------------------------------------------------------------------------------ |
| `full` | Runtime activation. Register tools, hooks, services, commands, routes, and other live side effects. |
| `discovery` | Read-only capability discovery. Register providers and metadata, but skip expensive live side effects. |
| `setup-only` | Channel setup metadata loading through a lightweight setup entry. |
| `setup-runtime` | Channel setup loading that also needs the runtime entry. |
| `cli-metadata` | CLI command metadata collection only. |
Plugin entries that open sockets, databases, background workers, or long-lived
clients should guard those side effects with `api.registrationMode === "full"`.
Discovery loads are cached separately from activating loads and do not replace
the running Gateway registry. Discovery is non-activating, not import-free:
OpenClaw may evaluate the trusted plugin entry or channel plugin module to build
the snapshot. Keep module top levels lightweight and side-effect-free, and move
network clients, subprocesses, listeners, credential reads, and service startup
behind full-runtime paths.
the running Gateway registry.
Common registration methods:
@@ -420,16 +416,15 @@ Native Codex app-server runs bridge Codex-native tool events back into this
hook surface. Plugins can block native Codex tools through `before_tool_call`,
observe results through `after_tool_call`, and participate in Codex
`PermissionRequest` approvals. The bridge does not rewrite Codex-native tool
arguments yet. The exact Codex runtime support boundary lives in the
[Codex harness v1 support contract](/plugins/codex-harness#v1-support-contract).
arguments yet.
For full typed hook behavior, see [SDK overview](/plugins/sdk-overview#hook-decision-semantics).
For full typed hook behavior, see [SDK Overview](/plugins/sdk-overview#hook-decision-semantics).
## Related
- [Building plugins](/plugins/building-plugins) — create your own plugin
- [Plugin bundles](/plugins/bundles) — Codex/Claude/Cursor bundle compatibility
- [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
- [Community plugins](/plugins/community) — third-party listings
- [Building Plugins](/plugins/building-plugins) — create your own plugin
- [Plugin Bundles](/plugins/bundles) — Codex/Claude/Cursor bundle compatibility
- [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
- [Community Plugins](/plugins/community) — third-party listings

View File

@@ -203,11 +203,6 @@ Fields under `metadata.openclaw`:
- `primaryEnv` — env var name associated with `skills.entries.<name>.apiKey`.
- `install` — optional array of installer specs used by the macOS Skills UI (brew/node/go/uv/download).
Legacy `metadata.clawdbot` blocks are still accepted when
`metadata.openclaw` is absent, so older installed skills keep their dependency
gates and installer hints. New and updated skills should use
`metadata.openclaw`.
Note on sandboxing:
- `requires.bins` is checked on the **host** at skill load time.

View File

@@ -170,7 +170,7 @@ Auto-archive:
- Auto-archive applies equally to depth-1 and depth-2 sessions.
- Browser cleanup is separate from archive cleanup: tracked browser tabs/processes are best-effort closed when the run finishes, even if the transcript/session record is kept.
## Nested sub-agents
## Nested Sub-Agents
By default, sub-agents cannot spawn their own sub-agents (`maxSpawnDepth: 1`). You can enable one level of nesting by setting `maxSpawnDepth: 2`, which allows the **orchestrator pattern**: main → orchestrator sub-agent → worker sub-sub-agents.
@@ -214,11 +214,6 @@ Operational guidance:
- Start child work once and wait for completion events instead of building poll
loops around `sessions_list`, `sessions_history`, `/subagents list`, or
`exec` sleep commands.
- `sessions_list` and `/subagents list` keep child-session relationships focused
on live work: live children remain attached, ended children stay visible for a
short recent window, and stale store-only child links are ignored after their
freshness window. This prevents old `spawnedBy` / `parentSessionKey` metadata
from resurrecting ghost children after restart.
- If a child completion event arrives after you already sent the final answer,
the correct follow-up is the exact silent token `NO_REPLY` / `no_reply`.
@@ -348,18 +343,6 @@ Sub-agents use a dedicated in-process queue lane:
- Lane name: `subagent`
- Concurrency: `agents.defaults.subagents.maxConcurrent` (default `8`)
## Liveness and recovery
OpenClaw does not treat `endedAt` absence as permanent proof that a sub-agent
is still alive. Unended runs older than the stale-run window stop counting as
active/pending in `/subagents list`, status summaries, descendant completion
gating, and per-session concurrency checks.
After a gateway restart, stale unended restored runs are pruned unless their
child session is marked `abortedLastRun: true`. Those restart-aborted child
sessions remain recoverable through the sub-agent orphan recovery flow, which
sends a synthetic resume message before clearing the aborted marker.
## Stopping
- Sending `/stop` in the requester chat aborts the requester session and stops any active sub-agent runs spawned from it, cascading to nested children.

View File

@@ -231,32 +231,6 @@ Resolution order is `messages.tts.providers.xai.apiKey` -> `XAI_API_KEY`.
Current live voices are `ara`, `eve`, `leo`, `rex`, `sal`, and `una`; `eve` is
the default. `language` accepts a BCP-47 tag or `auto`.
### OpenRouter primary
```json5
{
messages: {
tts: {
auto: "always",
provider: "openrouter",
providers: {
openrouter: {
apiKey: "openrouter_api_key",
model: "hexgrad/kokoro-82m",
voice: "af_alloy",
responseFormat: "mp3",
},
},
},
},
}
```
OpenRouter TTS uses the same `OPENROUTER_API_KEY` path as the bundled
OpenRouter model provider. Resolution order is
`messages.tts.providers.openrouter.apiKey` ->
`models.providers.openrouter.apiKey` -> `OPENROUTER_API_KEY`.
### Gradium primary
```json5
@@ -347,15 +321,13 @@ Then run:
- `mode`: `"final"` (default) or `"all"` (includes tool/block replies).
- `provider`: speech provider id such as `"elevenlabs"`, `"google"`, `"gradium"`, `"microsoft"`, `"minimax"`, `"openai"`, `"vydra"`, or `"xai"` (fallback is automatic).
- If `provider` is **unset**, OpenClaw uses the first configured speech provider in registry auto-select order.
- Legacy `provider: "edge"` config is repaired by `openclaw doctor --fix` and
rewritten to `provider: "microsoft"`.
- Legacy `provider: "edge"` still works and is normalized to `microsoft`.
- `summaryModel`: optional cheap model for auto-summary; defaults to `agents.defaults.model.primary`.
- Accepts `provider/model` or a configured model alias.
- `modelOverrides`: allow the model to emit TTS directives (on by default).
- `allowProvider` defaults to `false` (provider switching is opt-in).
- `providers.<id>`: provider-owned settings keyed by speech provider id.
- Legacy direct provider blocks (`messages.tts.openai`, `messages.tts.elevenlabs`, `messages.tts.microsoft`, `messages.tts.edge`) are repaired by `openclaw doctor --fix`; committed config should use `messages.tts.providers.<id>`.
- Legacy `messages.tts.providers.edge` is also repaired by `openclaw doctor --fix`; committed config should use `messages.tts.providers.microsoft`.
- Legacy direct provider blocks (`messages.tts.openai`, `messages.tts.elevenlabs`, `messages.tts.microsoft`, `messages.tts.edge`) are auto-migrated to `messages.tts.providers.<id>` on load.
- `maxTextLength`: hard cap for TTS input (chars). `/tts audio` fails if exceeded.
- `timeoutMs`: request timeout (ms).
- `prefsPath`: override the local prefs JSON path (provider/limit/summary).
@@ -376,11 +348,9 @@ Then run:
- `providers.minimax.voiceId`: voice identifier (default `English_expressive_narrator`, env: `MINIMAX_TTS_VOICE_ID`).
- `providers.minimax.speed`: playback speed `0.5..2.0` (default 1.0).
- `providers.minimax.vol`: volume `(0, 10]` (default 1.0; must be greater than 0).
- `providers.minimax.pitch`: integer pitch shift `-12..12` (default 0). Fractional values are truncated before calling MiniMax T2A because the API rejects non-integer pitch values.
- `providers.minimax.pitch`: pitch shift `-12..12` (default 0).
- `providers.google.model`: Gemini TTS model (default `gemini-3.1-flash-tts-preview`).
- `providers.google.voiceName`: Gemini prebuilt voice name (default `Kore`; `voice` is also accepted).
- `providers.google.audioProfile`: natural-language style prompt prepended before the spoken text.
- `providers.google.speakerName`: optional speaker label prepended before the spoken text when your TTS prompt uses a named speaker.
- `providers.google.baseUrl`: override the Gemini API base URL. Only `https://generativelanguage.googleapis.com` is accepted.
- If `messages.tts.providers.google.apiKey` is omitted, TTS can reuse `models.providers.google.apiKey` before env fallback.
- `providers.gradium.baseUrl`: override Gradium API base URL (default `https://api.gradium.ai`).
@@ -391,12 +361,6 @@ Then run:
- `providers.xai.language`: BCP-47 language code or `auto` (default `en`).
- `providers.xai.responseFormat`: `mp3`, `wav`, `pcm`, `mulaw`, or `alaw` (default `mp3`).
- `providers.xai.speed`: provider-native speed override.
- `providers.openrouter.apiKey`: OpenRouter API key (env: `OPENROUTER_API_KEY`; can reuse `models.providers.openrouter.apiKey`).
- `providers.openrouter.baseUrl`: override the OpenRouter TTS base URL (default `https://openrouter.ai/api/v1`; legacy `https://openrouter.ai/v1` is normalized).
- `providers.openrouter.model`: OpenRouter TTS model id (default `hexgrad/kokoro-82m`; `modelId` is also accepted).
- `providers.openrouter.voice`: provider-specific voice id (default `af_alloy`; `voiceId` is also accepted).
- `providers.openrouter.responseFormat`: `mp3` or `pcm` (default `mp3`).
- `providers.openrouter.speed`: provider-native speed override.
- `providers.microsoft.enabled`: allow Microsoft speech usage (default `true`; no API key).
- `providers.microsoft.voice`: Microsoft neural voice name (e.g. `en-US-MichelleNeural`).
- `providers.microsoft.lang`: language code (e.g. `en-US`).
@@ -406,8 +370,7 @@ Then run:
- `providers.microsoft.saveSubtitles`: write JSON subtitles alongside the audio file.
- `providers.microsoft.proxy`: proxy URL for Microsoft speech requests.
- `providers.microsoft.timeoutMs`: request timeout override (ms).
- `edge.*`: legacy alias for the same Microsoft settings. Run
`openclaw doctor --fix` to rewrite persisted config to `providers.microsoft`.
- `edge.*`: legacy alias for the same Microsoft settings.
## Model-driven overrides (default on)
@@ -437,7 +400,7 @@ Available directive keys (when enabled):
- `model` (OpenAI TTS model, ElevenLabs model id, or MiniMax model) or `google_model` (Google TTS model)
- `stability`, `similarityBoost`, `style`, `speed`, `useSpeakerBoost`
- `vol` / `volume` (MiniMax volume, 0-10)
- `pitch` (MiniMax integer pitch, -12 to 12; fractional values are truncated before the MiniMax request)
- `pitch` (MiniMax pitch, -12 to 12)
- `applyTextNormalization` (`auto|on|off`)
- `languageCode` (ISO 639-1)
- `seed`
@@ -493,7 +456,7 @@ These override `messages.tts.*` for that host.
- 48kHz / 64kbps is a good voice message tradeoff.
- **Other channels**: MP3 (`mp3_44100_128` from ElevenLabs, `mp3` from OpenAI).
- 44.1kHz / 128kbps is the default balance for speech clarity.
- **MiniMax**: MP3 (`speech-2.8-hd` model, 32kHz sample rate) for normal audio attachments. For voice-note targets such as Feishu and Telegram, OpenClaw transcodes the MiniMax MP3 to 48kHz Opus with `ffmpeg` before delivery.
- **MiniMax**: MP3 (`speech-2.8-hd` model, 32kHz sample rate). Voice-note format not natively supported; use OpenAI or ElevenLabs for guaranteed Opus voice messages.
- **Google Gemini**: Gemini API TTS returns raw 24kHz PCM. OpenClaw wraps it as WAV for audio attachments and returns PCM directly for Talk/telephony. Native Opus voice-note format is not supported by this path.
- **Gradium**: WAV for audio attachments, Opus for voice-note targets, and `ulaw_8000` at 8 kHz for telephony.
- **xAI**: MP3 by default; `responseFormat` may be `mp3`, `wav`, `pcm`, `mulaw`, or `alaw`. OpenClaw uses xAI's batch REST TTS endpoint and returns a complete audio attachment; xAI's streaming TTS WebSocket is not used by this provider path. Native Opus voice-note format is not supported by this path.

View File

@@ -154,10 +154,6 @@ Cron jobs panel notes:
- `chat.history` responses are size-bounded for UI safety. When transcript entries are too large, Gateway may truncate long text fields, omit heavy metadata blocks, and replace oversized messages with a placeholder (`[chat.history omitted: message too large]`).
- Assistant/generated images are persisted as managed media references and served back through authenticated Gateway media URLs, so reloads do not depend on raw base64 image payloads staying in the chat history response.
- `chat.history` also strips display-only inline directive tags from visible assistant text (for example `[[reply_to_*]]` and `[[audio_as_voice]]`), plain-text tool-call XML payloads (including `<tool_call>...</tool_call>`, `<function_call>...</function_call>`, `<tool_calls>...</tool_calls>`, `<function_calls>...</function_calls>`, and truncated tool-call blocks), and leaked ASCII/full-width model control tokens, and omits assistant entries whose whole visible text is only the exact silent token `NO_REPLY` / `no_reply`.
- During an active send and the final history refresh, the chat view keeps local
optimistic user/assistant messages visible if `chat.history` briefly returns
an older snapshot; the canonical transcript replaces those local messages once
the Gateway history catches up.
- `chat.inject` appends an assistant note to the session transcript and broadcasts a `chat` event for UI-only updates (no agent run, no channel delivery).
- The chat header model and thinking pickers patch the active session immediately through `sessions.patch`; they are persistent session overrides, not one-turn-only send options.
- When fresh Gateway session usage reports show high context pressure, the chat
@@ -318,7 +314,7 @@ Trusted-proxy note:
device identity
- this does **not** extend to node-role Control UI sessions
- same-host loopback reverse proxies still do not satisfy trusted-proxy auth; see
[Trusted proxy auth](/gateway/trusted-proxy-auth)
[Trusted Proxy Auth](/gateway/trusted-proxy-auth)
See [Tailscale](/gateway/tailscale) for HTTPS setup guidance.

View File

@@ -133,7 +133,7 @@
},
"probeAgent": {
"label": "Health Probe Agent",
"help": "Agent id used for the embedded ACP runtime health probe. Defaults to the first `acp.allowedAgents` entry when that allowlist is set, otherwise to the runtime built-in probe agent (codex). Set this explicitly (for example `opencode` or `claude`) when the default probe agent is not installed or not authenticated, so the whole embedded ACP backend does not get marked unavailable.",
"help": "Agent id used for the embedded ACP runtime health probe. Defaults to the runtime built-in probe agent (codex). Set this to another configured ACP agent id (for example `opencode` or `claude`) when the default probe agent is not installed or not authenticated, so the whole embedded ACP backend does not get marked unavailable.",
"advanced": true
},
"mcpServers": {

View File

@@ -176,61 +176,6 @@ describe("createAcpxRuntimeService", () => {
await service.stop?.(ctx);
});
it("uses the first allowed ACP agent as the default probe agent", async () => {
const workspaceDir = await makeTempDir();
const ctx = createServiceContext(workspaceDir);
ctx.config = {
acp: {
allowedAgents: [" OpenCode ", "codex"],
},
};
const runtime = createMockRuntime();
const runtimeFactory = vi.fn(() => runtime as never);
const service = createAcpxRuntimeService({
runtimeFactory,
});
await service.start(ctx);
expect(runtimeFactory).toHaveBeenCalledWith(
expect.objectContaining({
pluginConfig: expect.objectContaining({
probeAgent: "opencode",
}),
}),
);
await service.stop?.(ctx);
});
it("keeps explicit probeAgent ahead of acp.allowedAgents", async () => {
const workspaceDir = await makeTempDir();
const ctx = createServiceContext(workspaceDir);
ctx.config = {
acp: {
allowedAgents: ["opencode"],
},
};
const runtime = createMockRuntime();
const runtimeFactory = vi.fn(() => runtime as never);
const service = createAcpxRuntimeService({
pluginConfig: { probeAgent: "codex" },
runtimeFactory,
});
await service.start(ctx);
expect(runtimeFactory).toHaveBeenCalledWith(
expect.objectContaining({
pluginConfig: expect.objectContaining({
probeAgent: "codex",
}),
}),
);
await service.stop?.(ctx);
});
it("warns when legacy compatibility config is explicitly ignored", async () => {
const workspaceDir = await makeTempDir();
const ctx = createServiceContext(workspaceDir);
@@ -276,32 +221,6 @@ describe("createAcpxRuntimeService", () => {
await service.stop?.(ctx);
});
it("formats non-string doctor details without losing object payloads", async () => {
const workspaceDir = await makeTempDir();
const ctx = createServiceContext(workspaceDir);
const runtime = createMockRuntime({
doctor: async () => ({
ok: false,
message: "probe failed",
details: [{ code: "ACP_CLOSED", agent: "codex" }, new Error("stdin closed")],
}),
isHealthy: () => false,
});
const service = createAcpxRuntimeService({
runtimeFactory: () => runtime as never,
});
await service.start(ctx);
await vi.waitFor(() => {
expect(ctx.logger.warn).toHaveBeenCalledWith(
'embedded acpx runtime backend probe failed: probe failed ({"code":"ACP_CLOSED","agent":"codex"}; stdin closed)',
);
});
await service.stop?.(ctx);
});
it("can skip the embedded runtime backend via env", async () => {
process.env.OPENCLAW_SKIP_ACPX_RUNTIME = "1";
const workspaceDir = await makeTempDir();

View File

@@ -1,5 +1,4 @@
import fs from "node:fs/promises";
import { inspect } from "node:util";
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
import type {
AcpRuntime,
@@ -80,54 +79,11 @@ function warnOnIgnoredLegacyCompatibilityConfig(params: {
);
}
function formatDoctorDetail(detail: unknown): string | null {
if (!detail) {
return null;
}
if (typeof detail === "string") {
return detail.trim() || null;
}
if (detail instanceof Error) {
return formatErrorMessage(detail);
}
if (typeof detail === "object") {
try {
return JSON.stringify(detail) ?? inspect(detail, { breakLength: Infinity, depth: 3 });
} catch {
return inspect(detail, { breakLength: Infinity, depth: 3 });
}
}
if (
typeof detail === "number" ||
typeof detail === "boolean" ||
typeof detail === "bigint" ||
typeof detail === "symbol"
) {
return detail.toString();
}
return inspect(detail, { breakLength: Infinity, depth: 3 });
}
function formatDoctorFailureMessage(report: { message: string; details?: unknown[] }): string {
const detailText = report.details?.map(formatDoctorDetail).filter(Boolean).join("; ").trim();
function formatDoctorFailureMessage(report: { message: string; details?: string[] }): string {
const detailText = report.details?.filter(Boolean).join("; ").trim();
return detailText ? `${report.message} (${detailText})` : report.message;
}
function normalizeProbeAgent(value: string | undefined): string | undefined {
const normalized = value?.trim().toLowerCase();
return normalized ? normalized : undefined;
}
function resolveAllowedAgentsProbeAgent(ctx: OpenClawPluginServiceContext): string | undefined {
for (const agent of ctx.config.acp?.allowedAgents ?? []) {
const normalized = normalizeProbeAgent(agent);
if (normalized) {
return normalized;
}
}
return undefined;
}
export function createAcpxRuntimeService(
params: CreateAcpxRuntimeServiceParams = {},
): OpenClawPluginService {
@@ -146,12 +102,8 @@ export function createAcpxRuntimeService(
rawConfig: params.pluginConfig,
workspaceDir: ctx.workspaceDir,
});
const effectiveBasePluginConfig: ResolvedAcpxPluginConfig = {
...basePluginConfig,
probeAgent: basePluginConfig.probeAgent ?? resolveAllowedAgentsProbeAgent(ctx),
};
const pluginConfig = await prepareAcpxCodexAuthConfig({
pluginConfig: effectiveBasePluginConfig,
pluginConfig: basePluginConfig,
stateDir: ctx.stateDir,
logger: ctx.logger,
});

View File

@@ -566,7 +566,6 @@ describe("active-memory plugin", () => {
},
},
},
cleanupBundleMcpOnRunEnd: true,
});
});
@@ -654,9 +653,6 @@ describe("active-memory plugin", () => {
"You receive conversation context, including the user's latest message.",
);
expect(runParams?.prompt).toContain("Use only memory_search and memory_get.");
expect(runParams?.prompt).toContain(
"When searching for preference or habit recall, use a permissive memory_search threshold before deciding that no useful memory exists.",
);
expect(runParams?.prompt).toContain(
"If the user is directly asking about favorites, preferences, habits, routines, or personal facts, treat that as a strong recall signal.",
);

View File

@@ -787,7 +787,6 @@ function buildRecallPrompt(params: {
"Your job is to search memory and return only the most relevant memory context for that model.",
"You receive conversation context, including the user's latest message.",
"Use only memory_search and memory_get.",
"When searching for preference or habit recall, use a permissive memory_search threshold before deciding that no useful memory exists.",
"Do not answer the user directly.",
`Prompt style: ${params.config.promptStyle}.`,
...buildPromptStyleLines(params.config.promptStyle),
@@ -1685,7 +1684,6 @@ async function runRecallSubagent(params: {
thinkLevel: params.config.thinking,
reasoningLevel: "off",
silentExpected: true,
cleanupBundleMcpOnRunEnd: true,
abortSignal: params.abortSignal,
});
if (params.abortSignal?.aborted) {

View File

@@ -1,65 +0,0 @@
import { describe, expect, it, vi } from "vitest";
import { hasAwsCredentials } from "./embedding-provider.js";
describe("hasAwsCredentials", () => {
it("accepts static AWS key credentials without loading the credential chain", async () => {
const loadCredentialProvider = vi.fn();
await expect(
hasAwsCredentials(
{
AWS_ACCESS_KEY_ID: "access-key",
AWS_SECRET_ACCESS_KEY: "secret-key",
},
loadCredentialProvider,
),
).resolves.toBe(true);
expect(loadCredentialProvider).not.toHaveBeenCalled();
});
it("accepts the Bedrock bearer token without loading the credential chain", async () => {
const loadCredentialProvider = vi.fn();
await expect(
hasAwsCredentials(
{
AWS_BEARER_TOKEN_BEDROCK: "bearer-token",
},
loadCredentialProvider,
),
).resolves.toBe(true);
expect(loadCredentialProvider).not.toHaveBeenCalled();
});
it("requires AWS profile credentials to resolve through the credential chain", async () => {
const loadCredentialProvider = vi.fn().mockResolvedValue({
defaultProvider: () => async () => ({ accessKeyId: "resolved-access-key" }),
});
await expect(hasAwsCredentials({ AWS_PROFILE: "work" }, loadCredentialProvider)).resolves.toBe(
true,
);
expect(loadCredentialProvider).toHaveBeenCalledOnce();
});
it("rejects AWS profile markers when the credential chain cannot resolve", async () => {
const loadCredentialProvider = vi.fn().mockResolvedValue({
defaultProvider: () => async () => {
throw new Error("Could not load credentials from any providers");
},
});
await expect(
hasAwsCredentials({ AWS_PROFILE: "missing" }, loadCredentialProvider),
).resolves.toBe(false);
});
it("returns false when the AWS credential provider package is unavailable", async () => {
const loadCredentialProvider = vi.fn().mockResolvedValue(null);
await expect(hasAwsCredentials({}, loadCredentialProvider)).resolves.toBe(false);
});
});

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