mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-17 03:28:57 +08:00
Compare commits
2 Commits
feat/plugi
...
fix/codeql
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
16a367787c | ||
|
|
a07aaee3ec |
90
.github/workflows/ci.yml
vendored
90
.github/workflows/ci.yml
vendored
@@ -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-
|
||||
|
||||
|
||||
3
.github/workflows/install-smoke.yml
vendored
3
.github/workflows/install-smoke.yml
vendored
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
129
CHANGELOG.md
129
CHANGELOG.md
@@ -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
|
||||
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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("\\")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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`() {
|
||||
|
||||
@@ -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`() {
|
||||
|
||||
@@ -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"])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 网关"
|
||||
|
||||
@@ -86,8 +86,6 @@ This fires ~5–6 times per month instead of 0–1 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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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/`:
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 pi‑ai 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 pi‑ai 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 pi‑ai 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 pi‑ai 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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -1165,7 +1165,6 @@
|
||||
"plugins/hooks",
|
||||
"plugins/sdk-channel-plugins",
|
||||
"plugins/sdk-provider-plugins",
|
||||
"plugins/compatibility",
|
||||
"plugins/sdk-migration"
|
||||
]
|
||||
},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 don’t share one context by default:
|
||||
|
||||
@@ -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`).
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 app’s environment.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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`.
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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`,
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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" } } },
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 it’s 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.
|
||||
|
||||
|
||||
@@ -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)**
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -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`.
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -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 you’re
|
||||
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`
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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.",
|
||||
);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user