Merge branch 'main' into codex/cortex-openclaw-integration
This commit is contained in:
commit
811294e2ed
4
.github/labeler.yml
vendored
4
.github/labeler.yml
vendored
@ -314,3 +314,7 @@
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "extensions/xiaomi/**"
|
||||
"extensions: fal":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "extensions/fal/**"
|
||||
|
||||
80
.github/workflows/ci.yml
vendored
80
.github/workflows/ci.yml
vendored
@ -304,6 +304,86 @@ jobs:
|
||||
- name: Enforce safe external URL opening policy
|
||||
run: pnpm lint:ui:no-raw-window-open
|
||||
|
||||
plugin-extension-boundary:
|
||||
name: "plugin-extension-boundary"
|
||||
needs: [docs-scope, changed-scope]
|
||||
if: needs.docs-scope.outputs.docs_only != 'true' && needs.changed-scope.outputs.run_node == 'true'
|
||||
runs-on: blacksmith-16vcpu-ubuntu-2404
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: false
|
||||
|
||||
- name: Setup Node environment
|
||||
uses: ./.github/actions/setup-node-env
|
||||
with:
|
||||
install-bun: "false"
|
||||
use-sticky-disk: "false"
|
||||
|
||||
- name: Run plugin extension boundary guard
|
||||
run: pnpm run lint:plugins:no-extension-imports
|
||||
|
||||
web-search-provider-boundary:
|
||||
name: "web-search-provider-boundary"
|
||||
needs: [docs-scope, changed-scope]
|
||||
if: needs.docs-scope.outputs.docs_only != 'true' && needs.changed-scope.outputs.run_node == 'true'
|
||||
runs-on: blacksmith-16vcpu-ubuntu-2404
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: false
|
||||
|
||||
- name: Setup Node environment
|
||||
uses: ./.github/actions/setup-node-env
|
||||
with:
|
||||
install-bun: "false"
|
||||
use-sticky-disk: "false"
|
||||
|
||||
- name: Run web search provider boundary guard
|
||||
run: pnpm run lint:web-search-provider-boundaries
|
||||
|
||||
extension-src-outside-plugin-sdk-boundary:
|
||||
name: "extension-src-outside-plugin-sdk-boundary"
|
||||
needs: [docs-scope, changed-scope]
|
||||
if: needs.docs-scope.outputs.docs_only != 'true' && needs.changed-scope.outputs.run_node == 'true'
|
||||
runs-on: blacksmith-16vcpu-ubuntu-2404
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: false
|
||||
|
||||
- name: Setup Node environment
|
||||
uses: ./.github/actions/setup-node-env
|
||||
with:
|
||||
install-bun: "false"
|
||||
use-sticky-disk: "false"
|
||||
|
||||
- name: Run extension src boundary guard
|
||||
run: pnpm run lint:extensions:no-src-outside-plugin-sdk
|
||||
|
||||
extension-plugin-sdk-internal-boundary:
|
||||
name: "extension-plugin-sdk-internal-boundary"
|
||||
needs: [docs-scope, changed-scope]
|
||||
if: needs.docs-scope.outputs.docs_only != 'true' && needs.changed-scope.outputs.run_node == 'true'
|
||||
runs-on: blacksmith-16vcpu-ubuntu-2404
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: false
|
||||
|
||||
- name: Setup Node environment
|
||||
uses: ./.github/actions/setup-node-env
|
||||
with:
|
||||
install-bun: "false"
|
||||
use-sticky-disk: "false"
|
||||
|
||||
- name: Run extension plugin-sdk-internal guard
|
||||
run: pnpm run lint:extensions:no-plugin-sdk-internal
|
||||
|
||||
build-smoke:
|
||||
name: "build-smoke"
|
||||
needs: [docs-scope, changed-scope]
|
||||
|
||||
214
.github/workflows/plugin-npm-release.yml
vendored
Normal file
214
.github/workflows/plugin-npm-release.yml
vendored
Normal file
@ -0,0 +1,214 @@
|
||||
name: Plugin NPM Release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- ".github/workflows/plugin-npm-release.yml"
|
||||
- "extensions/**"
|
||||
- "package.json"
|
||||
- "scripts/lib/plugin-npm-release.ts"
|
||||
- "scripts/plugin-npm-publish.sh"
|
||||
- "scripts/plugin-npm-release-check.ts"
|
||||
- "scripts/plugin-npm-release-plan.ts"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
publish_scope:
|
||||
description: Publish the selected plugins or all publishable plugins from the ref
|
||||
required: true
|
||||
default: selected
|
||||
type: choice
|
||||
options:
|
||||
- selected
|
||||
- all-publishable
|
||||
ref:
|
||||
description: Commit SHA on main to publish from (copy from the preview run)
|
||||
required: true
|
||||
type: string
|
||||
plugins:
|
||||
description: Comma-separated plugin package names to publish when publish_scope=selected
|
||||
required: false
|
||||
type: string
|
||||
|
||||
concurrency:
|
||||
group: plugin-npm-release-${{ github.event_name == 'workflow_dispatch' && inputs.ref || github.sha }}
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
||||
NODE_VERSION: "24.x"
|
||||
PNPM_VERSION: "10.23.0"
|
||||
|
||||
jobs:
|
||||
preview_plugins_npm:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
outputs:
|
||||
ref_sha: ${{ steps.ref.outputs.sha }}
|
||||
has_candidates: ${{ steps.plan.outputs.has_candidates }}
|
||||
candidate_count: ${{ steps.plan.outputs.candidate_count }}
|
||||
matrix: ${{ steps.plan.outputs.matrix }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ github.event_name == 'workflow_dispatch' && inputs.ref || github.sha }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Node environment
|
||||
uses: ./.github/actions/setup-node-env
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
pnpm-version: ${{ env.PNPM_VERSION }}
|
||||
install-bun: "false"
|
||||
use-sticky-disk: "false"
|
||||
|
||||
- name: Resolve checked-out ref
|
||||
id: ref
|
||||
run: echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Validate ref is on main
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git fetch --no-tags origin +refs/heads/main:refs/remotes/origin/main
|
||||
git merge-base --is-ancestor HEAD origin/main
|
||||
|
||||
- name: Validate publishable plugin metadata
|
||||
env:
|
||||
PUBLISH_SCOPE: ${{ github.event_name == 'workflow_dispatch' && inputs.publish_scope || '' }}
|
||||
RELEASE_PLUGINS: ${{ github.event_name == 'workflow_dispatch' && inputs.plugins || '' }}
|
||||
BASE_REF: ${{ github.event_name != 'workflow_dispatch' && github.event.before || '' }}
|
||||
HEAD_REF: ${{ steps.ref.outputs.sha }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ -n "${PUBLISH_SCOPE}" ]]; then
|
||||
release_args=(--selection-mode "${PUBLISH_SCOPE}")
|
||||
if [[ -n "${RELEASE_PLUGINS}" ]]; then
|
||||
release_args+=(--plugins "${RELEASE_PLUGINS}")
|
||||
fi
|
||||
pnpm release:plugins:npm:check -- "${release_args[@]}"
|
||||
elif [[ -n "${BASE_REF}" ]]; then
|
||||
pnpm release:plugins:npm:check -- --base-ref "${BASE_REF}" --head-ref "${HEAD_REF}"
|
||||
else
|
||||
pnpm release:plugins:npm:check
|
||||
fi
|
||||
|
||||
- name: Resolve plugin release plan
|
||||
id: plan
|
||||
env:
|
||||
PUBLISH_SCOPE: ${{ github.event_name == 'workflow_dispatch' && inputs.publish_scope || '' }}
|
||||
RELEASE_PLUGINS: ${{ github.event_name == 'workflow_dispatch' && inputs.plugins || '' }}
|
||||
BASE_REF: ${{ github.event_name != 'workflow_dispatch' && github.event.before || '' }}
|
||||
HEAD_REF: ${{ steps.ref.outputs.sha }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
mkdir -p .local
|
||||
if [[ -n "${PUBLISH_SCOPE}" ]]; then
|
||||
plan_args=(--selection-mode "${PUBLISH_SCOPE}")
|
||||
if [[ -n "${RELEASE_PLUGINS}" ]]; then
|
||||
plan_args+=(--plugins "${RELEASE_PLUGINS}")
|
||||
fi
|
||||
node --import tsx scripts/plugin-npm-release-plan.ts "${plan_args[@]}" > .local/plugin-npm-release-plan.json
|
||||
elif [[ -n "${BASE_REF}" ]]; then
|
||||
node --import tsx scripts/plugin-npm-release-plan.ts --base-ref "${BASE_REF}" --head-ref "${HEAD_REF}" > .local/plugin-npm-release-plan.json
|
||||
else
|
||||
node --import tsx scripts/plugin-npm-release-plan.ts > .local/plugin-npm-release-plan.json
|
||||
fi
|
||||
|
||||
cat .local/plugin-npm-release-plan.json
|
||||
|
||||
candidate_count="$(jq -r '.candidates | length' .local/plugin-npm-release-plan.json)"
|
||||
has_candidates="false"
|
||||
if [[ "${candidate_count}" != "0" ]]; then
|
||||
has_candidates="true"
|
||||
fi
|
||||
matrix_json="$(jq -c '.candidates' .local/plugin-npm-release-plan.json)"
|
||||
|
||||
{
|
||||
echo "candidate_count=${candidate_count}"
|
||||
echo "has_candidates=${has_candidates}"
|
||||
echo "matrix=${matrix_json}"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
echo "Plugin release candidates:"
|
||||
jq -r '.candidates[]? | "- \(.packageName)@\(.version) [\(.publishTag)] from \(.packageDir)"' .local/plugin-npm-release-plan.json
|
||||
|
||||
echo "Already published / skipped:"
|
||||
jq -r '.skippedPublished[]? | "- \(.packageName)@\(.version)"' .local/plugin-npm-release-plan.json
|
||||
|
||||
preview_plugin_pack:
|
||||
needs: preview_plugins_npm
|
||||
if: needs.preview_plugins_npm.outputs.has_candidates == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
plugin: ${{ fromJson(needs.preview_plugins_npm.outputs.matrix) }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ needs.preview_plugins_npm.outputs.ref_sha }}
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Setup Node environment
|
||||
uses: ./.github/actions/setup-node-env
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
pnpm-version: ${{ env.PNPM_VERSION }}
|
||||
install-bun: "false"
|
||||
use-sticky-disk: "false"
|
||||
install-deps: "false"
|
||||
|
||||
- name: Preview publish command
|
||||
run: bash scripts/plugin-npm-publish.sh --dry-run "${{ matrix.plugin.packageDir }}"
|
||||
|
||||
- name: Preview npm pack contents
|
||||
working-directory: ${{ matrix.plugin.packageDir }}
|
||||
run: npm pack --dry-run --json --ignore-scripts
|
||||
|
||||
publish_plugins_npm:
|
||||
needs: [preview_plugins_npm, preview_plugin_pack]
|
||||
if: github.event_name == 'workflow_dispatch' && needs.preview_plugins_npm.outputs.has_candidates == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
environment: npm-release
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
plugin: ${{ fromJson(needs.preview_plugins_npm.outputs.matrix) }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ needs.preview_plugins_npm.outputs.ref_sha }}
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Setup Node environment
|
||||
uses: ./.github/actions/setup-node-env
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
pnpm-version: ${{ env.PNPM_VERSION }}
|
||||
install-bun: "false"
|
||||
use-sticky-disk: "false"
|
||||
install-deps: "false"
|
||||
|
||||
- name: Ensure version is not already published
|
||||
env:
|
||||
PACKAGE_NAME: ${{ matrix.plugin.packageName }}
|
||||
PACKAGE_VERSION: ${{ matrix.plugin.version }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if npm view "${PACKAGE_NAME}@${PACKAGE_VERSION}" version >/dev/null 2>&1; then
|
||||
echo "${PACKAGE_NAME}@${PACKAGE_VERSION} is already published on npm."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Publish
|
||||
run: bash scripts/plugin-npm-publish.sh --publish "${{ matrix.plugin.packageDir }}"
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@ -142,3 +142,6 @@ ui/src/ui/__screenshots__
|
||||
ui/src/ui/views/__screenshots__
|
||||
ui/.vitest-attachments
|
||||
docs/superpowers
|
||||
|
||||
# Deprecated changelog fragment workflow
|
||||
changelog/fragments/
|
||||
|
||||
3
.npmrc
3
.npmrc
@ -1 +1,4 @@
|
||||
# pnpm build-script allowlist lives in package.json -> pnpm.onlyBuiltDependencies.
|
||||
# TS 7 native-preview fails to resolve packages reliably from pnpm's isolated linker.
|
||||
# Keep the workspace on a hoisted layout so pnpm check/build stay stable.
|
||||
node-linker=hoisted
|
||||
|
||||
@ -114,6 +114,7 @@
|
||||
- Never add `@ts-nocheck` and do not disable `no-explicit-any`; fix root causes and update Oxlint/Oxfmt config only when required.
|
||||
- Dynamic import guardrail: do not mix `await import("x")` and static `import ... from "x"` for the same module in production code paths. If you need lazy loading, create a dedicated `*.runtime.ts` boundary (that re-exports from `x`) and dynamically import that boundary from lazy callers only.
|
||||
- Dynamic import verification: after refactors that touch lazy-loading/module boundaries, run `pnpm build` and check for `[INEFFECTIVE_DYNAMIC_IMPORT]` warnings before submitting.
|
||||
- Extension SDK self-import guardrail: inside an extension package, do not import that same extension via `openclaw/plugin-sdk/<extension>` from production files. Route internal imports through a local barrel such as `./api.ts` or `./runtime-api.ts`, and keep the `plugin-sdk/<extension>` path as the external contract only.
|
||||
- Never share class behavior via prototype mutation (`applyPrototypeMixins`, `Object.defineProperty` on `.prototype`, or exporting `Class.prototype` for merges). Use explicit inheritance/composition (`A extends B extends C`) or helper composition so TypeScript can typecheck.
|
||||
- If this pattern is needed, stop and get explicit approval before shipping; default behavior is to split/refactor into an explicit class hierarchy and keep members strongly typed.
|
||||
- In tests, prefer per-instance stubs over prototype mutation (`SomeClass.prototype.method = ...`) unless a test explicitly documents why prototype-level patching is required.
|
||||
@ -139,7 +140,7 @@
|
||||
- Do not set test workers above 16; tried already.
|
||||
- If local Vitest runs cause memory pressure (common on non-Mac-Studio hosts), use `OPENCLAW_TEST_PROFILE=low OPENCLAW_TEST_SERIAL_GATEWAY=1 pnpm test` for land/gate runs.
|
||||
- Live tests (real keys): `CLAWDBOT_LIVE_TEST=1 pnpm test:live` (OpenClaw-only) or `LIVE=1 pnpm test:live` (includes provider live tests). Docker: `pnpm test:docker:live-models`, `pnpm test:docker:live-gateway`. Onboarding Docker E2E: `pnpm test:docker:onboard`.
|
||||
- Full kit + what’s covered: `docs/testing.md`.
|
||||
- Full kit + what’s covered: `docs/help/testing.md`.
|
||||
- Changelog: user-facing changes only; no internal/meta notes (version alignment, appcast reminders, release process).
|
||||
- Changelog placement: in the active version block, append new entries to the end of the target section (`### Changes` or `### Fixes`); do not insert new entries at the top of a section.
|
||||
- Changelog attribution: use at most one contributor mention per line; prefer `Thanks @author` and do not also add `by @author` on the same entry.
|
||||
@ -280,7 +281,7 @@
|
||||
- If staged+unstaged diffs are formatting-only, auto-resolve without asking.
|
||||
- If commit/push already requested, auto-stage and include formatting-only follow-ups in the same commit (or a tiny follow-up commit if needed), no extra confirmation.
|
||||
- Only ask when changes are semantic (logic/data/behavior).
|
||||
- Lobster seam: use the shared CLI palette in `src/terminal/palette.ts` (no hardcoded colors); apply palette to onboarding/config prompts and other TTY UI output as needed.
|
||||
- Lobster palette: use the shared CLI palette in `src/terminal/palette.ts` (no hardcoded colors); apply palette to onboarding/config prompts and other TTY UI output as needed.
|
||||
- **Multi-agent safety:** focus reports on your edits; avoid guard-rail disclaimers unless truly blocked; when multiple agents touch the same file, continue if safe; end with a brief “other files present” note only if relevant.
|
||||
- Bug investigations: read source code of relevant npm dependencies and all related local code before concluding; aim for high-confidence root cause.
|
||||
- Code style: add brief comments for tricky logic; keep files under ~500 LOC when feasible (split/refactor as needed).
|
||||
|
||||
29
CHANGELOG.md
29
CHANGELOG.md
@ -35,13 +35,19 @@ Docs: https://docs.openclaw.ai
|
||||
- Models/OpenAI: add native forward-compat support for `gpt-5.4-mini` and `gpt-5.4-nano` in the OpenAI provider catalog, runtime resolution, and reasoning capability gates. Thanks @vincentkoc.
|
||||
- Plugins/bundles: make enabled bundle MCP servers expose runnable tools in embedded Pi, and default relative bundle MCP launches to the bundle root so marketplace bundles like Context7 work through Pi instead of stopping at config import.
|
||||
- Scope message SecretRef resolution and harden doctor/status paths. (#48728) Thanks @joshavant.
|
||||
- Plugins/testing: add a public `openclaw/plugin-sdk/testing` seam for plugin-author test helpers, and move bundled-extension-only test bridges out of `extensions/` into private repo test helpers.
|
||||
- Plugins/testing: add a public `openclaw/plugin-sdk/testing` surface for plugin-author test helpers, and move bundled-extension-only test bridges out of `extensions/` into private repo test helpers.
|
||||
- Plugins/Chutes: add a bundled Chutes provider with plugin-owned OAuth/API-key auth, dynamic model discovery, and default-on extension wiring. (#41416) Thanks @Veightor.
|
||||
- Plugins/binding: add `onConversationBindingResolved(...)` so plugins can react immediately after bind approvals or denies without blocking channel interaction acknowledgements. (#48678) Thanks @huntharo.
|
||||
- CLI/config: expand `config set` with SecretRef and provider builder modes, JSON/batch assignment support, and `--dry-run` validation with structured JSON output. (#49296) Thanks @joshavant.
|
||||
- Control UI/appearance: unify theme border radii across Claw, Knot, and Dash, and add a Roundness slider to the Appearance settings so users can adjust corner radius from sharp to fully rounded. Thanks @BunsDev.
|
||||
- Control UI/chat: add an expand-to-canvas button on assistant chat bubbles and in-app session navigation from Sessions and Cron views. Thanks @BunsDev.
|
||||
- Plugins/context engines: expose `delegateCompactionToRuntime(...)` on the public plugin SDK, refactor the legacy engine to use the shared helper, and clarify `ownsCompaction` delegation semantics for non-owning engines. (#49061) Thanks @jalehman.
|
||||
- Plugins/MiniMax: add MiniMax-M2.7 and MiniMax-M2.7-highspeed models and update the default model from M2.5 to M2.7. (#49691) Thanks @liyuan97.
|
||||
|
||||
### Fixes
|
||||
|
||||
- Plugins/bundler TDZ: fix `RESERVED_COMMANDS` temporal dead zone error that prevented device-pair, phone-control, and talk-voice plugins from registering when the bundler placed the commands module after call sites in the same output chunk. Thanks @BunsDev.
|
||||
- Plugins/imports: fix stale googlechat runtime-api import paths and signal SDK circular re-exports broken by recent plugin-sdk refactors. Thanks @BunsDev.
|
||||
- Google auth/Node 25: patch `gaxios` to use native fetch without injecting `globalThis.window`, while translating proxy and mTLS transport settings so Google Vertex and Google Chat auth keep working on Node 25. (#47914) Thanks @pdd-cli.
|
||||
- Gateway/startup: load bundled channel plugins from compiled `dist/extensions` entries in built installs, so gateway boot no longer recompiles bundled extension TypeScript on every startup and WhatsApp-class cold starts drop back to seconds instead of tens of seconds or worse. (#47560) Thanks @ngutman.
|
||||
- Plugins/context engines: enforce owner-aware context-engine registration on both loader and public SDK paths so plugins cannot spoof privileged ownership, claim the core `legacy` engine id, or overwrite an existing engine id through direct SDK imports. (#47595) Thanks @vincentkoc.
|
||||
@ -122,6 +128,12 @@ Docs: https://docs.openclaw.ai
|
||||
- Telegram/network: preserve sticky IPv4 fallback state across polling restarts so hosts with unstable IPv6 to `api.telegram.org` stop re-triggering repeated Telegram timeouts after each restart. (#48282) Thanks @yassinebkr.
|
||||
- Plugins/subagents: forward per-run provider and model overrides through gateway plugin subagent dispatch so plugin-launched agent delegations honor explicit model selection again. (#48277) Thanks @jalehman.
|
||||
- Agents/compaction: write minimal boundary summaries for empty preparations while keeping split-turn prefixes on the normal path, so no-summarizable-message sessions stop retriggering the safeguard loop. (#42215) thanks @lml2468.
|
||||
- Models/chat commands: keep `/model ...@YYYYMMDD` version suffixes intact by default, but still honor matching stored numeric auth-profile overrides for the same provider. (#48896) Thanks @Alix-007.
|
||||
- Gateway/channels: serialize per-account channel startup so overlapping starts do not boot the same provider twice, preventing MS Teams `EADDRINUSE` crash loops during startup and restart. (#49583) Thanks @sudie-codes.
|
||||
- Tests/OpenAI Codex auth: align login expectations with the default `gpt-5.4` model so CI coverage stays consistent with the current OpenAI Codex default. (#44367) Thanks @jrrcdev.
|
||||
|
||||
### Fixes
|
||||
|
||||
- Agents/bootstrap warnings: move bootstrap truncation warnings out of the system prompt and into the per-turn prompt body so prompt-cache reuse stays stable when truncation warnings appear or disappear. (#48753) Thanks @scoootscooob and @obviyus.
|
||||
- Telegram/DM topic session keys: route named-account DM topics through the same per-account base session key across inbound messages, native commands, and session-state lookups so `/status` and thread recovery stop creating phantom `agent:main:main:thread:...` sessions. (#48204) Thanks @vincentkoc.
|
||||
- macOS/node service startup: use `openclaw node start/stop --json` from the Mac app instead of the removed `openclaw service node ...` command shape, so current CLI installs expose the full node exec surface again. (#46843) Fixes #43171. Thanks @Br1an67.
|
||||
@ -131,12 +143,27 @@ Docs: https://docs.openclaw.ai
|
||||
- Mattermost/DM send: retry transient direct-channel creation failures for DM deliveries, with configurable backoff and per-request timeout. (#42398) Thanks @JonathanJing.
|
||||
- Telegram/network: unify API and media fetches under the same sticky IPv4 and pinned-IP fallback chain, and re-validate pinned override addresses against SSRF policy. (#49148) Thanks @obviyus.
|
||||
- Agents/prompt composition: append bootstrap truncation warnings to the current-turn prompt and add regression coverage for stable system-prompt cache invariants. (#49237) Thanks @scoootscooob.
|
||||
- Gateway/auth: add regression coverage that keeps device-less trusted-proxy Control UI sessions off privileged pairing approval RPCs. Thanks @vincentkoc.
|
||||
- Plugins/runtime-api: pin extension runtime-api export surfaces with explicit guardrail coverage so future surface creep becomes a deliberate diff. Thanks @vincentkoc.
|
||||
- Telegram/security: add regression coverage proving pinned fallback host overrides stay bound to Telegram and delegate non-matching hostnames back to the original lookup path. Thanks @vincentkoc.
|
||||
- Secrets/exec refs: require explicit `--allow-exec` for `secrets apply` write plans that contain exec SecretRefs/providers, and align audit/configure/apply dry-run behavior to skip exec checks unless opted in to prevent unexpected command side effects. (#49417) Thanks @restriction and @joshavant.
|
||||
- Tools/image generation: add bundled fal image generation support so `image_generate` can target `fal/*` models with `FAL_KEY`, including single-image edit flows via FLUX image-to-image. Thanks @vincentkoc.
|
||||
- xAI/web search: add missing Grok credential metadata so the bundled provider registration type-checks again. (#49472) thanks @scoootscooob.
|
||||
- Signal/runtime API: re-export `SignalAccountConfig` so Signal account resolution type-checks again. (#49470) Thanks @scoootscooob.
|
||||
- Google Chat/runtime API: thin the private runtime barrel onto the curated public SDK surface while keeping public Google Chat exports intact. (#49504) Thanks @scoootscooob.
|
||||
- WhatsApp: stabilize inbound monitor and setup tests (#50007) Thanks @joshavant.
|
||||
- Matrix: make onboarding status runtime-safe (#49995) Thanks @joshavant.
|
||||
|
||||
### Breaking
|
||||
|
||||
- Skills/image generation: remove the bundled `nano-banana-pro` skill wrapper. Use `agents.defaults.imageGenerationModel.primary: "google/gemini-3-pro-image-preview"` for the native Nano Banana-style path instead.
|
||||
|
||||
- Browser/Chrome MCP: remove the legacy Chrome extension relay path, bundled extension assets, `driver: "extension"`, and `browser.relayBindHost`. Run `openclaw doctor --fix` to migrate host-local browser config to `existing-session` / `user`; Docker, headless, sandbox, and remote browser flows still use raw CDP. (#47893) Thanks @vincentkoc.
|
||||
- Plugins/runtime: remove the public `openclaw/extension-api` surface with no compatibility shim. Bundled plugins must use injected runtime for host-side operations (for example `api.runtime.agent.runEmbeddedPiAgent`) and any remaining direct imports must come from narrow `openclaw/plugin-sdk/*` subpaths instead of the monolithic SDK root.
|
||||
- Tools/image generation: standardize the stock image create/edit path on the core `image_generate` tool. The old `nano-banana-pro` docs/examples are gone; if you previously copied that sample-skill config, switch to `agents.defaults.imageGenerationModel` for built-in image generation or install a separate third-party skill explicitly.
|
||||
- Skills/image generation: remove the bundled `nano-banana-pro` skill wrapper. Use `agents.defaults.imageGenerationModel.primary: "google/gemini-3-pro-image-preview"` for the native Nano Banana-style path instead.
|
||||
- Plugins/message discovery: require `ChannelMessageActionAdapter.describeMessageTool(...)` for shared `message` tool discovery. The legacy `listActions`, `getCapabilities`, and `getToolSchema` adapter methods are removed. Plugin authors should migrate message discovery to `describeMessageTool(...)` and keep channel-specific action runtime code inside the owning plugin package. Thanks @gumadeiras.
|
||||
- Exec/env sandbox: block build-tool JVM injection (`MAVEN_OPTS`, `SBT_OPTS`, `GRADLE_OPTS`, `ANT_OPTS`), glibc tunable exploitation (`GLIBC_TUNABLES`), and .NET dependency resolution hijack (`DOTNET_ADDITIONAL_DEPS`) from the host exec environment, and restrict Gradle init script redirect (`GRADLE_USER_HOME`) as an override-only block so user-configured Gradle homes still propagate. (#49702)
|
||||
|
||||
## 2026.3.13
|
||||
|
||||
|
||||
@ -83,7 +83,8 @@ Welcome to the lobster tank! 🦞
|
||||
|
||||
1. **Bugs & small fixes** → Open a PR!
|
||||
2. **New features / architecture** → Start a [GitHub Discussion](https://github.com/openclaw/openclaw/discussions) or ask in Discord first
|
||||
3. **Questions** → Discord [#help](https://discord.com/channels/1456350064065904867/1459642797895319552) / [#users-helping-users](https://discord.com/channels/1456350064065904867/1459007081603403828)
|
||||
3. **Test/CI-only PRs for known `main` failures** → Don't open a PR, the Maintainer team is already tracking it and such PRs will be closed automatically. If you've spotted a _new_ regression not yet shown in main CI, report it as an issue first.
|
||||
4. **Questions** → Discord [#help](https://discord.com/channels/1456350064065904867/1459642797895319552) / [#users-helping-users](https://discord.com/channels/1456350064065904867/1459007081603403828)
|
||||
|
||||
## Before You PR
|
||||
|
||||
@ -96,6 +97,7 @@ Welcome to the lobster tank! 🦞
|
||||
- For targeted shared-surface work, use `pnpm test:contracts:channels` or `pnpm test:contracts:plugins`
|
||||
- If you changed broader runtime behavior, still run the relevant wider lanes (`pnpm test:extensions`, `pnpm test:channels`, or `pnpm test`) before asking for review
|
||||
- If you have access to Codex, run `codex review --base origin/main` locally before opening or updating your PR. Treat this as the current highest standard of AI review, even if GitHub Codex review also runs.
|
||||
- Do not submit test or CI-config fixes for failures already red on `main` CI. If a failure is already visible in the [main branch CI runs](https://github.com/openclaw/openclaw/actions), it's a known issue the Maintainer team is tracking, and a PR that only addresses those failures will be closed automatically. If you spot a _new_ regression not yet shown in main CI, report it as an issue first.
|
||||
- Ensure CI checks pass
|
||||
- Keep PRs focused (one thing per PR; do not mix unrelated concerns)
|
||||
- Describe what & why
|
||||
|
||||
@ -325,7 +325,7 @@ If you plan to build/run companion apps, follow the platform runbooks below.
|
||||
- WebChat + debug tools.
|
||||
- Remote gateway control over SSH.
|
||||
|
||||
Note: signed builds required for macOS permissions to stick across rebuilds (see `docs/mac/permissions.md`).
|
||||
Note: signed builds required for macOS permissions to stick across rebuilds (see [macOS Permissions](https://docs.openclaw.ai/platforms/mac/permissions)).
|
||||
|
||||
### iOS node (optional)
|
||||
|
||||
|
||||
@ -28,11 +28,18 @@ enum HostEnvSecurityPolicy {
|
||||
"_JAVA_OPTIONS",
|
||||
"JDK_JAVA_OPTIONS",
|
||||
"PYTHONBREAKPOINT",
|
||||
"DOTNET_STARTUP_HOOKS"
|
||||
"DOTNET_STARTUP_HOOKS",
|
||||
"DOTNET_ADDITIONAL_DEPS",
|
||||
"GLIBC_TUNABLES",
|
||||
"MAVEN_OPTS",
|
||||
"SBT_OPTS",
|
||||
"GRADLE_OPTS",
|
||||
"ANT_OPTS"
|
||||
]
|
||||
|
||||
static let blockedOverrideKeys: Set<String> = [
|
||||
"HOME",
|
||||
"GRADLE_USER_HOME",
|
||||
"ZDOTDIR",
|
||||
"GIT_SSH_COMMAND",
|
||||
"GIT_SSH",
|
||||
|
||||
@ -1 +0,0 @@
|
||||
- tests: align OpenAI Codex auth login expectations with the `gpt-5.4` default model to prevent stale CI failures. (#44367) thanks @jrrcdev
|
||||
@ -1 +0,0 @@
|
||||
- runner: infer canonical tool names from malformed `toolCallId` variants (e.g. `functionsread3`, `functionswrite4`) when allowlist is present, preventing `Tool not found` regressions in strict routers.
|
||||
@ -15230,7 +15230,7 @@
|
||||
"network"
|
||||
],
|
||||
"label": "Feishu",
|
||||
"help": "飞书/Lark enterprise messaging.",
|
||||
"help": "飞书/Lark enterprise messaging with doc/wiki/drive tools.",
|
||||
"hasChildren": true
|
||||
},
|
||||
{
|
||||
@ -17232,7 +17232,7 @@
|
||||
"network"
|
||||
],
|
||||
"label": "Google Chat",
|
||||
"help": "Google Workspace Chat app with HTTP webhook.",
|
||||
"help": "Google Workspace Chat app via HTTP webhooks.",
|
||||
"hasChildren": true
|
||||
},
|
||||
{
|
||||
@ -22069,7 +22069,7 @@
|
||||
"network"
|
||||
],
|
||||
"label": "Matrix",
|
||||
"help": "open protocol; configure a homeserver + access token.",
|
||||
"help": "open protocol; install the plugin to enable.",
|
||||
"hasChildren": true
|
||||
},
|
||||
{
|
||||
@ -26190,7 +26190,7 @@
|
||||
"network"
|
||||
],
|
||||
"label": "Nostr",
|
||||
"help": "Decentralized DMs via Nostr relays (NIP-04)",
|
||||
"help": "Decentralized protocol; encrypted DMs via NIP-04.",
|
||||
"hasChildren": true
|
||||
},
|
||||
{
|
||||
@ -30798,7 +30798,7 @@
|
||||
"network"
|
||||
],
|
||||
"label": "Synology Chat",
|
||||
"help": "Connect your Synology NAS Chat to OpenClaw",
|
||||
"help": "Connect your Synology NAS Chat to OpenClaw with full agent capabilities.",
|
||||
"hasChildren": true
|
||||
},
|
||||
{
|
||||
@ -34814,7 +34814,7 @@
|
||||
"network"
|
||||
],
|
||||
"label": "Tlon",
|
||||
"help": "Decentralized messaging on Urbit",
|
||||
"help": "decentralized messaging on Urbit; install the plugin to enable.",
|
||||
"hasChildren": true
|
||||
},
|
||||
{
|
||||
@ -44903,6 +44903,16 @@
|
||||
"tags": [],
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
"path": "models.providers.*.models.*.compat.nativeWebSearchTool",
|
||||
"kind": "core",
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [],
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
"path": "models.providers.*.models.*.compat.requiresAssistantAfterToolResult",
|
||||
"kind": "core",
|
||||
@ -45023,6 +45033,26 @@
|
||||
"tags": [],
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
"path": "models.providers.*.models.*.compat.toolCallArgumentsEncoding",
|
||||
"kind": "core",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [],
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
"path": "models.providers.*.models.*.compat.toolSchemaProfile",
|
||||
"kind": "core",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [],
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
"path": "models.providers.*.models.*.contextWindow",
|
||||
"kind": "core",
|
||||
@ -46155,6 +46185,52 @@
|
||||
],
|
||||
"label": "@openclaw/brave-plugin Config",
|
||||
"help": "Plugin-defined config payload for brave.",
|
||||
"hasChildren": true
|
||||
},
|
||||
{
|
||||
"path": "plugins.entries.brave.config.webSearch",
|
||||
"kind": "plugin",
|
||||
"type": "object",
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [],
|
||||
"hasChildren": true
|
||||
},
|
||||
{
|
||||
"path": "plugins.entries.brave.config.webSearch.apiKey",
|
||||
"kind": "plugin",
|
||||
"type": [
|
||||
"object",
|
||||
"string"
|
||||
],
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": true,
|
||||
"tags": [
|
||||
"auth",
|
||||
"security"
|
||||
],
|
||||
"label": "Brave Search API Key",
|
||||
"help": "Brave Search API key (fallback: BRAVE_API_KEY env var).",
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
"path": "plugins.entries.brave.config.webSearch.mode",
|
||||
"kind": "plugin",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"enumValues": [
|
||||
"web",
|
||||
"llm-context"
|
||||
],
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [
|
||||
"advanced"
|
||||
],
|
||||
"label": "Brave Search Mode",
|
||||
"help": "Brave Search mode: web or llm-context.",
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
@ -47690,6 +47766,127 @@
|
||||
"help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.",
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
"path": "plugins.entries.fal",
|
||||
"kind": "plugin",
|
||||
"type": "object",
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [
|
||||
"advanced"
|
||||
],
|
||||
"label": "@openclaw/fal-provider",
|
||||
"help": "OpenClaw fal provider plugin (plugin: fal)",
|
||||
"hasChildren": true
|
||||
},
|
||||
{
|
||||
"path": "plugins.entries.fal.config",
|
||||
"kind": "plugin",
|
||||
"type": "object",
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [
|
||||
"advanced"
|
||||
],
|
||||
"label": "@openclaw/fal-provider Config",
|
||||
"help": "Plugin-defined config payload for fal.",
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
"path": "plugins.entries.fal.enabled",
|
||||
"kind": "plugin",
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [
|
||||
"advanced"
|
||||
],
|
||||
"label": "Enable @openclaw/fal-provider",
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
"path": "plugins.entries.fal.hooks",
|
||||
"kind": "plugin",
|
||||
"type": "object",
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [
|
||||
"advanced"
|
||||
],
|
||||
"label": "Plugin Hook Policy",
|
||||
"help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.",
|
||||
"hasChildren": true
|
||||
},
|
||||
{
|
||||
"path": "plugins.entries.fal.hooks.allowPromptInjection",
|
||||
"kind": "plugin",
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [
|
||||
"access"
|
||||
],
|
||||
"label": "Allow Prompt Injection Hooks",
|
||||
"help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.",
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
"path": "plugins.entries.fal.subagent",
|
||||
"kind": "plugin",
|
||||
"type": "object",
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [
|
||||
"advanced"
|
||||
],
|
||||
"label": "Plugin Subagent Policy",
|
||||
"help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.",
|
||||
"hasChildren": true
|
||||
},
|
||||
{
|
||||
"path": "plugins.entries.fal.subagent.allowedModels",
|
||||
"kind": "plugin",
|
||||
"type": "array",
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [
|
||||
"access"
|
||||
],
|
||||
"label": "Plugin Subagent Allowed Models",
|
||||
"help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.",
|
||||
"hasChildren": true
|
||||
},
|
||||
{
|
||||
"path": "plugins.entries.fal.subagent.allowedModels.*",
|
||||
"kind": "plugin",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [],
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
"path": "plugins.entries.fal.subagent.allowModelOverride",
|
||||
"kind": "plugin",
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [
|
||||
"access"
|
||||
],
|
||||
"label": "Allow Plugin Subagent Model Override",
|
||||
"help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.",
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
"path": "plugins.entries.feishu",
|
||||
"kind": "plugin",
|
||||
@ -47837,6 +48034,48 @@
|
||||
],
|
||||
"label": "@openclaw/firecrawl-plugin Config",
|
||||
"help": "Plugin-defined config payload for firecrawl.",
|
||||
"hasChildren": true
|
||||
},
|
||||
{
|
||||
"path": "plugins.entries.firecrawl.config.webSearch",
|
||||
"kind": "plugin",
|
||||
"type": "object",
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [],
|
||||
"hasChildren": true
|
||||
},
|
||||
{
|
||||
"path": "plugins.entries.firecrawl.config.webSearch.apiKey",
|
||||
"kind": "plugin",
|
||||
"type": [
|
||||
"object",
|
||||
"string"
|
||||
],
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": true,
|
||||
"tags": [
|
||||
"auth",
|
||||
"security"
|
||||
],
|
||||
"label": "Firecrawl Search API Key",
|
||||
"help": "Firecrawl API key for web search (fallback: FIRECRAWL_API_KEY env var).",
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
"path": "plugins.entries.firecrawl.config.webSearch.baseUrl",
|
||||
"kind": "plugin",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [
|
||||
"advanced"
|
||||
],
|
||||
"label": "Firecrawl Search Base URL",
|
||||
"help": "Firecrawl Search base URL override.",
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
@ -48079,6 +48318,48 @@
|
||||
],
|
||||
"label": "@openclaw/google-plugin Config",
|
||||
"help": "Plugin-defined config payload for google.",
|
||||
"hasChildren": true
|
||||
},
|
||||
{
|
||||
"path": "plugins.entries.google.config.webSearch",
|
||||
"kind": "plugin",
|
||||
"type": "object",
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [],
|
||||
"hasChildren": true
|
||||
},
|
||||
{
|
||||
"path": "plugins.entries.google.config.webSearch.apiKey",
|
||||
"kind": "plugin",
|
||||
"type": [
|
||||
"object",
|
||||
"string"
|
||||
],
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": true,
|
||||
"tags": [
|
||||
"auth",
|
||||
"security"
|
||||
],
|
||||
"label": "Gemini Search API Key",
|
||||
"help": "Gemini API key for Google Search grounding (fallback: GEMINI_API_KEY env var).",
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
"path": "plugins.entries.google.config.webSearch.model",
|
||||
"kind": "plugin",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [
|
||||
"models"
|
||||
],
|
||||
"label": "Gemini Search Model",
|
||||
"help": "Gemini model override for web search grounding.",
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
@ -50456,6 +50737,62 @@
|
||||
],
|
||||
"label": "@openclaw/moonshot-provider Config",
|
||||
"help": "Plugin-defined config payload for moonshot.",
|
||||
"hasChildren": true
|
||||
},
|
||||
{
|
||||
"path": "plugins.entries.moonshot.config.webSearch",
|
||||
"kind": "plugin",
|
||||
"type": "object",
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [],
|
||||
"hasChildren": true
|
||||
},
|
||||
{
|
||||
"path": "plugins.entries.moonshot.config.webSearch.apiKey",
|
||||
"kind": "plugin",
|
||||
"type": [
|
||||
"object",
|
||||
"string"
|
||||
],
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": true,
|
||||
"tags": [
|
||||
"auth",
|
||||
"security"
|
||||
],
|
||||
"label": "Kimi Search API Key",
|
||||
"help": "Moonshot/Kimi API key (fallback: KIMI_API_KEY or MOONSHOT_API_KEY env var).",
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
"path": "plugins.entries.moonshot.config.webSearch.baseUrl",
|
||||
"kind": "plugin",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [
|
||||
"advanced"
|
||||
],
|
||||
"label": "Kimi Search Base URL",
|
||||
"help": "Kimi base URL override.",
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
"path": "plugins.entries.moonshot.config.webSearch.model",
|
||||
"kind": "plugin",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [
|
||||
"models"
|
||||
],
|
||||
"label": "Kimi Search Model",
|
||||
"help": "Kimi model override.",
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
@ -52075,6 +52412,62 @@
|
||||
],
|
||||
"label": "@openclaw/perplexity-plugin Config",
|
||||
"help": "Plugin-defined config payload for perplexity.",
|
||||
"hasChildren": true
|
||||
},
|
||||
{
|
||||
"path": "plugins.entries.perplexity.config.webSearch",
|
||||
"kind": "plugin",
|
||||
"type": "object",
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [],
|
||||
"hasChildren": true
|
||||
},
|
||||
{
|
||||
"path": "plugins.entries.perplexity.config.webSearch.apiKey",
|
||||
"kind": "plugin",
|
||||
"type": [
|
||||
"object",
|
||||
"string"
|
||||
],
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": true,
|
||||
"tags": [
|
||||
"auth",
|
||||
"security"
|
||||
],
|
||||
"label": "Perplexity API Key",
|
||||
"help": "Perplexity or OpenRouter API key for web search.",
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
"path": "plugins.entries.perplexity.config.webSearch.baseUrl",
|
||||
"kind": "plugin",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [
|
||||
"advanced"
|
||||
],
|
||||
"label": "Perplexity Base URL",
|
||||
"help": "Optional Perplexity/OpenRouter chat-completions base URL override.",
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
"path": "plugins.entries.perplexity.config.webSearch.model",
|
||||
"kind": "plugin",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [
|
||||
"models"
|
||||
],
|
||||
"label": "Perplexity Model",
|
||||
"help": "Optional Sonar/OpenRouter model override.",
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
@ -56010,6 +56403,62 @@
|
||||
],
|
||||
"label": "@openclaw/xai-plugin Config",
|
||||
"help": "Plugin-defined config payload for xai.",
|
||||
"hasChildren": true
|
||||
},
|
||||
{
|
||||
"path": "plugins.entries.xai.config.webSearch",
|
||||
"kind": "plugin",
|
||||
"type": "object",
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [],
|
||||
"hasChildren": true
|
||||
},
|
||||
{
|
||||
"path": "plugins.entries.xai.config.webSearch.apiKey",
|
||||
"kind": "plugin",
|
||||
"type": [
|
||||
"object",
|
||||
"string"
|
||||
],
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": true,
|
||||
"tags": [
|
||||
"auth",
|
||||
"security"
|
||||
],
|
||||
"label": "Grok Search API Key",
|
||||
"help": "xAI API key for Grok web search (fallback: XAI_API_KEY env var).",
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
"path": "plugins.entries.xai.config.webSearch.inlineCitations",
|
||||
"kind": "plugin",
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [
|
||||
"advanced"
|
||||
],
|
||||
"label": "Inline Citations",
|
||||
"help": "Include inline markdown citations in Grok responses.",
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
"path": "plugins.entries.xai.config.webSearch.model",
|
||||
"kind": "plugin",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [
|
||||
"models"
|
||||
],
|
||||
"label": "Grok Search Model",
|
||||
"help": "Grok model override for web search.",
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
@ -62780,8 +63229,6 @@
|
||||
"security",
|
||||
"tools"
|
||||
],
|
||||
"label": "Brave Search API Key",
|
||||
"help": "Brave Search API key (fallback: BRAVE_API_KEY env var).",
|
||||
"hasChildren": true
|
||||
},
|
||||
{
|
||||
@ -62824,6 +63271,63 @@
|
||||
"tags": [],
|
||||
"hasChildren": true
|
||||
},
|
||||
{
|
||||
"path": "tools.web.search.brave.apiKey",
|
||||
"kind": "core",
|
||||
"type": [
|
||||
"object",
|
||||
"string"
|
||||
],
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": true,
|
||||
"tags": [
|
||||
"auth",
|
||||
"security",
|
||||
"tools"
|
||||
],
|
||||
"hasChildren": true
|
||||
},
|
||||
{
|
||||
"path": "tools.web.search.brave.apiKey.id",
|
||||
"kind": "core",
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [],
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
"path": "tools.web.search.brave.apiKey.provider",
|
||||
"kind": "core",
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [],
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
"path": "tools.web.search.brave.apiKey.source",
|
||||
"kind": "core",
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [],
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
"path": "tools.web.search.brave.baseUrl",
|
||||
"kind": "core",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [],
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
"path": "tools.web.search.brave.mode",
|
||||
"kind": "core",
|
||||
@ -62831,11 +63335,17 @@
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [
|
||||
"tools"
|
||||
],
|
||||
"label": "Brave Search Mode",
|
||||
"help": "Brave Search mode: \"web\" (URL results) or \"llm-context\" (pre-extracted page content for LLM grounding).",
|
||||
"tags": [],
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
"path": "tools.web.search.brave.model",
|
||||
"kind": "core",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [],
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
@ -62893,8 +63403,6 @@
|
||||
"security",
|
||||
"tools"
|
||||
],
|
||||
"label": "Firecrawl Search API Key",
|
||||
"help": "Firecrawl API key for web search (fallback: FIRECRAWL_API_KEY env var).",
|
||||
"hasChildren": true
|
||||
},
|
||||
{
|
||||
@ -62934,11 +63442,17 @@
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [
|
||||
"tools"
|
||||
],
|
||||
"label": "Firecrawl Search Base URL",
|
||||
"help": "Firecrawl Search base URL override (default: \"https://api.firecrawl.dev\").",
|
||||
"tags": [],
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
"path": "tools.web.search.firecrawl.model",
|
||||
"kind": "core",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [],
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
@ -62966,8 +63480,6 @@
|
||||
"security",
|
||||
"tools"
|
||||
],
|
||||
"label": "Gemini Search API Key",
|
||||
"help": "Gemini API key for Google Search grounding (fallback: GEMINI_API_KEY env var).",
|
||||
"hasChildren": true
|
||||
},
|
||||
{
|
||||
@ -63000,6 +63512,16 @@
|
||||
"tags": [],
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
"path": "tools.web.search.gemini.baseUrl",
|
||||
"kind": "core",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [],
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
"path": "tools.web.search.gemini.model",
|
||||
"kind": "core",
|
||||
@ -63007,12 +63529,7 @@
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [
|
||||
"models",
|
||||
"tools"
|
||||
],
|
||||
"label": "Gemini Search Model",
|
||||
"help": "Gemini model override (default: \"gemini-2.5-flash\").",
|
||||
"tags": [],
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
@ -63040,8 +63557,6 @@
|
||||
"security",
|
||||
"tools"
|
||||
],
|
||||
"label": "Grok Search API Key",
|
||||
"help": "Grok (xAI) API key (fallback: XAI_API_KEY env var).",
|
||||
"hasChildren": true
|
||||
},
|
||||
{
|
||||
@ -63074,6 +63589,16 @@
|
||||
"tags": [],
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
"path": "tools.web.search.grok.baseUrl",
|
||||
"kind": "core",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [],
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
"path": "tools.web.search.grok.inlineCitations",
|
||||
"kind": "core",
|
||||
@ -63091,12 +63616,7 @@
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [
|
||||
"models",
|
||||
"tools"
|
||||
],
|
||||
"label": "Grok Search Model",
|
||||
"help": "Grok model override (default: \"grok-4-1-fast\").",
|
||||
"tags": [],
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
@ -63124,8 +63644,6 @@
|
||||
"security",
|
||||
"tools"
|
||||
],
|
||||
"label": "Kimi Search API Key",
|
||||
"help": "Moonshot/Kimi API key (fallback: KIMI_API_KEY or MOONSHOT_API_KEY env var).",
|
||||
"hasChildren": true
|
||||
},
|
||||
{
|
||||
@ -63165,11 +63683,7 @@
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [
|
||||
"tools"
|
||||
],
|
||||
"label": "Kimi Search Base URL",
|
||||
"help": "Kimi base URL override (default: \"https://api.moonshot.ai/v1\").",
|
||||
"tags": [],
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
@ -63179,12 +63693,7 @@
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [
|
||||
"models",
|
||||
"tools"
|
||||
],
|
||||
"label": "Kimi Search Model",
|
||||
"help": "Kimi model override (default: \"moonshot-v1-128k\").",
|
||||
"tags": [],
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
@ -63227,8 +63736,6 @@
|
||||
"security",
|
||||
"tools"
|
||||
],
|
||||
"label": "Perplexity API Key",
|
||||
"help": "Perplexity or OpenRouter API key (fallback: PERPLEXITY_API_KEY or OPENROUTER_API_KEY env var). Direct Perplexity keys default to the Search API; OpenRouter keys use Sonar chat completions.",
|
||||
"hasChildren": true
|
||||
},
|
||||
{
|
||||
@ -63268,11 +63775,7 @@
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [
|
||||
"tools"
|
||||
],
|
||||
"label": "Perplexity Base URL",
|
||||
"help": "Optional Perplexity/OpenRouter chat-completions base URL override. Setting this opts Perplexity into the legacy Sonar/OpenRouter compatibility path.",
|
||||
"tags": [],
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
@ -63282,12 +63785,7 @@
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [
|
||||
"models",
|
||||
"tools"
|
||||
],
|
||||
"label": "Perplexity Model",
|
||||
"help": "Optional Sonar/OpenRouter model override (default: \"perplexity/sonar-pro\"). Setting this opts Perplexity into the legacy chat-completions compatibility path.",
|
||||
"tags": [],
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
@ -63301,7 +63799,7 @@
|
||||
"tools"
|
||||
],
|
||||
"label": "Web Search Provider",
|
||||
"help": "Search provider (\"brave\", \"firecrawl\", \"gemini\", \"grok\", \"kimi\", or \"perplexity\"). Auto-detected from available API keys if omitted.",
|
||||
"help": "Search provider id. Auto-detected from available API keys if omitted.",
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
@ -63386,7 +63884,7 @@
|
||||
"advanced"
|
||||
],
|
||||
"label": "Accent Color",
|
||||
"help": "Primary accent/seam color used by UI surfaces for emphasis, badges, and visual identity cues. Use high-contrast values that remain readable across light/dark themes.",
|
||||
"help": "Primary accent color used by UI surfaces for emphasis, badges, and visual identity cues. Use high-contrast values that remain readable across light/dark themes.",
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
{"generatedBy":"scripts/generate-config-doc-baseline.ts","recordType":"meta","totalPaths":5476}
|
||||
{"generatedBy":"scripts/generate-config-doc-baseline.ts","recordType":"meta","totalPaths":5518}
|
||||
{"recordType":"path","path":"acp","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"ACP","help":"ACP runtime controls for enabling dispatch, selecting backends, constraining allowed agent targets, and tuning streamed turn projection behavior.","hasChildren":true}
|
||||
{"recordType":"path","path":"acp.allowedAgents","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"ACP Allowed Agents","help":"Allowlist of ACP target agent ids permitted for ACP runtime sessions. Empty means no additional allowlist restriction.","hasChildren":true}
|
||||
{"recordType":"path","path":"acp.allowedAgents.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
@ -1352,7 +1352,7 @@
|
||||
{"recordType":"path","path":"channels.discord.voice.tts.provider","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.discord.voice.tts.summaryModel","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.discord.voice.tts.timeoutMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.feishu","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Feishu","help":"飞书/Lark enterprise messaging.","hasChildren":true}
|
||||
{"recordType":"path","path":"channels.feishu","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Feishu","help":"飞书/Lark enterprise messaging with doc/wiki/drive tools.","hasChildren":true}
|
||||
{"recordType":"path","path":"channels.feishu.accounts","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"channels.feishu.accounts.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"channels.feishu.accounts.*.actions","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
@ -1532,7 +1532,7 @@
|
||||
{"recordType":"path","path":"channels.feishu.webhookHost","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.feishu.webhookPath","kind":"channel","type":"string","required":true,"defaultValue":"/feishu/events","deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.feishu.webhookPort","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.googlechat","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Google Chat","help":"Google Workspace Chat app with HTTP webhook.","hasChildren":true}
|
||||
{"recordType":"path","path":"channels.googlechat","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Google Chat","help":"Google Workspace Chat app via HTTP webhooks.","hasChildren":true}
|
||||
{"recordType":"path","path":"channels.googlechat.accounts","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"channels.googlechat.accounts.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"channels.googlechat.accounts.*.actions","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
@ -1980,7 +1980,7 @@
|
||||
{"recordType":"path","path":"channels.line.secretFile","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.line.tokenFile","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.line.webhookPath","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.matrix","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Matrix","help":"open protocol; configure a homeserver + access token.","hasChildren":true}
|
||||
{"recordType":"path","path":"channels.matrix","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Matrix","help":"open protocol; install the plugin to enable.","hasChildren":true}
|
||||
{"recordType":"path","path":"channels.matrix.accessToken","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.matrix.accounts","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"channels.matrix.accounts.*","kind":"channel","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
@ -2362,7 +2362,7 @@
|
||||
{"recordType":"path","path":"channels.nextcloud-talk.webhookPath","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.nextcloud-talk.webhookPort","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.nextcloud-talk.webhookPublicUrl","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.nostr","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Nostr","help":"Decentralized DMs via Nostr relays (NIP-04)","hasChildren":true}
|
||||
{"recordType":"path","path":"channels.nostr","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Nostr","help":"Decentralized protocol; encrypted DMs via NIP-04.","hasChildren":true}
|
||||
{"recordType":"path","path":"channels.nostr.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"channels.nostr.allowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.nostr.defaultAccount","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
@ -2779,7 +2779,7 @@
|
||||
{"recordType":"path","path":"channels.slack.userToken.source","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.slack.userTokenReadOnly","kind":"channel","type":"boolean","required":true,"defaultValue":true,"deprecated":false,"sensitive":false,"tags":["auth","channels","network","security"],"label":"Slack User Token Read Only","help":"When true, treat configured Slack user token usage as read-only helper behavior where possible. Keep enabled if you only need supplemental reads without user-context writes.","hasChildren":false}
|
||||
{"recordType":"path","path":"channels.slack.webhookPath","kind":"channel","type":"string","required":true,"defaultValue":"/slack/events","deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.synology-chat","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Synology Chat","help":"Connect your Synology NAS Chat to OpenClaw","hasChildren":true}
|
||||
{"recordType":"path","path":"channels.synology-chat","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Synology Chat","help":"Connect your Synology NAS Chat to OpenClaw with full agent capabilities.","hasChildren":true}
|
||||
{"recordType":"path","path":"channels.synology-chat.*","kind":"channel","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.telegram","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Telegram","help":"simplest way to get started — register a bot with @BotFather and get going.","hasChildren":true}
|
||||
{"recordType":"path","path":"channels.telegram.accounts","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
@ -3139,7 +3139,7 @@
|
||||
{"recordType":"path","path":"channels.telegram.webhookSecret.provider","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.telegram.webhookSecret.source","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.telegram.webhookUrl","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.tlon","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Tlon","help":"Decentralized messaging on Urbit","hasChildren":true}
|
||||
{"recordType":"path","path":"channels.tlon","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Tlon","help":"decentralized messaging on Urbit; install the plugin to enable.","hasChildren":true}
|
||||
{"recordType":"path","path":"channels.tlon.accounts","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"channels.tlon.accounts.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"channels.tlon.accounts.*.allowPrivateNetwork","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
@ -3986,6 +3986,7 @@
|
||||
{"recordType":"path","path":"models.providers.*.models.*.api","kind":"core","type":"string","required":false,"enumValues":["openai-completions","openai-responses","openai-codex-responses","anthropic-messages","google-generative-ai","github-copilot","bedrock-converse-stream","ollama"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"models.providers.*.models.*.compat","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"models.providers.*.models.*.compat.maxTokensField","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"models.providers.*.models.*.compat.nativeWebSearchTool","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"models.providers.*.models.*.compat.requiresAssistantAfterToolResult","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"models.providers.*.models.*.compat.requiresMistralToolIds","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"models.providers.*.models.*.compat.requiresOpenAiAnthropicToolPayload","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
@ -3998,6 +3999,8 @@
|
||||
{"recordType":"path","path":"models.providers.*.models.*.compat.supportsTools","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"models.providers.*.models.*.compat.supportsUsageInStreaming","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"models.providers.*.models.*.compat.thinkingFormat","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"models.providers.*.models.*.compat.toolCallArgumentsEncoding","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"models.providers.*.models.*.compat.toolSchemaProfile","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"models.providers.*.models.*.contextWindow","kind":"core","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"models.providers.*.models.*.cost","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"models.providers.*.models.*.cost.cacheRead","kind":"core","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
@ -4086,7 +4089,10 @@
|
||||
{"recordType":"path","path":"plugins.entries.bluebubbles.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.bluebubbles.subagent.allowModelOverride","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Allow Plugin Subagent Model Override","help":"Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.brave","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/brave-plugin","help":"OpenClaw Brave plugin (plugin: brave)","hasChildren":true}
|
||||
{"recordType":"path","path":"plugins.entries.brave.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/brave-plugin Config","help":"Plugin-defined config payload for brave.","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.brave.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/brave-plugin Config","help":"Plugin-defined config payload for brave.","hasChildren":true}
|
||||
{"recordType":"path","path":"plugins.entries.brave.config.webSearch","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"plugins.entries.brave.config.webSearch.apiKey","kind":"plugin","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","security"],"label":"Brave Search API Key","help":"Brave Search API key (fallback: BRAVE_API_KEY env var).","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.brave.config.webSearch.mode","kind":"plugin","type":"string","required":false,"enumValues":["web","llm-context"],"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Brave Search Mode","help":"Brave Search mode: web or llm-context.","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.brave.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/brave-plugin","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.brave.hooks","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Plugin Hook Policy","help":"Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.","hasChildren":true}
|
||||
{"recordType":"path","path":"plugins.entries.brave.hooks.allowPromptInjection","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Allow Prompt Injection Hooks","help":"Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.","hasChildren":false}
|
||||
@ -4198,6 +4204,15 @@
|
||||
{"recordType":"path","path":"plugins.entries.elevenlabs.subagent.allowedModels","kind":"plugin","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Plugin Subagent Allowed Models","help":"Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.","hasChildren":true}
|
||||
{"recordType":"path","path":"plugins.entries.elevenlabs.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.elevenlabs.subagent.allowModelOverride","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Allow Plugin Subagent Model Override","help":"Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.fal","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/fal-provider","help":"OpenClaw fal provider plugin (plugin: fal)","hasChildren":true}
|
||||
{"recordType":"path","path":"plugins.entries.fal.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/fal-provider Config","help":"Plugin-defined config payload for fal.","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.fal.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/fal-provider","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.fal.hooks","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Plugin Hook Policy","help":"Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.","hasChildren":true}
|
||||
{"recordType":"path","path":"plugins.entries.fal.hooks.allowPromptInjection","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Allow Prompt Injection Hooks","help":"Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.fal.subagent","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Plugin Subagent Policy","help":"Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.","hasChildren":true}
|
||||
{"recordType":"path","path":"plugins.entries.fal.subagent.allowedModels","kind":"plugin","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Plugin Subagent Allowed Models","help":"Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.","hasChildren":true}
|
||||
{"recordType":"path","path":"plugins.entries.fal.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.fal.subagent.allowModelOverride","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Allow Plugin Subagent Model Override","help":"Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.feishu","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/feishu","help":"OpenClaw Feishu/Lark channel plugin (community maintained by @m1heng) (plugin: feishu)","hasChildren":true}
|
||||
{"recordType":"path","path":"plugins.entries.feishu.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/feishu Config","help":"Plugin-defined config payload for feishu.","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.feishu.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/feishu","hasChildren":false}
|
||||
@ -4208,7 +4223,10 @@
|
||||
{"recordType":"path","path":"plugins.entries.feishu.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.feishu.subagent.allowModelOverride","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Allow Plugin Subagent Model Override","help":"Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.firecrawl","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/firecrawl-plugin","help":"OpenClaw Firecrawl plugin (plugin: firecrawl)","hasChildren":true}
|
||||
{"recordType":"path","path":"plugins.entries.firecrawl.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/firecrawl-plugin Config","help":"Plugin-defined config payload for firecrawl.","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.firecrawl.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/firecrawl-plugin Config","help":"Plugin-defined config payload for firecrawl.","hasChildren":true}
|
||||
{"recordType":"path","path":"plugins.entries.firecrawl.config.webSearch","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"plugins.entries.firecrawl.config.webSearch.apiKey","kind":"plugin","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","security"],"label":"Firecrawl Search API Key","help":"Firecrawl API key for web search (fallback: FIRECRAWL_API_KEY env var).","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.firecrawl.config.webSearch.baseUrl","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Firecrawl Search Base URL","help":"Firecrawl Search base URL override.","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.firecrawl.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/firecrawl-plugin","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.firecrawl.hooks","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Plugin Hook Policy","help":"Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.","hasChildren":true}
|
||||
{"recordType":"path","path":"plugins.entries.firecrawl.hooks.allowPromptInjection","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Allow Prompt Injection Hooks","help":"Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.","hasChildren":false}
|
||||
@ -4226,7 +4244,10 @@
|
||||
{"recordType":"path","path":"plugins.entries.github-copilot.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.github-copilot.subagent.allowModelOverride","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Allow Plugin Subagent Model Override","help":"Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.google","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/google-plugin","help":"OpenClaw Google plugin (plugin: google)","hasChildren":true}
|
||||
{"recordType":"path","path":"plugins.entries.google.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/google-plugin Config","help":"Plugin-defined config payload for google.","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.google.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/google-plugin Config","help":"Plugin-defined config payload for google.","hasChildren":true}
|
||||
{"recordType":"path","path":"plugins.entries.google.config.webSearch","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"plugins.entries.google.config.webSearch.apiKey","kind":"plugin","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","security"],"label":"Gemini Search API Key","help":"Gemini API key for Google Search grounding (fallback: GEMINI_API_KEY env var).","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.google.config.webSearch.model","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["models"],"label":"Gemini Search Model","help":"Gemini model override for web search grounding.","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.google.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/google-plugin","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.google.hooks","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Plugin Hook Policy","help":"Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.","hasChildren":true}
|
||||
{"recordType":"path","path":"plugins.entries.google.hooks.allowPromptInjection","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Allow Prompt Injection Hooks","help":"Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.","hasChildren":false}
|
||||
@ -4404,7 +4425,11 @@
|
||||
{"recordType":"path","path":"plugins.entries.modelstudio.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.modelstudio.subagent.allowModelOverride","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Allow Plugin Subagent Model Override","help":"Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.moonshot","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/moonshot-provider","help":"OpenClaw Moonshot provider plugin (plugin: moonshot)","hasChildren":true}
|
||||
{"recordType":"path","path":"plugins.entries.moonshot.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/moonshot-provider Config","help":"Plugin-defined config payload for moonshot.","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.moonshot.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/moonshot-provider Config","help":"Plugin-defined config payload for moonshot.","hasChildren":true}
|
||||
{"recordType":"path","path":"plugins.entries.moonshot.config.webSearch","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"plugins.entries.moonshot.config.webSearch.apiKey","kind":"plugin","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","security"],"label":"Kimi Search API Key","help":"Moonshot/Kimi API key (fallback: KIMI_API_KEY or MOONSHOT_API_KEY env var).","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.moonshot.config.webSearch.baseUrl","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Kimi Search Base URL","help":"Kimi base URL override.","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.moonshot.config.webSearch.model","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["models"],"label":"Kimi Search Model","help":"Kimi model override.","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.moonshot.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/moonshot-provider","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.moonshot.hooks","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Plugin Hook Policy","help":"Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.","hasChildren":true}
|
||||
{"recordType":"path","path":"plugins.entries.moonshot.hooks.allowPromptInjection","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Allow Prompt Injection Hooks","help":"Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.","hasChildren":false}
|
||||
@ -4524,7 +4549,11 @@
|
||||
{"recordType":"path","path":"plugins.entries.openshell.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.openshell.subagent.allowModelOverride","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Allow Plugin Subagent Model Override","help":"Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.perplexity","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/perplexity-plugin","help":"OpenClaw Perplexity plugin (plugin: perplexity)","hasChildren":true}
|
||||
{"recordType":"path","path":"plugins.entries.perplexity.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/perplexity-plugin Config","help":"Plugin-defined config payload for perplexity.","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.perplexity.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/perplexity-plugin Config","help":"Plugin-defined config payload for perplexity.","hasChildren":true}
|
||||
{"recordType":"path","path":"plugins.entries.perplexity.config.webSearch","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"plugins.entries.perplexity.config.webSearch.apiKey","kind":"plugin","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","security"],"label":"Perplexity API Key","help":"Perplexity or OpenRouter API key for web search.","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.perplexity.config.webSearch.baseUrl","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Perplexity Base URL","help":"Optional Perplexity/OpenRouter chat-completions base URL override.","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.perplexity.config.webSearch.model","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["models"],"label":"Perplexity Model","help":"Optional Sonar/OpenRouter model override.","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.perplexity.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/perplexity-plugin","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.perplexity.hooks","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Plugin Hook Policy","help":"Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.","hasChildren":true}
|
||||
{"recordType":"path","path":"plugins.entries.perplexity.hooks.allowPromptInjection","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Allow Prompt Injection Hooks","help":"Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.","hasChildren":false}
|
||||
@ -4832,7 +4861,11 @@
|
||||
{"recordType":"path","path":"plugins.entries.whatsapp.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.whatsapp.subagent.allowModelOverride","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Allow Plugin Subagent Model Override","help":"Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.xai","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/xai-plugin","help":"OpenClaw xAI plugin (plugin: xai)","hasChildren":true}
|
||||
{"recordType":"path","path":"plugins.entries.xai.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/xai-plugin Config","help":"Plugin-defined config payload for xai.","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.xai.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/xai-plugin Config","help":"Plugin-defined config payload for xai.","hasChildren":true}
|
||||
{"recordType":"path","path":"plugins.entries.xai.config.webSearch","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"plugins.entries.xai.config.webSearch.apiKey","kind":"plugin","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","security"],"label":"Grok Search API Key","help":"xAI API key for Grok web search (fallback: XAI_API_KEY env var).","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.xai.config.webSearch.inlineCitations","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Inline Citations","help":"Include inline markdown citations in Grok responses.","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.xai.config.webSearch.model","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["models"],"label":"Grok Search Model","help":"Grok model override for web search.","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.xai.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/xai-plugin","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.xai.hooks","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Plugin Hook Policy","help":"Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.","hasChildren":true}
|
||||
{"recordType":"path","path":"plugins.entries.xai.hooks.allowPromptInjection","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Allow Prompt Injection Hooks","help":"Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.","hasChildren":false}
|
||||
@ -5403,55 +5436,64 @@
|
||||
{"recordType":"path","path":"tools.web.fetch.timeoutSeconds","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["performance","tools"],"label":"Web Fetch Timeout (sec)","help":"Timeout in seconds for web_fetch requests.","hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.fetch.userAgent","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["tools"],"label":"Web Fetch User-Agent","help":"Override User-Agent header for web_fetch requests.","hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"tools.web.search.apiKey","kind":"core","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","security","tools"],"label":"Brave Search API Key","help":"Brave Search API key (fallback: BRAVE_API_KEY env var).","hasChildren":true}
|
||||
{"recordType":"path","path":"tools.web.search.apiKey","kind":"core","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","security","tools"],"hasChildren":true}
|
||||
{"recordType":"path","path":"tools.web.search.apiKey.id","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.apiKey.provider","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.apiKey.source","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.brave","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"tools.web.search.brave.mode","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["tools"],"label":"Brave Search Mode","help":"Brave Search mode: \"web\" (URL results) or \"llm-context\" (pre-extracted page content for LLM grounding).","hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.brave.apiKey","kind":"core","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","security","tools"],"hasChildren":true}
|
||||
{"recordType":"path","path":"tools.web.search.brave.apiKey.id","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.brave.apiKey.provider","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.brave.apiKey.source","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.brave.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.brave.mode","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.brave.model","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.cacheTtlMinutes","kind":"core","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":["performance","storage","tools"],"label":"Web Search Cache TTL (min)","help":"Cache TTL in minutes for web_search results.","hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["tools"],"label":"Enable Web Search Tool","help":"Enable the web_search tool (requires a provider API key).","hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.firecrawl","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"tools.web.search.firecrawl.apiKey","kind":"core","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","security","tools"],"label":"Firecrawl Search API Key","help":"Firecrawl API key for web search (fallback: FIRECRAWL_API_KEY env var).","hasChildren":true}
|
||||
{"recordType":"path","path":"tools.web.search.firecrawl.apiKey","kind":"core","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","security","tools"],"hasChildren":true}
|
||||
{"recordType":"path","path":"tools.web.search.firecrawl.apiKey.id","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.firecrawl.apiKey.provider","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.firecrawl.apiKey.source","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.firecrawl.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["tools"],"label":"Firecrawl Search Base URL","help":"Firecrawl Search base URL override (default: \"https://api.firecrawl.dev\").","hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.firecrawl.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.firecrawl.model","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.gemini","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"tools.web.search.gemini.apiKey","kind":"core","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","security","tools"],"label":"Gemini Search API Key","help":"Gemini API key for Google Search grounding (fallback: GEMINI_API_KEY env var).","hasChildren":true}
|
||||
{"recordType":"path","path":"tools.web.search.gemini.apiKey","kind":"core","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","security","tools"],"hasChildren":true}
|
||||
{"recordType":"path","path":"tools.web.search.gemini.apiKey.id","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.gemini.apiKey.provider","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.gemini.apiKey.source","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.gemini.model","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["models","tools"],"label":"Gemini Search Model","help":"Gemini model override (default: \"gemini-2.5-flash\").","hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.gemini.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.gemini.model","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.grok","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"tools.web.search.grok.apiKey","kind":"core","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","security","tools"],"label":"Grok Search API Key","help":"Grok (xAI) API key (fallback: XAI_API_KEY env var).","hasChildren":true}
|
||||
{"recordType":"path","path":"tools.web.search.grok.apiKey","kind":"core","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","security","tools"],"hasChildren":true}
|
||||
{"recordType":"path","path":"tools.web.search.grok.apiKey.id","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.grok.apiKey.provider","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.grok.apiKey.source","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.grok.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.grok.inlineCitations","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.grok.model","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["models","tools"],"label":"Grok Search Model","help":"Grok model override (default: \"grok-4-1-fast\").","hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.grok.model","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.kimi","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"tools.web.search.kimi.apiKey","kind":"core","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","security","tools"],"label":"Kimi Search API Key","help":"Moonshot/Kimi API key (fallback: KIMI_API_KEY or MOONSHOT_API_KEY env var).","hasChildren":true}
|
||||
{"recordType":"path","path":"tools.web.search.kimi.apiKey","kind":"core","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","security","tools"],"hasChildren":true}
|
||||
{"recordType":"path","path":"tools.web.search.kimi.apiKey.id","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.kimi.apiKey.provider","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.kimi.apiKey.source","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.kimi.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["tools"],"label":"Kimi Search Base URL","help":"Kimi base URL override (default: \"https://api.moonshot.ai/v1\").","hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.kimi.model","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["models","tools"],"label":"Kimi Search Model","help":"Kimi model override (default: \"moonshot-v1-128k\").","hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.kimi.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.kimi.model","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.maxResults","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["performance","tools"],"label":"Web Search Max Results","help":"Number of results to return (1-10).","hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.perplexity","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"tools.web.search.perplexity.apiKey","kind":"core","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","security","tools"],"label":"Perplexity API Key","help":"Perplexity or OpenRouter API key (fallback: PERPLEXITY_API_KEY or OPENROUTER_API_KEY env var). Direct Perplexity keys default to the Search API; OpenRouter keys use Sonar chat completions.","hasChildren":true}
|
||||
{"recordType":"path","path":"tools.web.search.perplexity.apiKey","kind":"core","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","security","tools"],"hasChildren":true}
|
||||
{"recordType":"path","path":"tools.web.search.perplexity.apiKey.id","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.perplexity.apiKey.provider","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.perplexity.apiKey.source","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.perplexity.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["tools"],"label":"Perplexity Base URL","help":"Optional Perplexity/OpenRouter chat-completions base URL override. Setting this opts Perplexity into the legacy Sonar/OpenRouter compatibility path.","hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.perplexity.model","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["models","tools"],"label":"Perplexity Model","help":"Optional Sonar/OpenRouter model override (default: \"perplexity/sonar-pro\"). Setting this opts Perplexity into the legacy chat-completions compatibility path.","hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.provider","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["tools"],"label":"Web Search Provider","help":"Search provider (\"brave\", \"firecrawl\", \"gemini\", \"grok\", \"kimi\", or \"perplexity\"). Auto-detected from available API keys if omitted.","hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.perplexity.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.perplexity.model","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.provider","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["tools"],"label":"Web Search Provider","help":"Search provider id. Auto-detected from available API keys if omitted.","hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.timeoutSeconds","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["performance","tools"],"label":"Web Search Timeout (sec)","help":"Timeout in seconds for web_search requests.","hasChildren":false}
|
||||
{"recordType":"path","path":"ui","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"UI","help":"UI presentation settings for accenting and assistant identity shown in control surfaces. Use this for branding and readability customization without changing runtime behavior.","hasChildren":true}
|
||||
{"recordType":"path","path":"ui.assistant","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Assistant Appearance","help":"Assistant display identity settings for name and avatar shown in UI surfaces. Keep these values aligned with your operator-facing persona and support expectations.","hasChildren":true}
|
||||
{"recordType":"path","path":"ui.assistant.avatar","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Assistant Avatar","help":"Assistant avatar image source used in UI surfaces (URL, path, or data URI depending on runtime support). Use trusted assets and consistent branding dimensions for clean rendering.","hasChildren":false}
|
||||
{"recordType":"path","path":"ui.assistant.name","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Assistant Name","help":"Display name shown for the assistant in UI views, chat chrome, and status contexts. Keep this stable so operators can reliably identify which assistant persona is active.","hasChildren":false}
|
||||
{"recordType":"path","path":"ui.seamColor","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Accent Color","help":"Primary accent/seam color used by UI surfaces for emphasis, badges, and visual identity cues. Use high-contrast values that remain readable across light/dark themes.","hasChildren":false}
|
||||
{"recordType":"path","path":"ui.seamColor","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Accent Color","help":"Primary accent color used by UI surfaces for emphasis, badges, and visual identity cues. Use high-contrast values that remain readable across light/dark themes.","hasChildren":false}
|
||||
{"recordType":"path","path":"update","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Updates","help":"Update-channel and startup-check behavior for keeping OpenClaw runtime versions current. Use conservative channels in production and more experimental channels only in controlled environments.","hasChildren":true}
|
||||
{"recordType":"path","path":"update.auto","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"update.auto.betaCheckIntervalHours","kind":"core","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":["performance"],"label":"Auto Update Beta Check Interval (hours)","help":"How often beta-channel checks run in hours (default: 1).","hasChildren":false}
|
||||
|
||||
@ -1 +0,0 @@
|
||||
docs.openclaw.ai
|
||||
@ -1,3 +1,11 @@
|
||||
---
|
||||
title: "Auth Credential Semantics"
|
||||
summary: "Canonical credential eligibility and resolution semantics for auth profiles"
|
||||
read_when:
|
||||
- Working on auth profile resolution or credential routing
|
||||
- Debugging model auth failures or profile order
|
||||
---
|
||||
|
||||
# Auth Credential Semantics
|
||||
|
||||
This document defines the canonical credential eligibility and resolution semantics used across:
|
||||
|
||||
@ -700,7 +700,7 @@ openclaw system event --mode now --text "Next heartbeat: check battery."
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### “Nothing runs”
|
||||
### "Nothing runs"
|
||||
|
||||
- Check cron is enabled: `cron.enabled` and `OPENCLAW_SKIP_CRON`.
|
||||
- Check the Gateway is running continuously (cron runs inside the Gateway process).
|
||||
|
||||
@ -17,7 +17,7 @@ Hooks are small scripts that run when something happens. There are two kinds:
|
||||
- **Hooks** (this page): run inside the Gateway when agent events fire, like `/new`, `/reset`, `/stop`, or lifecycle events.
|
||||
- **Webhooks**: external HTTP webhooks that let other systems trigger work in OpenClaw. See [Webhook Hooks](/automation/webhook) or use `openclaw webhooks` for Gmail helper commands.
|
||||
|
||||
Hooks can also be bundled inside plugins; see [Plugins](/tools/plugin#plugin-hooks).
|
||||
Hooks can also be bundled inside plugins; see [Plugin hooks](/plugins/architecture#provider-runtime-hooks).
|
||||
|
||||
Common uses:
|
||||
|
||||
|
||||
@ -20,11 +20,21 @@ OpenClaw supports Brave Search API as a `web_search` provider.
|
||||
|
||||
```json5
|
||||
{
|
||||
plugins: {
|
||||
entries: {
|
||||
brave: {
|
||||
config: {
|
||||
webSearch: {
|
||||
apiKey: "BRAVE_API_KEY_HERE",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
tools: {
|
||||
web: {
|
||||
search: {
|
||||
provider: "brave",
|
||||
apiKey: "BRAVE_API_KEY_HERE",
|
||||
maxResults: 5,
|
||||
timeoutSeconds: 30,
|
||||
},
|
||||
@ -33,6 +43,9 @@ OpenClaw supports Brave Search API as a `web_search` provider.
|
||||
}
|
||||
```
|
||||
|
||||
Provider-specific Brave search settings now live under `plugins.entries.brave.config.webSearch.*`.
|
||||
Legacy `tools.web.search.apiKey` still loads through the compatibility shim, but it is no longer the canonical config path.
|
||||
|
||||
## Tool parameters
|
||||
|
||||
| Parameter | Description |
|
||||
|
||||
@ -11,7 +11,7 @@ Goal: let Clawd sit in WhatsApp groups, wake up only when pinged, and keep that
|
||||
|
||||
Note: `agents.list[].groupChat.mentionPatterns` is now used by Telegram/Discord/Slack/iMessage as well; this doc focuses on WhatsApp-specific behavior. For multi-agent setups, set `agents.list[].groupChat.mentionPatterns` per agent (or use `messages.groupChat.mentionPatterns` as a global fallback).
|
||||
|
||||
## What’s implemented (2025-12-03)
|
||||
## Current implementation (2025-12-03)
|
||||
|
||||
- Activation modes: `mention` (default) or `always`. `mention` requires a ping (real WhatsApp @-mentions via `mentionedJids`, safe regex patterns, or the bot’s E.164 anywhere in the text). `always` wakes the agent on every message but it should reply only when it can add meaningful value; otherwise it returns the silent token `NO_REPLY`. Defaults can be set in config (`channels.whatsapp.groups`) and overridden per group via `/activation`. When `channels.whatsapp.groups` is set, it also acts as a group allowlist (include `"*"` to allow all).
|
||||
- Group policy: `channels.whatsapp.groupPolicy` controls whether group messages are accepted (`open|disabled|allowlist`). `allowlist` uses `channels.whatsapp.groupAllowFrom` (fallback: explicit `channels.whatsapp.allowFrom`). Default is `allowlist` (blocked until you add senders).
|
||||
|
||||
@ -7,6 +7,8 @@ read_when:
|
||||
- You are configuring IRC allowlists, group policy, or mention gating
|
||||
---
|
||||
|
||||
# IRC
|
||||
|
||||
Use IRC when you want OpenClaw in classic channels (`#room`) and direct messages.
|
||||
IRC ships as an extension plugin, but it is configured in the main config under `channels.irc`.
|
||||
|
||||
|
||||
@ -255,7 +255,7 @@ openclaw doctor
|
||||
openclaw channels status --probe
|
||||
```
|
||||
|
||||
### Bot doesn't respond to messages
|
||||
### Bot does not respond to messages
|
||||
|
||||
**Check access control:** Ensure your user ID is in `allowFrom`, or temporarily remove
|
||||
`allowFrom` and set `allowedRoles: ["all"]` to test.
|
||||
|
||||
@ -8,7 +8,7 @@ title: "acp"
|
||||
|
||||
# acp
|
||||
|
||||
Run the [Agent Client Protocol (ACP)](https://agentclientprotocol.com/) bridge that talks to a OpenClaw Gateway.
|
||||
Run the [Agent Client Protocol (ACP)](https://agentclientprotocol.com/) bridge that talks to an OpenClaw Gateway.
|
||||
|
||||
This command speaks ACP over stdio for IDEs and forwards prompts to the Gateway
|
||||
over WebSocket. It keeps ACP sessions mapped to Gateway session keys.
|
||||
@ -102,7 +102,7 @@ Permission model (client debug mode):
|
||||
## How to use this
|
||||
|
||||
Use ACP when an IDE (or other client) speaks Agent Client Protocol and you want
|
||||
it to drive a OpenClaw Gateway session.
|
||||
it to drive an OpenClaw Gateway session.
|
||||
|
||||
1. Ensure the Gateway is running (local or remote).
|
||||
2. Configure the Gateway target (config or flags).
|
||||
|
||||
@ -21,7 +21,7 @@ openclaw config set agents.defaults.heartbeat.every "2h"
|
||||
openclaw config set agents.list[0].tools.exec.node "node-id-or-name"
|
||||
openclaw config set channels.discord.token --ref-provider default --ref-source env --ref-id DISCORD_BOT_TOKEN
|
||||
openclaw config set secrets.providers.vaultfile --provider-source file --provider-path /etc/openclaw/secrets.json --provider-mode json
|
||||
openclaw config unset tools.web.search.apiKey
|
||||
openclaw config unset plugins.entries.brave.config.webSearch.apiKey
|
||||
openclaw config set channels.discord.token --ref-provider default --ref-source env --ref-id DISCORD_BOT_TOKEN --dry-run
|
||||
openclaw config validate
|
||||
openclaw config validate --json
|
||||
|
||||
@ -40,7 +40,7 @@ openclaw message send --channel slack --target user:U012ABCDEF --message "hello"
|
||||
- Zalo (plugin): user id (Bot API)
|
||||
- Zalo Personal / `zalouser` (plugin): thread id (DM/group) from `zca` (`me`, `friend list`, `group list`)
|
||||
|
||||
## Self (“me”)
|
||||
## Self ("me")
|
||||
|
||||
```bash
|
||||
openclaw directory self --channel zalouser
|
||||
|
||||
@ -13,7 +13,7 @@ Manage agent hooks (event-driven automations for commands like `/new`, `/reset`,
|
||||
Related:
|
||||
|
||||
- Hooks: [Hooks](/automation/hooks)
|
||||
- Plugin hooks: [Plugins](/tools/plugin#plugin-hooks)
|
||||
- Plugin hooks: [Plugin hooks](/plugins/architecture#provider-runtime-hooks)
|
||||
|
||||
## List All Hooks
|
||||
|
||||
|
||||
@ -88,7 +88,7 @@ OpenClaw uses a lobster palette for CLI output.
|
||||
- `error` (#E23D2D): errors, failures.
|
||||
- `muted` (#8B7F77): de-emphasis, metadata.
|
||||
|
||||
Palette source of truth: `src/terminal/palette.ts` (aka “lobster seam”).
|
||||
Palette source of truth: `src/terminal/palette.ts` (the “lobster palette”).
|
||||
|
||||
## Command tree
|
||||
|
||||
@ -276,9 +276,9 @@ Note: plugins can add additional top-level commands (for example `openclaw voice
|
||||
## Secrets
|
||||
|
||||
- `openclaw secrets reload` — re-resolve refs and atomically swap the runtime snapshot.
|
||||
- `openclaw secrets audit` — scan for plaintext residues, unresolved refs, and precedence drift.
|
||||
- `openclaw secrets configure` — interactive helper for provider setup + SecretRef mapping + preflight/apply.
|
||||
- `openclaw secrets apply --from <plan.json>` — apply a previously generated plan (`--dry-run` supported).
|
||||
- `openclaw secrets audit` — scan for plaintext residues, unresolved refs, and precedence drift (`--allow-exec` to execute exec providers during audit).
|
||||
- `openclaw secrets configure` — interactive helper for provider setup + SecretRef mapping + preflight/apply (`--allow-exec` to execute exec providers during preflight and exec-containing apply flows).
|
||||
- `openclaw secrets apply --from <plan.json>` — apply a previously generated plan (`--dry-run` supported; use `--allow-exec` to permit exec providers in dry-run and exec-containing write plans).
|
||||
|
||||
## Plugins
|
||||
|
||||
|
||||
@ -21,6 +21,7 @@ Related:
|
||||
|
||||
```bash
|
||||
openclaw plugins list
|
||||
openclaw plugins install <path-or-spec>
|
||||
openclaw plugins inspect <id>
|
||||
openclaw plugins enable <id>
|
||||
openclaw plugins disable <id>
|
||||
@ -31,8 +32,6 @@ openclaw plugins update --all
|
||||
openclaw plugins marketplace list <marketplace>
|
||||
```
|
||||
|
||||
`info` is an alias for `inspect`.
|
||||
|
||||
Bundled plugins ship with OpenClaw but start disabled. Use `plugins enable` to
|
||||
activate them.
|
||||
|
||||
@ -159,16 +158,18 @@ openclaw plugins inspect <id> --json
|
||||
```
|
||||
|
||||
Deep introspection for a single plugin. Shows identity, load status, source,
|
||||
plugin shape, registered capabilities, hooks, tools, commands, services,
|
||||
gateway methods, HTTP routes, policy flags, diagnostics, and install metadata.
|
||||
registered capabilities, hooks, tools, commands, services, gateway methods,
|
||||
HTTP routes, policy flags, diagnostics, and install metadata.
|
||||
|
||||
Plugin shape is derived from actual registration behavior:
|
||||
Each plugin is classified by what it actually registers at runtime:
|
||||
|
||||
- **plain-capability** — one capability type registered
|
||||
- **hybrid-capability** — multiple capability types registered
|
||||
- **plain-capability** — one capability type (e.g. a provider-only plugin)
|
||||
- **hybrid-capability** — multiple capability types (e.g. text + speech + images)
|
||||
- **hook-only** — only hooks, no capabilities or surfaces
|
||||
- **non-capability** — tools/commands/services but no capabilities
|
||||
|
||||
See [Plugin shapes](/plugins/architecture#plugin-shapes) for more on the capability model.
|
||||
|
||||
The `--json` flag outputs a machine-readable report suitable for scripting and
|
||||
auditing.
|
||||
|
||||
|
||||
@ -14,9 +14,9 @@ Use `openclaw secrets` to manage SecretRefs and keep the active runtime snapshot
|
||||
Command roles:
|
||||
|
||||
- `reload`: gateway RPC (`secrets.reload`) that re-resolves refs and swaps runtime snapshot only on full success (no config writes).
|
||||
- `audit`: read-only scan of configuration/auth/generated-model stores and legacy residues for plaintext, unresolved refs, and precedence drift.
|
||||
- `audit`: read-only scan of configuration/auth/generated-model stores and legacy residues for plaintext, unresolved refs, and precedence drift (exec refs are skipped unless `--allow-exec` is set).
|
||||
- `configure`: interactive planner for provider setup, target mapping, and preflight (TTY required).
|
||||
- `apply`: execute a saved plan (`--dry-run` for validation only), then scrub targeted plaintext residues.
|
||||
- `apply`: execute a saved plan (`--dry-run` for validation only; dry-run skips exec checks by default, and write mode rejects exec-containing plans unless `--allow-exec` is set), then scrub targeted plaintext residues.
|
||||
|
||||
Recommended operator loop:
|
||||
|
||||
@ -29,6 +29,8 @@ openclaw secrets audit --check
|
||||
openclaw secrets reload
|
||||
```
|
||||
|
||||
If your plan includes `exec` SecretRefs/providers, pass `--allow-exec` on both dry-run and write apply commands.
|
||||
|
||||
Exit code note for CI/gates:
|
||||
|
||||
- `audit --check` returns `1` on findings.
|
||||
@ -73,6 +75,7 @@ Header residue note:
|
||||
openclaw secrets audit
|
||||
openclaw secrets audit --check
|
||||
openclaw secrets audit --json
|
||||
openclaw secrets audit --allow-exec
|
||||
```
|
||||
|
||||
Exit behavior:
|
||||
@ -83,6 +86,7 @@ Exit behavior:
|
||||
Report shape highlights:
|
||||
|
||||
- `status`: `clean | findings | unresolved`
|
||||
- `resolution`: `refsChecked`, `skippedExecRefs`, `resolvabilityComplete`
|
||||
- `summary`: `plaintextCount`, `unresolvedRefCount`, `shadowedRefCount`, `legacyResidueCount`
|
||||
- finding codes:
|
||||
- `PLAINTEXT_FOUND`
|
||||
@ -115,6 +119,7 @@ Flags:
|
||||
- `--providers-only`: configure `secrets.providers` only, skip credential mapping.
|
||||
- `--skip-provider-setup`: skip provider setup and map credentials to existing providers.
|
||||
- `--agent <id>`: scope `auth-profiles.json` target discovery and writes to one agent store.
|
||||
- `--allow-exec`: allow exec SecretRef checks during preflight/apply (may execute provider commands).
|
||||
|
||||
Notes:
|
||||
|
||||
@ -124,6 +129,7 @@ Notes:
|
||||
- `configure` supports creating new `auth-profiles.json` mappings directly in the picker flow.
|
||||
- Canonical supported surface: [SecretRef Credential Surface](/reference/secretref-credential-surface).
|
||||
- It performs preflight resolution before apply.
|
||||
- If preflight/apply includes exec refs, keep `--allow-exec` set for both steps.
|
||||
- Generated plans default to scrub options (`scrubEnv`, `scrubAuthProfilesForProviderTargets`, `scrubLegacyAuthJson` all enabled).
|
||||
- Apply path is one-way for scrubbed plaintext values.
|
||||
- Without `--apply`, CLI still prompts `Apply this plan now?` after preflight.
|
||||
@ -141,10 +147,19 @@ Apply or preflight a plan generated previously:
|
||||
|
||||
```bash
|
||||
openclaw secrets apply --from /tmp/openclaw-secrets-plan.json
|
||||
openclaw secrets apply --from /tmp/openclaw-secrets-plan.json --allow-exec
|
||||
openclaw secrets apply --from /tmp/openclaw-secrets-plan.json --dry-run
|
||||
openclaw secrets apply --from /tmp/openclaw-secrets-plan.json --dry-run --allow-exec
|
||||
openclaw secrets apply --from /tmp/openclaw-secrets-plan.json --json
|
||||
```
|
||||
|
||||
Exec behavior:
|
||||
|
||||
- `--dry-run` validates preflight without writing files.
|
||||
- exec SecretRef checks are skipped by default in dry-run.
|
||||
- write mode rejects plans that contain exec SecretRefs/providers unless `--allow-exec` is set.
|
||||
- Use `--allow-exec` to opt in to exec provider checks/execution in either mode.
|
||||
|
||||
Plan contract details (allowed target paths, validation rules, and failure semantics):
|
||||
|
||||
- [Secrets Apply Plan Contract](/gateway/secrets-plan-contract)
|
||||
|
||||
@ -92,7 +92,7 @@ These run inside the agent loop or gateway pipeline:
|
||||
- **`session_start` / `session_end`**: session lifecycle boundaries.
|
||||
- **`gateway_start` / `gateway_stop`**: gateway lifecycle events.
|
||||
|
||||
See [Plugins](/tools/plugin#plugin-hooks) for the hook API and registration details.
|
||||
See [Plugin hooks](/plugins/architecture#provider-runtime-hooks) for the hook API and registration details.
|
||||
|
||||
## Streaming + partial replies
|
||||
|
||||
|
||||
@ -108,6 +108,14 @@ summaries, vector retrieval, incremental condensation, etc.
|
||||
When a plugin engine sets `ownsCompaction: true`, OpenClaw delegates all
|
||||
compaction decisions to the engine and does not run built-in auto-compaction.
|
||||
|
||||
When `ownsCompaction` is `false` or unset, OpenClaw may still use Pi's
|
||||
built-in in-attempt auto-compaction, but the active engine's `compact()` method
|
||||
still handles `/compact` and overflow recovery. There is no automatic fallback
|
||||
to the legacy engine's compaction path.
|
||||
|
||||
If you are building a non-owning context engine, implement `compact()` by
|
||||
calling `delegateCompactionToRuntime(...)` from `openclaw/plugin-sdk/core`.
|
||||
|
||||
## Tips
|
||||
|
||||
- Use `/compact` when sessions feel stale or context is bloated.
|
||||
|
||||
@ -14,7 +14,7 @@ It decides which messages to include, how to summarize older history, and how
|
||||
to manage context across subagent boundaries.
|
||||
|
||||
OpenClaw ships with a built-in `legacy` engine. Plugins can register
|
||||
alternative engines that replace the entire context pipeline.
|
||||
alternative engines that replace the active context-engine lifecycle.
|
||||
|
||||
## Quick start
|
||||
|
||||
@ -194,13 +194,31 @@ Optional members:
|
||||
|
||||
### ownsCompaction
|
||||
|
||||
When `info.ownsCompaction` is `true`, the engine manages its own compaction
|
||||
lifecycle. OpenClaw will not trigger the built-in auto-compaction; instead it
|
||||
delegates entirely to the engine's `compact()` method. The engine may also
|
||||
run compaction proactively in `afterTurn()`.
|
||||
`ownsCompaction` controls whether Pi's built-in in-attempt auto-compaction stays
|
||||
enabled for the run:
|
||||
|
||||
When `false` or unset, OpenClaw's built-in auto-compaction logic runs
|
||||
alongside the engine.
|
||||
- `true` — the engine owns compaction behavior. OpenClaw disables Pi's built-in
|
||||
auto-compaction for that run, and the engine's `compact()` implementation is
|
||||
responsible for `/compact`, overflow recovery compaction, and any proactive
|
||||
compaction it wants to do in `afterTurn()`.
|
||||
- `false` or unset — Pi's built-in auto-compaction may still run during prompt
|
||||
execution, but the active engine's `compact()` method is still called for
|
||||
`/compact` and overflow recovery.
|
||||
|
||||
`ownsCompaction: false` does **not** mean OpenClaw automatically falls back to
|
||||
the legacy engine's compaction path.
|
||||
|
||||
That means there are two valid plugin patterns:
|
||||
|
||||
- **Owning mode** — implement your own compaction algorithm and set
|
||||
`ownsCompaction: true`.
|
||||
- **Delegating mode** — set `ownsCompaction: false` and have `compact()` call
|
||||
`delegateCompactionToRuntime(...)` from `openclaw/plugin-sdk/core` to use
|
||||
OpenClaw's built-in compaction behavior.
|
||||
|
||||
A no-op `compact()` is unsafe for an active non-owning engine because it
|
||||
disables the normal `/compact` and overflow-recovery compaction path for that
|
||||
engine slot.
|
||||
|
||||
## Configuration reference
|
||||
|
||||
|
||||
@ -116,7 +116,7 @@ Large files are truncated per-file using `agents.defaults.bootstrapMaxChars` (de
|
||||
|
||||
When truncation occurs, the runtime can inject an in-prompt warning block under Project Context. Configure this with `agents.defaults.bootstrapPromptTruncationWarning` (`off`, `once`, `always`; default `once`).
|
||||
|
||||
## Skills: what’s injected vs loaded on-demand
|
||||
## Skills: injected vs loaded on-demand
|
||||
|
||||
The system prompt includes a compact **skills list** (name + description + location). This list has real overhead.
|
||||
|
||||
@ -131,7 +131,7 @@ Tools affect context in two ways:
|
||||
|
||||
`/context detail` breaks down the biggest tool schemas so you can see what dominates.
|
||||
|
||||
## Commands, directives, and “inline shortcuts”
|
||||
## Commands, directives, and "inline shortcuts"
|
||||
|
||||
Slash commands are handled by the Gateway. There are a few different behaviors:
|
||||
|
||||
@ -157,7 +157,9 @@ By default, OpenClaw uses the built-in `legacy` context engine for assembly and
|
||||
compaction. If you install a plugin that provides `kind: "context-engine"` and
|
||||
select it with `plugins.slots.contextEngine`, OpenClaw delegates context
|
||||
assembly, `/compact`, and related subagent context lifecycle hooks to that
|
||||
engine instead. See [Context Engine](/concepts/context-engine) for the full
|
||||
engine instead. `ownsCompaction: false` does not auto-fallback to the legacy
|
||||
engine; the active engine must still implement `compact()` correctly. See
|
||||
[Context Engine](/concepts/context-engine) for the full
|
||||
pluggable interface, lifecycle hooks, and configuration.
|
||||
|
||||
## What `/context` actually reports
|
||||
|
||||
@ -5,6 +5,8 @@ read_when:
|
||||
title: "Features"
|
||||
---
|
||||
|
||||
# Features
|
||||
|
||||
## Highlights
|
||||
|
||||
<Columns>
|
||||
|
||||
@ -70,7 +70,7 @@ they are tried first, but OpenClaw may rotate to another profile on rate limits/
|
||||
User‑pinned profiles stay locked to that profile; if it fails and model fallbacks
|
||||
are configured, OpenClaw moves to the next model instead of switching profiles.
|
||||
|
||||
### Why OAuth can “look lost”
|
||||
### Why OAuth can "look lost"
|
||||
|
||||
If you have both an OAuth profile and an API key profile for the same provider, round‑robin can switch between them across messages unless pinned. To force a single profile:
|
||||
|
||||
|
||||
@ -34,7 +34,7 @@ For model selection rules, see [/concepts/models](/concepts/models).
|
||||
`fetchUsageSnapshot`.
|
||||
- Note: provider runtime `capabilities` is shared runner metadata (provider
|
||||
family, transcript/tooling quirks, transport/cache hints). It is not the
|
||||
same as the [public capability model](/tools/plugin#public-capability-model)
|
||||
same as the [public capability model](/plugins/architecture#public-capability-model)
|
||||
which describes what a plugin registers (text inference, speech, etc.).
|
||||
|
||||
## Plugin-owned provider behavior
|
||||
|
||||
@ -60,7 +60,7 @@ to `zai/*`.
|
||||
Provider configuration examples (including OpenCode) live in
|
||||
[/gateway/configuration](/gateway/configuration#opencode).
|
||||
|
||||
## “Model is not allowed” (and why replies stop)
|
||||
## "Model is not allowed" (and why replies stop)
|
||||
|
||||
If `agents.defaults.models` is set, it becomes the **allowlist** for `/model` and for
|
||||
session overrides. When a user selects a model that isn’t in that allowlist,
|
||||
|
||||
@ -9,7 +9,7 @@ status: active
|
||||
|
||||
Goal: multiple _isolated_ agents (separate workspace + `agentDir` + sessions), plus multiple channel accounts (e.g. two WhatsApps) in one running Gateway. Inbound is routed to an agent via bindings.
|
||||
|
||||
## What is “one agent”?
|
||||
## What is "one agent"?
|
||||
|
||||
An **agent** is a fully scoped brain with its own:
|
||||
|
||||
|
||||
@ -45,7 +45,7 @@ even before any clients connect.
|
||||
Every WS client begins with a `connect` request. On successful handshake the
|
||||
Gateway upserts a presence entry for that connection.
|
||||
|
||||
#### Why one‑off CLI commands don’t show up
|
||||
#### Why one-off CLI commands do not show up
|
||||
|
||||
The CLI often connects for short, one‑off commands. To avoid spamming the
|
||||
Instances list, `client.mode === "cli"` is **not** turned into a presence entry.
|
||||
|
||||
@ -90,7 +90,7 @@ more natural.
|
||||
- Modes: `off` (default), `natural` (800–2500ms), `custom` (`minMs`/`maxMs`).
|
||||
- Applies only to **block replies**, not final replies or tool summaries.
|
||||
|
||||
## “Stream chunks or everything”
|
||||
## "Stream chunks or everything"
|
||||
|
||||
This maps to:
|
||||
|
||||
|
||||
@ -185,7 +185,7 @@ ws.on("message", (data) => {
|
||||
});
|
||||
```
|
||||
|
||||
## Worked example: add a method end‑to‑end
|
||||
## Worked example: add a method end-to-end
|
||||
|
||||
Example: add a new `system.echo` request that returns `{ ok: true, text }`.
|
||||
|
||||
|
||||
@ -1,534 +0,0 @@
|
||||
# Kilo Gateway Provider Integration Design
|
||||
|
||||
## Overview
|
||||
|
||||
This document outlines the design for integrating "Kilo Gateway" as a first-class provider in OpenClaw, modeled after the existing OpenRouter implementation. Kilo Gateway uses an OpenAI-compatible completions API with a different base URL.
|
||||
|
||||
## Design Decisions
|
||||
|
||||
### 1. Provider Naming
|
||||
|
||||
**Recommendation: `kilocode`**
|
||||
|
||||
Rationale:
|
||||
|
||||
- Matches the user config example provided (`kilocode` provider key)
|
||||
- Consistent with existing provider naming patterns (e.g., `openrouter`, `opencode`, `moonshot`)
|
||||
- Short and memorable
|
||||
- Avoids confusion with generic "kilo" or "gateway" terms
|
||||
|
||||
Alternative considered: `kilo-gateway` - rejected because hyphenated names are less common in the codebase and `kilocode` is more concise.
|
||||
|
||||
### 2. Default Model Reference
|
||||
|
||||
**Recommendation: `kilocode/anthropic/claude-opus-4.6`**
|
||||
|
||||
Rationale:
|
||||
|
||||
- Based on user config example
|
||||
- Claude Opus 4.5 is a capable default model
|
||||
- Explicit model selection avoids reliance on auto-routing
|
||||
|
||||
### 3. Base URL Configuration
|
||||
|
||||
**Recommendation: Hardcoded default with config override**
|
||||
|
||||
- **Default Base URL:** `https://api.kilo.ai/api/gateway/`
|
||||
- **Configurable:** Yes, via `models.providers.kilocode.baseUrl`
|
||||
|
||||
This matches the pattern used by other providers like Moonshot, Venice, and Synthetic.
|
||||
|
||||
### 4. Model Scanning
|
||||
|
||||
**Recommendation: No dedicated model scanning endpoint initially**
|
||||
|
||||
Rationale:
|
||||
|
||||
- Kilo Gateway proxies to OpenRouter, so models are dynamic
|
||||
- Users can manually configure models in their config
|
||||
- If Kilo Gateway exposes a `/models` endpoint in the future, scanning can be added
|
||||
|
||||
### 5. Special Handling
|
||||
|
||||
**Recommendation: Inherit OpenRouter behavior for Anthropic models**
|
||||
|
||||
Since Kilo Gateway proxies to OpenRouter, the same special handling should apply:
|
||||
|
||||
- Cache TTL eligibility for `anthropic/*` models
|
||||
- Extra params (cacheControlTtl) for `anthropic/*` models
|
||||
- Transcript policy follows OpenRouter patterns
|
||||
|
||||
## Files to Modify
|
||||
|
||||
### Core Credential Management
|
||||
|
||||
#### 1. `src/commands/onboard-auth.credentials.ts`
|
||||
|
||||
Add:
|
||||
|
||||
```typescript
|
||||
export const KILOCODE_DEFAULT_MODEL_REF = "kilocode/anthropic/claude-opus-4.6";
|
||||
|
||||
export async function setKilocodeApiKey(key: string, agentDir?: string) {
|
||||
upsertAuthProfile({
|
||||
profileId: "kilocode:default",
|
||||
credential: {
|
||||
type: "api_key",
|
||||
provider: "kilocode",
|
||||
key,
|
||||
},
|
||||
agentDir: resolveAuthAgentDir(agentDir),
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. `src/agents/model-auth.ts`
|
||||
|
||||
Add to `envMap` in `resolveEnvApiKey()`:
|
||||
|
||||
```typescript
|
||||
const envMap: Record<string, string> = {
|
||||
// ... existing entries
|
||||
kilocode: "KILOCODE_API_KEY",
|
||||
};
|
||||
```
|
||||
|
||||
#### 3. `src/config/io.ts`
|
||||
|
||||
Add to `SHELL_ENV_EXPECTED_KEYS`:
|
||||
|
||||
```typescript
|
||||
const SHELL_ENV_EXPECTED_KEYS = [
|
||||
// ... existing entries
|
||||
"KILOCODE_API_KEY",
|
||||
];
|
||||
```
|
||||
|
||||
### Config Application
|
||||
|
||||
#### 4. `src/commands/onboard-auth.config-core.ts`
|
||||
|
||||
Add new functions:
|
||||
|
||||
```typescript
|
||||
export const KILOCODE_BASE_URL = "https://api.kilo.ai/api/gateway/";
|
||||
|
||||
export function applyKilocodeProviderConfig(cfg: OpenClawConfig): OpenClawConfig {
|
||||
const models = { ...cfg.agents?.defaults?.models };
|
||||
models[KILOCODE_DEFAULT_MODEL_REF] = {
|
||||
...models[KILOCODE_DEFAULT_MODEL_REF],
|
||||
alias: models[KILOCODE_DEFAULT_MODEL_REF]?.alias ?? "Kilo Gateway",
|
||||
};
|
||||
|
||||
const providers = { ...cfg.models?.providers };
|
||||
const existingProvider = providers.kilocode;
|
||||
const { apiKey: existingApiKey, ...existingProviderRest } = (existingProvider ?? {}) as Record<
|
||||
string,
|
||||
unknown
|
||||
> as { apiKey?: string };
|
||||
const resolvedApiKey = typeof existingApiKey === "string" ? existingApiKey : undefined;
|
||||
const normalizedApiKey = resolvedApiKey?.trim();
|
||||
|
||||
providers.kilocode = {
|
||||
...existingProviderRest,
|
||||
baseUrl: KILOCODE_BASE_URL,
|
||||
api: "openai-completions",
|
||||
...(normalizedApiKey ? { apiKey: normalizedApiKey } : {}),
|
||||
};
|
||||
|
||||
return {
|
||||
...cfg,
|
||||
agents: {
|
||||
...cfg.agents,
|
||||
defaults: {
|
||||
...cfg.agents?.defaults,
|
||||
models,
|
||||
},
|
||||
},
|
||||
models: {
|
||||
mode: cfg.models?.mode ?? "merge",
|
||||
providers,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function applyKilocodeConfig(cfg: OpenClawConfig): OpenClawConfig {
|
||||
const next = applyKilocodeProviderConfig(cfg);
|
||||
const existingModel = next.agents?.defaults?.model;
|
||||
return {
|
||||
...next,
|
||||
agents: {
|
||||
...next.agents,
|
||||
defaults: {
|
||||
...next.agents?.defaults,
|
||||
model: {
|
||||
...(existingModel && "fallbacks" in (existingModel as Record<string, unknown>)
|
||||
? {
|
||||
fallbacks: (existingModel as { fallbacks?: string[] }).fallbacks,
|
||||
}
|
||||
: undefined),
|
||||
primary: KILOCODE_DEFAULT_MODEL_REF,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Auth Choice System
|
||||
|
||||
#### 5. `src/commands/onboard-types.ts`
|
||||
|
||||
Add to `AuthChoice` type:
|
||||
|
||||
```typescript
|
||||
export type AuthChoice =
|
||||
// ... existing choices
|
||||
"kilocode-api-key";
|
||||
// ...
|
||||
```
|
||||
|
||||
Add to `OnboardOptions`:
|
||||
|
||||
```typescript
|
||||
export type OnboardOptions = {
|
||||
// ... existing options
|
||||
kilocodeApiKey?: string;
|
||||
// ...
|
||||
};
|
||||
```
|
||||
|
||||
#### 6. `src/commands/auth-choice-options.ts`
|
||||
|
||||
Add to `AuthChoiceGroupId`:
|
||||
|
||||
```typescript
|
||||
export type AuthChoiceGroupId =
|
||||
// ... existing groups
|
||||
"kilocode";
|
||||
// ...
|
||||
```
|
||||
|
||||
Add to `AUTH_CHOICE_GROUP_DEFS`:
|
||||
|
||||
```typescript
|
||||
{
|
||||
value: "kilocode",
|
||||
label: "Kilo Gateway",
|
||||
hint: "API key (OpenRouter-compatible)",
|
||||
choices: ["kilocode-api-key"],
|
||||
},
|
||||
```
|
||||
|
||||
Add to `buildAuthChoiceOptions()`:
|
||||
|
||||
```typescript
|
||||
options.push({
|
||||
value: "kilocode-api-key",
|
||||
label: "Kilo Gateway API key",
|
||||
hint: "OpenRouter-compatible gateway",
|
||||
});
|
||||
```
|
||||
|
||||
#### 7. `src/commands/auth-choice.preferred-provider.ts`
|
||||
|
||||
Add mapping:
|
||||
|
||||
```typescript
|
||||
const PREFERRED_PROVIDER_BY_AUTH_CHOICE: Partial<Record<AuthChoice, string>> = {
|
||||
// ... existing mappings
|
||||
"kilocode-api-key": "kilocode",
|
||||
};
|
||||
```
|
||||
|
||||
### Auth Choice Application
|
||||
|
||||
#### 8. `src/commands/auth-choice.apply.api-providers.ts`
|
||||
|
||||
Add import:
|
||||
|
||||
```typescript
|
||||
import {
|
||||
// ... existing imports
|
||||
applyKilocodeConfig,
|
||||
applyKilocodeProviderConfig,
|
||||
KILOCODE_DEFAULT_MODEL_REF,
|
||||
setKilocodeApiKey,
|
||||
} from "./onboard-auth.js";
|
||||
```
|
||||
|
||||
Add handling for `kilocode-api-key`:
|
||||
|
||||
```typescript
|
||||
if (authChoice === "kilocode-api-key") {
|
||||
const store = ensureAuthProfileStore(params.agentDir, {
|
||||
allowKeychainPrompt: false,
|
||||
});
|
||||
const profileOrder = resolveAuthProfileOrder({
|
||||
cfg: nextConfig,
|
||||
store,
|
||||
provider: "kilocode",
|
||||
});
|
||||
const existingProfileId = profileOrder.find((profileId) => Boolean(store.profiles[profileId]));
|
||||
const existingCred = existingProfileId ? store.profiles[existingProfileId] : undefined;
|
||||
let profileId = "kilocode:default";
|
||||
let mode: "api_key" | "oauth" | "token" = "api_key";
|
||||
let hasCredential = false;
|
||||
|
||||
if (existingProfileId && existingCred?.type) {
|
||||
profileId = existingProfileId;
|
||||
mode =
|
||||
existingCred.type === "oauth" ? "oauth" : existingCred.type === "token" ? "token" : "api_key";
|
||||
hasCredential = true;
|
||||
}
|
||||
|
||||
if (!hasCredential && params.opts?.token && params.opts?.tokenProvider === "kilocode") {
|
||||
await setKilocodeApiKey(normalizeApiKeyInput(params.opts.token), params.agentDir);
|
||||
hasCredential = true;
|
||||
}
|
||||
|
||||
if (!hasCredential) {
|
||||
const envKey = resolveEnvApiKey("kilocode");
|
||||
if (envKey) {
|
||||
const useExisting = await params.prompter.confirm({
|
||||
message: `Use existing KILOCODE_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
|
||||
initialValue: true,
|
||||
});
|
||||
if (useExisting) {
|
||||
await setKilocodeApiKey(envKey.apiKey, params.agentDir);
|
||||
hasCredential = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasCredential) {
|
||||
const key = await params.prompter.text({
|
||||
message: "Enter Kilo Gateway API key",
|
||||
validate: validateApiKeyInput,
|
||||
});
|
||||
await setKilocodeApiKey(normalizeApiKeyInput(String(key)), params.agentDir);
|
||||
hasCredential = true;
|
||||
}
|
||||
|
||||
if (hasCredential) {
|
||||
nextConfig = applyAuthProfileConfig(nextConfig, {
|
||||
profileId,
|
||||
provider: "kilocode",
|
||||
mode,
|
||||
});
|
||||
}
|
||||
{
|
||||
const applied = await applyDefaultModelChoice({
|
||||
config: nextConfig,
|
||||
setDefaultModel: params.setDefaultModel,
|
||||
defaultModel: KILOCODE_DEFAULT_MODEL_REF,
|
||||
applyDefaultConfig: applyKilocodeConfig,
|
||||
applyProviderConfig: applyKilocodeProviderConfig,
|
||||
noteDefault: KILOCODE_DEFAULT_MODEL_REF,
|
||||
noteAgentModel,
|
||||
prompter: params.prompter,
|
||||
});
|
||||
nextConfig = applied.config;
|
||||
agentModelOverride = applied.agentModelOverride ?? agentModelOverride;
|
||||
}
|
||||
return { config: nextConfig, agentModelOverride };
|
||||
}
|
||||
```
|
||||
|
||||
Also add tokenProvider mapping at the top of the function:
|
||||
|
||||
```typescript
|
||||
if (params.opts.tokenProvider === "kilocode") {
|
||||
authChoice = "kilocode-api-key";
|
||||
}
|
||||
```
|
||||
|
||||
### CLI Registration
|
||||
|
||||
#### 9. `src/cli/program/register.onboard.ts`
|
||||
|
||||
Add CLI option:
|
||||
|
||||
```typescript
|
||||
.option("--kilocode-api-key <key>", "Kilo Gateway API key")
|
||||
```
|
||||
|
||||
Add to action handler:
|
||||
|
||||
```typescript
|
||||
kilocodeApiKey: opts.kilocodeApiKey as string | undefined,
|
||||
```
|
||||
|
||||
Update auth-choice help text:
|
||||
|
||||
```typescript
|
||||
.option(
|
||||
"--auth-choice <choice>",
|
||||
"Auth: setup-token|token|chutes|openai-codex|openai-api-key|openrouter-api-key|kilocode-api-key|ai-gateway-api-key|...",
|
||||
)
|
||||
```
|
||||
|
||||
### Non-Interactive Onboarding
|
||||
|
||||
#### 10. `src/commands/onboard-non-interactive/local/auth-choice.ts`
|
||||
|
||||
Add handling for `kilocode-api-key`:
|
||||
|
||||
```typescript
|
||||
if (authChoice === "kilocode-api-key") {
|
||||
const resolved = await resolveNonInteractiveApiKey({
|
||||
provider: "kilocode",
|
||||
cfg: baseConfig,
|
||||
flagValue: opts.kilocodeApiKey,
|
||||
flagName: "--kilocode-api-key",
|
||||
envVar: "KILOCODE_API_KEY",
|
||||
});
|
||||
await setKilocodeApiKey(resolved.apiKey, agentDir);
|
||||
nextConfig = applyAuthProfileConfig(nextConfig, {
|
||||
profileId: "kilocode:default",
|
||||
provider: "kilocode",
|
||||
mode: "api_key",
|
||||
});
|
||||
// ... apply default model
|
||||
}
|
||||
```
|
||||
|
||||
### Export Updates
|
||||
|
||||
#### 11. `src/commands/onboard-auth.ts`
|
||||
|
||||
Add exports:
|
||||
|
||||
```typescript
|
||||
export {
|
||||
// ... existing exports
|
||||
applyKilocodeConfig,
|
||||
applyKilocodeProviderConfig,
|
||||
KILOCODE_BASE_URL,
|
||||
} from "./onboard-auth.config-core.js";
|
||||
|
||||
export {
|
||||
// ... existing exports
|
||||
KILOCODE_DEFAULT_MODEL_REF,
|
||||
setKilocodeApiKey,
|
||||
} from "./onboard-auth.credentials.js";
|
||||
```
|
||||
|
||||
### Special Handling (Optional)
|
||||
|
||||
#### 12. `src/agents/pi-embedded-runner/cache-ttl.ts`
|
||||
|
||||
Add Kilo Gateway support for Anthropic models:
|
||||
|
||||
```typescript
|
||||
export function isCacheTtlEligibleProvider(provider: string, modelId: string): boolean {
|
||||
const normalizedProvider = provider.toLowerCase();
|
||||
const normalizedModelId = modelId.toLowerCase();
|
||||
if (normalizedProvider === "anthropic") return true;
|
||||
if (normalizedProvider === "openrouter" && normalizedModelId.startsWith("anthropic/"))
|
||||
return true;
|
||||
if (normalizedProvider === "kilocode" && normalizedModelId.startsWith("anthropic/")) return true;
|
||||
return false;
|
||||
}
|
||||
```
|
||||
|
||||
#### 13. `src/agents/transcript-policy.ts`
|
||||
|
||||
Add Kilo Gateway handling (similar to OpenRouter):
|
||||
|
||||
```typescript
|
||||
const isKilocodeGemini = provider === "kilocode" && modelId.toLowerCase().includes("gemini");
|
||||
|
||||
// Include in needsNonImageSanitize check
|
||||
const needsNonImageSanitize =
|
||||
isGoogle || isAnthropic || isMistral || isOpenRouterGemini || isKilocodeGemini;
|
||||
```
|
||||
|
||||
## Configuration Structure
|
||||
|
||||
### User Config Example
|
||||
|
||||
```json
|
||||
{
|
||||
"models": {
|
||||
"mode": "merge",
|
||||
"providers": {
|
||||
"kilocode": {
|
||||
"baseUrl": "https://api.kilo.ai/api/gateway/",
|
||||
"apiKey": "xxxxx",
|
||||
"api": "openai-completions",
|
||||
"models": [
|
||||
{
|
||||
"id": "anthropic/claude-opus-4.6",
|
||||
"name": "Anthropic: Claude Opus 4.6"
|
||||
},
|
||||
{ "id": "minimax/minimax-m2.5:free", "name": "Minimax: Minimax M2.5" }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Auth Profile Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"profiles": {
|
||||
"kilocode:default": {
|
||||
"type": "api_key",
|
||||
"provider": "kilocode",
|
||||
"key": "xxxxx"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Testing Considerations
|
||||
|
||||
1. **Unit Tests:**
|
||||
- Test `setKilocodeApiKey()` writes correct profile
|
||||
- Test `applyKilocodeConfig()` sets correct defaults
|
||||
- Test `resolveEnvApiKey("kilocode")` returns correct env var
|
||||
|
||||
2. **Integration Tests:**
|
||||
- Test setup flow with `--auth-choice kilocode-api-key`
|
||||
- Test non-interactive setup with `--kilocode-api-key`
|
||||
- Test model selection with `kilocode/` prefix
|
||||
|
||||
3. **E2E Tests:**
|
||||
- Test actual API calls through Kilo Gateway (live tests)
|
||||
|
||||
## Migration Notes
|
||||
|
||||
- No migration needed for existing users
|
||||
- New users can immediately use `kilocode-api-key` auth choice
|
||||
- Existing manual config with `kilocode` provider will continue to work
|
||||
|
||||
## Future Considerations
|
||||
|
||||
1. **Model Catalog:** If Kilo Gateway exposes a `/models` endpoint, add scanning support similar to `scanOpenRouterModels()`
|
||||
|
||||
2. **OAuth Support:** If Kilo Gateway adds OAuth, extend the auth system accordingly
|
||||
|
||||
3. **Rate Limiting:** Consider adding rate limit handling specific to Kilo Gateway if needed
|
||||
|
||||
4. **Documentation:** Add docs at `docs/providers/kilocode.md` explaining setup and usage
|
||||
|
||||
## Summary of Changes
|
||||
|
||||
| File | Change Type | Description |
|
||||
| ----------------------------------------------------------- | ----------- | ----------------------------------------------------------------------- |
|
||||
| `src/commands/onboard-auth.credentials.ts` | Add | `KILOCODE_DEFAULT_MODEL_REF`, `setKilocodeApiKey()` |
|
||||
| `src/agents/model-auth.ts` | Modify | Add `kilocode` to `envMap` |
|
||||
| `src/config/io.ts` | Modify | Add `KILOCODE_API_KEY` to shell env keys |
|
||||
| `src/commands/onboard-auth.config-core.ts` | Add | `applyKilocodeProviderConfig()`, `applyKilocodeConfig()` |
|
||||
| `src/commands/onboard-types.ts` | Modify | Add `kilocode-api-key` to `AuthChoice`, add `kilocodeApiKey` to options |
|
||||
| `src/commands/auth-choice-options.ts` | Modify | Add `kilocode` group and option |
|
||||
| `src/commands/auth-choice.preferred-provider.ts` | Modify | Add `kilocode-api-key` mapping |
|
||||
| `src/commands/auth-choice.apply.api-providers.ts` | Modify | Add `kilocode-api-key` handling |
|
||||
| `src/cli/program/register.onboard.ts` | Modify | Add `--kilocode-api-key` option |
|
||||
| `src/commands/onboard-non-interactive/local/auth-choice.ts` | Modify | Add non-interactive handling |
|
||||
| `src/commands/onboard-auth.ts` | Modify | Export new functions |
|
||||
| `src/agents/pi-embedded-runner/cache-ttl.ts` | Modify | Add kilocode support |
|
||||
| `src/agents/transcript-policy.ts` | Modify | Add kilocode Gemini handling |
|
||||
@ -65,7 +65,7 @@
|
||||
},
|
||||
{
|
||||
"source": "/cron",
|
||||
"destination": "/cron-jobs"
|
||||
"destination": "/automation/cron-jobs"
|
||||
},
|
||||
{
|
||||
"source": "/minimax",
|
||||
@ -513,11 +513,11 @@
|
||||
},
|
||||
{
|
||||
"source": "/model",
|
||||
"destination": "/models"
|
||||
"destination": "/concepts/models"
|
||||
},
|
||||
{
|
||||
"source": "/model/",
|
||||
"destination": "/models"
|
||||
"destination": "/concepts/models"
|
||||
},
|
||||
{
|
||||
"source": "/models",
|
||||
@ -535,10 +535,6 @@
|
||||
"source": "/onboarding",
|
||||
"destination": "/start/onboarding"
|
||||
},
|
||||
{
|
||||
"source": "/onboarding-config-protocol",
|
||||
"destination": "/experiments/onboarding-config-protocol"
|
||||
},
|
||||
{
|
||||
"source": "/pairing",
|
||||
"destination": "/channels/pairing"
|
||||
@ -559,10 +555,6 @@
|
||||
"source": "/presence",
|
||||
"destination": "/concepts/presence"
|
||||
},
|
||||
{
|
||||
"source": "/proposals/model-config",
|
||||
"destination": "/experiments/proposals/model-config"
|
||||
},
|
||||
{
|
||||
"source": "/provider-routing",
|
||||
"destination": "/channels/channel-routing"
|
||||
@ -583,10 +575,6 @@
|
||||
"source": "/remote-gateway-readme",
|
||||
"destination": "/gateway/remote-gateway-readme"
|
||||
},
|
||||
{
|
||||
"source": "/research/memory",
|
||||
"destination": "/experiments/research/memory"
|
||||
},
|
||||
{
|
||||
"source": "/rpc",
|
||||
"destination": "/reference/rpc"
|
||||
@ -1002,9 +990,8 @@
|
||||
"pages": [
|
||||
"tools/apply-patch",
|
||||
"brave-search",
|
||||
"perplexity",
|
||||
"tools/btw",
|
||||
"tools/diffs",
|
||||
"tools/pdf",
|
||||
"tools/elevated",
|
||||
"tools/exec",
|
||||
"tools/exec-approvals",
|
||||
@ -1012,10 +999,11 @@
|
||||
"tools/llm-task",
|
||||
"tools/lobster",
|
||||
"tools/loop-detection",
|
||||
"tools/pdf",
|
||||
"perplexity",
|
||||
"tools/reactions",
|
||||
"tools/thinking",
|
||||
"tools/web",
|
||||
"tools/btw"
|
||||
"tools/web"
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -1049,12 +1037,15 @@
|
||||
{
|
||||
"group": "Extensions",
|
||||
"pages": [
|
||||
"plugins/building-extensions",
|
||||
"plugins/architecture",
|
||||
"plugins/community",
|
||||
"plugins/bundles",
|
||||
"plugins/voice-call",
|
||||
"plugins/zalouser",
|
||||
"plugins/manifest",
|
||||
"plugins/agent-tools",
|
||||
"tools/capability-cookbook",
|
||||
"prose"
|
||||
]
|
||||
},
|
||||
@ -1112,11 +1103,13 @@
|
||||
"providers/claude-max-api-proxy",
|
||||
"providers/deepgram",
|
||||
"providers/github-copilot",
|
||||
"providers/google",
|
||||
"providers/huggingface",
|
||||
"providers/kilocode",
|
||||
"providers/litellm",
|
||||
"providers/glm",
|
||||
"providers/minimax",
|
||||
"providers/modelstudio",
|
||||
"providers/moonshot",
|
||||
"providers/mistral",
|
||||
"providers/nvidia",
|
||||
@ -1125,13 +1118,17 @@
|
||||
"providers/opencode-go",
|
||||
"providers/opencode",
|
||||
"providers/openrouter",
|
||||
"providers/perplexity-provider",
|
||||
"providers/qianfan",
|
||||
"providers/qwen",
|
||||
"providers/sglang",
|
||||
"providers/synthetic",
|
||||
"providers/together",
|
||||
"providers/vercel-ai-gateway",
|
||||
"providers/venice",
|
||||
"providers/vllm",
|
||||
"providers/volcengine",
|
||||
"providers/xai",
|
||||
"providers/xiaomi",
|
||||
"providers/zai"
|
||||
]
|
||||
@ -1212,6 +1209,7 @@
|
||||
"pages": [
|
||||
"gateway/security/index",
|
||||
"gateway/sandboxing",
|
||||
"gateway/openshell",
|
||||
"gateway/sandbox-vs-tool-policy-vs-elevated"
|
||||
]
|
||||
},
|
||||
@ -1357,21 +1355,6 @@
|
||||
{
|
||||
"group": "Release policy",
|
||||
"pages": ["reference/RELEASING", "reference/test"]
|
||||
},
|
||||
{
|
||||
"group": "Experiments",
|
||||
"pages": [
|
||||
"design/kilo-gateway-integration",
|
||||
"experiments/onboarding-config-protocol",
|
||||
"experiments/plans/acp-thread-bound-agents",
|
||||
"experiments/plans/acp-unified-streaming-refactor",
|
||||
"experiments/plans/browser-evaluate-cdp-refactor",
|
||||
"experiments/plans/openresponses-gateway",
|
||||
"experiments/plans/pty-process-supervision",
|
||||
"experiments/plans/session-binding-channel-agnostic",
|
||||
"experiments/research/memory",
|
||||
"experiments/proposals/model-config"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -1600,13 +1583,13 @@
|
||||
"pages": [
|
||||
"zh-CN/tools/apply-patch",
|
||||
"zh-CN/brave-search",
|
||||
"zh-CN/perplexity",
|
||||
"zh-CN/tools/elevated",
|
||||
"zh-CN/tools/exec",
|
||||
"zh-CN/tools/exec-approvals",
|
||||
"zh-CN/tools/firecrawl",
|
||||
"zh-CN/tools/llm-task",
|
||||
"zh-CN/tools/lobster",
|
||||
"zh-CN/perplexity",
|
||||
"zh-CN/tools/reactions",
|
||||
"zh-CN/tools/thinking",
|
||||
"zh-CN/tools/web"
|
||||
@ -1642,6 +1625,7 @@
|
||||
{
|
||||
"group": "扩展",
|
||||
"pages": [
|
||||
"zh-CN/plugins/architecture",
|
||||
"zh-CN/plugins/voice-call",
|
||||
"zh-CN/plugins/zalouser",
|
||||
"zh-CN/plugins/manifest",
|
||||
@ -1937,27 +1921,6 @@
|
||||
{
|
||||
"group": "发布策略",
|
||||
"pages": ["zh-CN/reference/RELEASING", "zh-CN/reference/test"]
|
||||
},
|
||||
{
|
||||
"group": "实验性功能",
|
||||
"pages": [
|
||||
"zh-CN/experiments/onboarding-config-protocol",
|
||||
"zh-CN/experiments/plans/openresponses-gateway",
|
||||
"zh-CN/experiments/plans/cron-add-hardening",
|
||||
"zh-CN/experiments/plans/group-policy-hardening",
|
||||
"zh-CN/experiments/research/memory",
|
||||
"zh-CN/experiments/proposals/model-config"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "重构方案",
|
||||
"pages": [
|
||||
"zh-CN/refactor/clawnet",
|
||||
"zh-CN/refactor/exec-host",
|
||||
"zh-CN/refactor/outbound-session-mirroring",
|
||||
"zh-CN/refactor/plugin-sdk",
|
||||
"zh-CN/refactor/strict-config"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@ -1,43 +0,0 @@
|
||||
---
|
||||
summary: "RPC protocol notes for setup wizard and config schema"
|
||||
read_when: "Changing setup wizard steps or config schema endpoints"
|
||||
title: "Onboarding and Config Protocol"
|
||||
---
|
||||
|
||||
# Onboarding + Config Protocol
|
||||
|
||||
Purpose: shared onboarding + config surfaces across CLI, macOS app, and Web UI.
|
||||
|
||||
## Components
|
||||
|
||||
- Wizard engine (shared session + prompts + onboarding state).
|
||||
- CLI onboarding uses the same wizard flow as the UI clients.
|
||||
- Gateway RPC exposes wizard + config schema endpoints.
|
||||
- macOS onboarding uses the wizard step model.
|
||||
- Web UI renders config forms from JSON Schema + UI hints.
|
||||
|
||||
## Gateway RPC
|
||||
|
||||
- `wizard.start` params: `{ mode?: "local"|"remote", workspace?: string }`
|
||||
- `wizard.next` params: `{ sessionId, answer?: { stepId, value? } }`
|
||||
- `wizard.cancel` params: `{ sessionId }`
|
||||
- `wizard.status` params: `{ sessionId }`
|
||||
- `config.schema` params: `{}`
|
||||
- `config.schema.lookup` params: `{ path }`
|
||||
- `path` accepts standard config segments plus slash-delimited plugin ids, for example `plugins.entries.pack/one.config`.
|
||||
|
||||
Responses (shape)
|
||||
|
||||
- Wizard: `{ sessionId, done, step?, status?, error? }`
|
||||
- Config schema: `{ schema, uiHints, version, generatedAt }`
|
||||
- Config schema lookup: `{ path, schema, hint?, hintPath?, children[] }`
|
||||
|
||||
## UI Hints
|
||||
|
||||
- `uiHints` keyed by path; optional metadata (label/help/group/order/advanced/sensitive/placeholder).
|
||||
- Sensitive fields render as password inputs; no redaction layer.
|
||||
- Unsupported schema nodes fall back to the raw JSON editor.
|
||||
|
||||
## Notes
|
||||
|
||||
- This doc is the single place to track protocol refactors for onboarding/config.
|
||||
@ -1,375 +0,0 @@
|
||||
# ACP Persistent Bindings for Discord Channels and Telegram Topics
|
||||
|
||||
Status: Draft
|
||||
|
||||
## Summary
|
||||
|
||||
Introduce persistent ACP bindings that map:
|
||||
|
||||
- Discord channels (and existing threads, where needed), and
|
||||
- Telegram forum topics in groups/supergroups (`chatId:topic:topicId`)
|
||||
|
||||
to long-lived ACP sessions, with binding state stored in top-level `bindings[]` entries using explicit binding types.
|
||||
|
||||
This makes ACP usage in high-traffic messaging channels predictable and durable, so users can create dedicated channels/topics such as `codex`, `claude-1`, or `claude-myrepo`.
|
||||
|
||||
## Why
|
||||
|
||||
Current thread-bound ACP behavior is optimized for ephemeral Discord thread workflows. Telegram does not have the same thread model; it has forum topics in groups/supergroups. Users want stable, always-on ACP “workspaces” in chat surfaces, not only temporary thread sessions.
|
||||
|
||||
## Goals
|
||||
|
||||
- Support durable ACP binding for:
|
||||
- Discord channels/threads
|
||||
- Telegram forum topics (groups/supergroups)
|
||||
- Make binding source-of-truth config-driven.
|
||||
- Keep `/acp`, `/new`, `/reset`, `/focus`, and delivery behavior consistent across Discord and Telegram.
|
||||
- Preserve existing temporary binding flows for ad-hoc usage.
|
||||
|
||||
## Non-Goals
|
||||
|
||||
- Full redesign of ACP runtime/session internals.
|
||||
- Removing existing ephemeral binding flows.
|
||||
- Expanding to every channel in the first iteration.
|
||||
- Implementing Telegram channel direct-messages topics (`direct_messages_topic_id`) in this phase.
|
||||
- Implementing Telegram private-chat topic variants in this phase.
|
||||
|
||||
## UX Direction
|
||||
|
||||
### 1) Two binding types
|
||||
|
||||
- **Persistent binding**: saved in config, reconciled on startup, intended for “named workspace” channels/topics.
|
||||
- **Temporary binding**: runtime-only, expires by idle/max-age policy.
|
||||
|
||||
### 2) Command behavior
|
||||
|
||||
- `/acp spawn ... --thread here|auto|off` remains available.
|
||||
- Add explicit bind lifecycle controls:
|
||||
- `/acp bind [session|agent] [--persist]`
|
||||
- `/acp unbind [--persist]`
|
||||
- `/acp status` includes whether binding is `persistent` or `temporary`.
|
||||
- In bound conversations, `/new` and `/reset` reset the bound ACP session in place and keep the binding attached.
|
||||
|
||||
### 3) Conversation identity
|
||||
|
||||
- Use canonical conversation IDs:
|
||||
- Discord: channel/thread ID.
|
||||
- Telegram topic: `chatId:topic:topicId`.
|
||||
- Never key Telegram bindings by bare topic ID alone.
|
||||
|
||||
## Config Model (Proposed)
|
||||
|
||||
Unify routing and persistent ACP binding configuration in top-level `bindings[]` with explicit `type` discriminator:
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"agents": {
|
||||
"list": [
|
||||
{
|
||||
"id": "main",
|
||||
"default": true,
|
||||
"workspace": "~/.openclaw/workspace-main",
|
||||
"runtime": { "type": "embedded" },
|
||||
},
|
||||
{
|
||||
"id": "codex",
|
||||
"workspace": "~/.openclaw/workspace-codex",
|
||||
"runtime": {
|
||||
"type": "acp",
|
||||
"acp": {
|
||||
"agent": "codex",
|
||||
"backend": "acpx",
|
||||
"mode": "persistent",
|
||||
"cwd": "/workspace/repo-a",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"id": "claude",
|
||||
"workspace": "~/.openclaw/workspace-claude",
|
||||
"runtime": {
|
||||
"type": "acp",
|
||||
"acp": {
|
||||
"agent": "claude",
|
||||
"backend": "acpx",
|
||||
"mode": "persistent",
|
||||
"cwd": "/workspace/repo-b",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
"acp": {
|
||||
"enabled": true,
|
||||
"backend": "acpx",
|
||||
"allowedAgents": ["codex", "claude"],
|
||||
},
|
||||
"bindings": [
|
||||
// Route bindings (existing behavior)
|
||||
{
|
||||
"type": "route",
|
||||
"agentId": "main",
|
||||
"match": { "channel": "discord", "accountId": "default" },
|
||||
},
|
||||
{
|
||||
"type": "route",
|
||||
"agentId": "main",
|
||||
"match": { "channel": "telegram", "accountId": "default" },
|
||||
},
|
||||
// Persistent ACP conversation bindings
|
||||
{
|
||||
"type": "acp",
|
||||
"agentId": "codex",
|
||||
"match": {
|
||||
"channel": "discord",
|
||||
"accountId": "default",
|
||||
"peer": { "kind": "channel", "id": "222222222222222222" },
|
||||
},
|
||||
"acp": {
|
||||
"label": "codex-main",
|
||||
"mode": "persistent",
|
||||
"cwd": "/workspace/repo-a",
|
||||
"backend": "acpx",
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "acp",
|
||||
"agentId": "claude",
|
||||
"match": {
|
||||
"channel": "discord",
|
||||
"accountId": "default",
|
||||
"peer": { "kind": "channel", "id": "333333333333333333" },
|
||||
},
|
||||
"acp": {
|
||||
"label": "claude-repo-b",
|
||||
"mode": "persistent",
|
||||
"cwd": "/workspace/repo-b",
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "acp",
|
||||
"agentId": "codex",
|
||||
"match": {
|
||||
"channel": "telegram",
|
||||
"accountId": "default",
|
||||
"peer": { "kind": "group", "id": "-1001234567890:topic:42" },
|
||||
},
|
||||
"acp": {
|
||||
"label": "tg-codex-42",
|
||||
"mode": "persistent",
|
||||
},
|
||||
},
|
||||
],
|
||||
"channels": {
|
||||
"discord": {
|
||||
"guilds": {
|
||||
"111111111111111111": {
|
||||
"channels": {
|
||||
"222222222222222222": {
|
||||
"enabled": true,
|
||||
"requireMention": false,
|
||||
},
|
||||
"333333333333333333": {
|
||||
"enabled": true,
|
||||
"requireMention": false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"telegram": {
|
||||
"groups": {
|
||||
"-1001234567890": {
|
||||
"topics": {
|
||||
"42": {
|
||||
"requireMention": false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Minimal Example (No Per-Binding ACP Overrides)
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"agents": {
|
||||
"list": [
|
||||
{ "id": "main", "default": true, "runtime": { "type": "embedded" } },
|
||||
{
|
||||
"id": "codex",
|
||||
"runtime": {
|
||||
"type": "acp",
|
||||
"acp": { "agent": "codex", "backend": "acpx", "mode": "persistent" },
|
||||
},
|
||||
},
|
||||
{
|
||||
"id": "claude",
|
||||
"runtime": {
|
||||
"type": "acp",
|
||||
"acp": { "agent": "claude", "backend": "acpx", "mode": "persistent" },
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
"acp": { "enabled": true, "backend": "acpx" },
|
||||
"bindings": [
|
||||
{
|
||||
"type": "route",
|
||||
"agentId": "main",
|
||||
"match": { "channel": "discord", "accountId": "default" },
|
||||
},
|
||||
{
|
||||
"type": "route",
|
||||
"agentId": "main",
|
||||
"match": { "channel": "telegram", "accountId": "default" },
|
||||
},
|
||||
|
||||
{
|
||||
"type": "acp",
|
||||
"agentId": "codex",
|
||||
"match": {
|
||||
"channel": "discord",
|
||||
"accountId": "default",
|
||||
"peer": { "kind": "channel", "id": "222222222222222222" },
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "acp",
|
||||
"agentId": "claude",
|
||||
"match": {
|
||||
"channel": "discord",
|
||||
"accountId": "default",
|
||||
"peer": { "kind": "channel", "id": "333333333333333333" },
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "acp",
|
||||
"agentId": "codex",
|
||||
"match": {
|
||||
"channel": "telegram",
|
||||
"accountId": "default",
|
||||
"peer": { "kind": "group", "id": "-1009876543210:topic:5" },
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- `bindings[].type` is explicit:
|
||||
- `route`: normal agent routing.
|
||||
- `acp`: persistent ACP harness binding for a matched conversation.
|
||||
- For `type: "acp"`, `match.peer.id` is the canonical conversation key:
|
||||
- Discord channel/thread: raw channel/thread ID.
|
||||
- Telegram topic: `chatId:topic:topicId`.
|
||||
- `bindings[].acp.backend` is optional. Backend fallback order:
|
||||
1. `bindings[].acp.backend`
|
||||
2. `agents.list[].runtime.acp.backend`
|
||||
3. global `acp.backend`
|
||||
- `mode`, `cwd`, and `label` follow the same override pattern (`binding override -> agent runtime default -> global/default behavior`).
|
||||
- Keep existing `session.threadBindings.*` and `channels.discord.threadBindings.*` for temporary binding policies.
|
||||
- Persistent entries declare desired state; runtime reconciles to actual ACP sessions/bindings.
|
||||
- One active ACP binding per conversation node is the intended model.
|
||||
- Backward compatibility: missing `type` is interpreted as `route` for legacy entries.
|
||||
|
||||
### Backend Selection
|
||||
|
||||
- ACP session initialization already uses configured backend selection during spawn (`acp.backend` today).
|
||||
- This proposal extends spawn/reconcile logic to prefer typed ACP binding overrides:
|
||||
- `bindings[].acp.backend` for conversation-local override.
|
||||
- `agents.list[].runtime.acp.backend` for per-agent defaults.
|
||||
- If no override exists, keep current behavior (`acp.backend` default).
|
||||
|
||||
## Architecture Fit in Current System
|
||||
|
||||
### Reuse existing components
|
||||
|
||||
- `SessionBindingService` already supports channel-agnostic conversation references.
|
||||
- ACP spawn/bind flows already support binding through service APIs.
|
||||
- Telegram already carries topic/thread context via `MessageThreadId` and `chatId`.
|
||||
|
||||
### New/extended components
|
||||
|
||||
- **Telegram binding adapter** (parallel to Discord adapter):
|
||||
- register adapter per Telegram account,
|
||||
- resolve/list/bind/unbind/touch by canonical conversation ID.
|
||||
- **Typed binding resolver/index**:
|
||||
- split `bindings[]` into `route` and `acp` views,
|
||||
- keep `resolveAgentRoute` on `route` bindings only,
|
||||
- resolve persistent ACP intent from `acp` bindings only.
|
||||
- **Inbound binding resolution for Telegram**:
|
||||
- resolve bound session before route finalization (Discord already does this).
|
||||
- **Persistent binding reconciler**:
|
||||
- on startup: load configured top-level `type: "acp"` bindings, ensure ACP sessions exist, ensure bindings exist.
|
||||
- on config change: apply deltas safely.
|
||||
- **Cutover model**:
|
||||
- no channel-local ACP binding fallback is read,
|
||||
- persistent ACP bindings are sourced only from top-level `bindings[].type="acp"` entries.
|
||||
|
||||
## Phased Delivery
|
||||
|
||||
### Phase 1: Typed binding schema foundation
|
||||
|
||||
- Extend config schema to support `bindings[].type` discriminator:
|
||||
- `route`,
|
||||
- `acp` with optional `acp` override object (`mode`, `backend`, `cwd`, `label`).
|
||||
- Extend agent schema with runtime descriptor to mark ACP-native agents (`agents.list[].runtime.type`).
|
||||
- Add parser/indexer split for route vs ACP bindings.
|
||||
|
||||
### Phase 2: Runtime resolution + Discord/Telegram parity
|
||||
|
||||
- Resolve persistent ACP bindings from top-level `type: "acp"` entries for:
|
||||
- Discord channels/threads,
|
||||
- Telegram forum topics (`chatId:topic:topicId` canonical IDs).
|
||||
- Implement Telegram binding adapter and inbound bound-session override parity with Discord.
|
||||
- Do not include Telegram direct/private topic variants in this phase.
|
||||
|
||||
### Phase 3: Command parity and resets
|
||||
|
||||
- Align `/acp`, `/new`, `/reset`, and `/focus` behavior in bound Telegram/Discord conversations.
|
||||
- Ensure binding survives reset flows as configured.
|
||||
|
||||
### Phase 4: Hardening
|
||||
|
||||
- Better diagnostics (`/acp status`, startup reconciliation logs).
|
||||
- Conflict handling and health checks.
|
||||
|
||||
## Guardrails and Policy
|
||||
|
||||
- Respect ACP enablement and sandbox restrictions exactly as today.
|
||||
- Keep explicit account scoping (`accountId`) to avoid cross-account bleed.
|
||||
- Fail closed on ambiguous routing.
|
||||
- Keep mention/access policy behavior explicit per channel config.
|
||||
|
||||
## Testing Plan
|
||||
|
||||
- Unit:
|
||||
- conversation ID normalization (especially Telegram topic IDs),
|
||||
- reconciler create/update/delete paths,
|
||||
- `/acp bind --persist` and unbind flows.
|
||||
- Integration:
|
||||
- inbound Telegram topic -> bound ACP session resolution,
|
||||
- inbound Discord channel/thread -> persistent binding precedence.
|
||||
- Regression:
|
||||
- temporary bindings continue to work,
|
||||
- unbound channels/topics keep current routing behavior.
|
||||
|
||||
## Open Questions
|
||||
|
||||
- Should `/acp spawn --thread auto` in Telegram topic default to `here`?
|
||||
- Should persistent bindings always bypass mention-gating in bound conversations, or require explicit `requireMention=false`?
|
||||
- Should `/focus` gain `--persist` as an alias for `/acp bind --persist`?
|
||||
|
||||
## Rollout
|
||||
|
||||
- Ship as opt-in per conversation (`bindings[].type="acp"` entry present).
|
||||
- Start with Discord + Telegram only.
|
||||
- Add docs with examples for:
|
||||
- “one channel/topic per agent”
|
||||
- “multiple channels/topics per same agent with different `cwd`”
|
||||
- “team naming patterns (`codex-1`, `claude-repo-x`)".
|
||||
@ -1,800 +0,0 @@
|
||||
---
|
||||
summary: "Integrate ACP coding agents via a first-class ACP control plane in core and plugin-backed runtimes (acpx first)"
|
||||
owner: "onutc"
|
||||
status: "draft"
|
||||
last_updated: "2026-02-25"
|
||||
title: "ACP Thread Bound Agents"
|
||||
---
|
||||
|
||||
# ACP Thread Bound Agents
|
||||
|
||||
## Overview
|
||||
|
||||
This plan defines how OpenClaw should support ACP coding agents in thread-capable channels (Discord first) with production-level lifecycle and recovery.
|
||||
|
||||
Related document:
|
||||
|
||||
- [Unified Runtime Streaming Refactor Plan](/experiments/plans/acp-unified-streaming-refactor)
|
||||
|
||||
Target user experience:
|
||||
|
||||
- a user spawns or focuses an ACP session into a thread
|
||||
- user messages in that thread route to the bound ACP session
|
||||
- agent output streams back to the same thread persona
|
||||
- session can be persistent or one shot with explicit cleanup controls
|
||||
|
||||
## Decision summary
|
||||
|
||||
Long term recommendation is a hybrid architecture:
|
||||
|
||||
- OpenClaw core owns ACP control plane concerns
|
||||
- session identity and metadata
|
||||
- thread binding and routing decisions
|
||||
- delivery invariants and duplicate suppression
|
||||
- lifecycle cleanup and recovery semantics
|
||||
- ACP runtime backend is pluggable
|
||||
- first backend is an acpx-backed plugin service
|
||||
- runtime does ACP transport, queueing, cancel, reconnect
|
||||
|
||||
OpenClaw should not reimplement ACP transport internals in core.
|
||||
OpenClaw should not rely on a pure plugin-only interception path for routing.
|
||||
|
||||
## North-star architecture (holy grail)
|
||||
|
||||
Treat ACP as a first-class control plane in OpenClaw, with pluggable runtime adapters.
|
||||
|
||||
Non-negotiable invariants:
|
||||
|
||||
- every ACP thread binding references a valid ACP session record
|
||||
- every ACP session has explicit lifecycle state (`creating`, `idle`, `running`, `cancelling`, `closed`, `error`)
|
||||
- every ACP run has explicit run state (`queued`, `running`, `completed`, `failed`, `cancelled`)
|
||||
- spawn, bind, and initial enqueue are atomic
|
||||
- command retries are idempotent (no duplicate runs or duplicate Discord outputs)
|
||||
- bound-thread channel output is a projection of ACP run events, never ad-hoc side effects
|
||||
|
||||
Long-term ownership model:
|
||||
|
||||
- `AcpSessionManager` is the single ACP writer and orchestrator
|
||||
- manager lives in gateway process first; can be moved to a dedicated sidecar later behind the same interface
|
||||
- per ACP session key, manager owns one in-memory actor (serialized command execution)
|
||||
- adapters (`acpx`, future backends) are transport/runtime implementations only
|
||||
|
||||
Long-term persistence model:
|
||||
|
||||
- move ACP control-plane state to a dedicated SQLite store (WAL mode) under OpenClaw state dir
|
||||
- keep `SessionEntry.acp` as compatibility projection during migration, not source-of-truth
|
||||
- store ACP events append-only to support replay, crash recovery, and deterministic delivery
|
||||
|
||||
### Delivery strategy (bridge to holy-grail)
|
||||
|
||||
- short-term bridge
|
||||
- keep current thread binding mechanics and existing ACP config surface
|
||||
- fix metadata-gap bugs and route ACP turns through a single core ACP branch
|
||||
- add idempotency keys and fail-closed routing checks immediately
|
||||
- long-term cutover
|
||||
- move ACP source-of-truth to control-plane DB + actors
|
||||
- make bound-thread delivery purely event-projection based
|
||||
- remove legacy fallback behavior that depends on opportunistic session-entry metadata
|
||||
|
||||
## Why not pure plugin only
|
||||
|
||||
Current plugin hooks are not sufficient for end to end ACP session routing without core changes.
|
||||
|
||||
- inbound routing from thread binding resolves to a session key in core dispatch first
|
||||
- message hooks are fire-and-forget and cannot short-circuit the main reply path
|
||||
- plugin commands are good for control operations but not for replacing core per-turn dispatch flow
|
||||
|
||||
Result:
|
||||
|
||||
- ACP runtime can be pluginized
|
||||
- ACP routing branch must exist in core
|
||||
|
||||
## Existing foundation to reuse
|
||||
|
||||
Already implemented and should remain canonical:
|
||||
|
||||
- thread binding target supports `subagent` and `acp`
|
||||
- inbound thread routing override resolves by binding before normal dispatch
|
||||
- outbound thread identity via webhook in reply delivery
|
||||
- `/focus` and `/unfocus` flow with ACP target compatibility
|
||||
- persistent binding store with restore on startup
|
||||
- unbind lifecycle on archive, delete, unfocus, reset, and delete
|
||||
|
||||
This plan extends that foundation rather than replacing it.
|
||||
|
||||
## Architecture
|
||||
|
||||
### Boundary model
|
||||
|
||||
Core (must be in OpenClaw core):
|
||||
|
||||
- ACP session-mode dispatch branch in the reply pipeline
|
||||
- delivery arbitration to avoid parent plus thread duplication
|
||||
- ACP control-plane persistence (with `SessionEntry.acp` compatibility projection during migration)
|
||||
- lifecycle unbind and runtime detach semantics tied to session reset/delete
|
||||
|
||||
Plugin backend (acpx implementation):
|
||||
|
||||
- ACP runtime worker supervision
|
||||
- acpx process invocation and event parsing
|
||||
- ACP command handlers (`/acp ...`) and operator UX
|
||||
- backend-specific config defaults and diagnostics
|
||||
|
||||
### Runtime ownership model
|
||||
|
||||
- one gateway process owns ACP orchestration state
|
||||
- ACP execution runs in supervised child processes via acpx backend
|
||||
- process strategy is long lived per active ACP session key, not per message
|
||||
|
||||
This avoids startup cost on every prompt and keeps cancel and reconnect semantics reliable.
|
||||
|
||||
### Core runtime contract
|
||||
|
||||
Add a core ACP runtime contract so routing code does not depend on CLI details and can switch backends without changing dispatch logic:
|
||||
|
||||
```ts
|
||||
export type AcpRuntimePromptMode = "prompt" | "steer";
|
||||
|
||||
export type AcpRuntimeHandle = {
|
||||
sessionKey: string;
|
||||
backend: string;
|
||||
runtimeSessionName: string;
|
||||
};
|
||||
|
||||
export type AcpRuntimeEvent =
|
||||
| { type: "text_delta"; stream: "output" | "thought"; text: string }
|
||||
| { type: "tool_call"; name: string; argumentsText: string }
|
||||
| { type: "done"; usage?: Record<string, number> }
|
||||
| { type: "error"; code: string; message: string; retryable?: boolean };
|
||||
|
||||
export interface AcpRuntime {
|
||||
ensureSession(input: {
|
||||
sessionKey: string;
|
||||
agent: string;
|
||||
mode: "persistent" | "oneshot";
|
||||
cwd?: string;
|
||||
env?: Record<string, string>;
|
||||
idempotencyKey: string;
|
||||
}): Promise<AcpRuntimeHandle>;
|
||||
|
||||
submit(input: {
|
||||
handle: AcpRuntimeHandle;
|
||||
text: string;
|
||||
mode: AcpRuntimePromptMode;
|
||||
idempotencyKey: string;
|
||||
}): Promise<{ runtimeRunId: string }>;
|
||||
|
||||
stream(input: {
|
||||
handle: AcpRuntimeHandle;
|
||||
runtimeRunId: string;
|
||||
onEvent: (event: AcpRuntimeEvent) => Promise<void> | void;
|
||||
signal?: AbortSignal;
|
||||
}): Promise<void>;
|
||||
|
||||
cancel(input: {
|
||||
handle: AcpRuntimeHandle;
|
||||
runtimeRunId?: string;
|
||||
reason?: string;
|
||||
idempotencyKey: string;
|
||||
}): Promise<void>;
|
||||
|
||||
close(input: { handle: AcpRuntimeHandle; reason: string; idempotencyKey: string }): Promise<void>;
|
||||
|
||||
health?(): Promise<{ ok: boolean; details?: string }>;
|
||||
}
|
||||
```
|
||||
|
||||
Implementation detail:
|
||||
|
||||
- first backend: `AcpxRuntime` shipped as a plugin service
|
||||
- core resolves runtime via registry and fails with explicit operator error when no ACP runtime backend is available
|
||||
|
||||
### Control-plane data model and persistence
|
||||
|
||||
Long-term source-of-truth is a dedicated ACP SQLite database (WAL mode), for transactional updates and crash-safe recovery:
|
||||
|
||||
- `acp_sessions`
|
||||
- `session_key` (pk), `backend`, `agent`, `mode`, `cwd`, `state`, `created_at`, `updated_at`, `last_error`
|
||||
- `acp_runs`
|
||||
- `run_id` (pk), `session_key` (fk), `state`, `requester_message_id`, `idempotency_key`, `started_at`, `ended_at`, `error_code`, `error_message`
|
||||
- `acp_bindings`
|
||||
- `binding_key` (pk), `thread_id`, `channel_id`, `account_id`, `session_key` (fk), `expires_at`, `bound_at`
|
||||
- `acp_events`
|
||||
- `event_id` (pk), `run_id` (fk), `seq`, `kind`, `payload_json`, `created_at`
|
||||
- `acp_delivery_checkpoint`
|
||||
- `run_id` (pk/fk), `last_event_seq`, `last_discord_message_id`, `updated_at`
|
||||
- `acp_idempotency`
|
||||
- `scope`, `idempotency_key`, `result_json`, `created_at`, unique `(scope, idempotency_key)`
|
||||
|
||||
```ts
|
||||
export type AcpSessionMeta = {
|
||||
backend: string;
|
||||
agent: string;
|
||||
runtimeSessionName: string;
|
||||
mode: "persistent" | "oneshot";
|
||||
cwd?: string;
|
||||
state: "idle" | "running" | "error";
|
||||
lastActivityAt: number;
|
||||
lastError?: string;
|
||||
};
|
||||
```
|
||||
|
||||
Storage rules:
|
||||
|
||||
- keep `SessionEntry.acp` as a compatibility projection during migration
|
||||
- process ids and sockets stay in memory only
|
||||
- durable lifecycle and run status live in ACP DB, not generic session JSON
|
||||
- if runtime owner dies, gateway rehydrates from ACP DB and resumes from checkpoints
|
||||
|
||||
### Routing and delivery
|
||||
|
||||
Inbound:
|
||||
|
||||
- keep current thread binding lookup as first routing step
|
||||
- if bound target is ACP session, route to ACP runtime branch instead of `getReplyFromConfig`
|
||||
- explicit `/acp steer` command uses `mode: "steer"`
|
||||
|
||||
Outbound:
|
||||
|
||||
- ACP event stream is normalized to OpenClaw reply chunks
|
||||
- delivery target is resolved through existing bound destination path
|
||||
- when a bound thread is active for that session turn, parent channel completion is suppressed
|
||||
|
||||
Streaming policy:
|
||||
|
||||
- stream partial output with coalescing window
|
||||
- configurable min interval and max chunk bytes to stay under Discord rate limits
|
||||
- final message always emitted on completion or failure
|
||||
|
||||
### State machines and transaction boundaries
|
||||
|
||||
Session state machine:
|
||||
|
||||
- `creating -> idle -> running -> idle`
|
||||
- `running -> cancelling -> idle | error`
|
||||
- `idle -> closed`
|
||||
- `error -> idle | closed`
|
||||
|
||||
Run state machine:
|
||||
|
||||
- `queued -> running -> completed`
|
||||
- `running -> failed | cancelled`
|
||||
- `queued -> cancelled`
|
||||
|
||||
Required transaction boundaries:
|
||||
|
||||
- spawn transaction
|
||||
- create ACP session row
|
||||
- create/update ACP thread binding row
|
||||
- enqueue initial run row
|
||||
- close transaction
|
||||
- mark session closed
|
||||
- delete/expire binding rows
|
||||
- write final close event
|
||||
- cancel transaction
|
||||
- mark target run cancelling/cancelled with idempotency key
|
||||
|
||||
No partial success is allowed across these boundaries.
|
||||
|
||||
### Per-session actor model
|
||||
|
||||
`AcpSessionManager` runs one actor per ACP session key:
|
||||
|
||||
- actor mailbox serializes `submit`, `cancel`, `close`, and `stream` side effects
|
||||
- actor owns runtime handle hydration and runtime adapter process lifecycle for that session
|
||||
- actor writes run events in-order (`seq`) before any Discord delivery
|
||||
- actor updates delivery checkpoints after successful outbound send
|
||||
|
||||
This removes cross-turn races and prevents duplicate or out-of-order thread output.
|
||||
|
||||
### Idempotency and delivery projection
|
||||
|
||||
All external ACP actions must carry idempotency keys:
|
||||
|
||||
- spawn idempotency key
|
||||
- prompt/steer idempotency key
|
||||
- cancel idempotency key
|
||||
- close idempotency key
|
||||
|
||||
Delivery rules:
|
||||
|
||||
- Discord messages are derived from `acp_events` plus `acp_delivery_checkpoint`
|
||||
- retries resume from checkpoint without re-sending already delivered chunks
|
||||
- final reply emission is exactly-once per run from projection logic
|
||||
|
||||
### Recovery and self-healing
|
||||
|
||||
On gateway start:
|
||||
|
||||
- load non-terminal ACP sessions (`creating`, `idle`, `running`, `cancelling`, `error`)
|
||||
- recreate actors lazily on first inbound event or eagerly under configured cap
|
||||
- reconcile any `running` runs missing heartbeats and mark `failed` or recover via adapter
|
||||
|
||||
On inbound Discord thread message:
|
||||
|
||||
- if binding exists but ACP session is missing, fail closed with explicit stale-binding message
|
||||
- optionally auto-unbind stale binding after operator-safe validation
|
||||
- never silently route stale ACP bindings to normal LLM path
|
||||
|
||||
### Lifecycle and safety
|
||||
|
||||
Supported operations:
|
||||
|
||||
- cancel current run: `/acp cancel`
|
||||
- unbind thread: `/unfocus`
|
||||
- close ACP session: `/acp close`
|
||||
- auto close idle sessions by effective TTL
|
||||
|
||||
TTL policy:
|
||||
|
||||
- effective TTL is minimum of
|
||||
- global/session TTL
|
||||
- Discord thread binding TTL
|
||||
- ACP runtime owner TTL
|
||||
|
||||
Safety controls:
|
||||
|
||||
- allowlist ACP agents by name
|
||||
- restrict workspace roots for ACP sessions
|
||||
- env allowlist passthrough
|
||||
- max concurrent ACP sessions per account and globally
|
||||
- bounded restart backoff for runtime crashes
|
||||
|
||||
## Config surface
|
||||
|
||||
Core keys:
|
||||
|
||||
- `acp.enabled`
|
||||
- `acp.dispatch.enabled` (independent ACP routing kill switch)
|
||||
- `acp.backend` (default `acpx`)
|
||||
- `acp.defaultAgent`
|
||||
- `acp.allowedAgents[]`
|
||||
- `acp.maxConcurrentSessions`
|
||||
- `acp.stream.coalesceIdleMs`
|
||||
- `acp.stream.maxChunkChars`
|
||||
- `acp.runtime.ttlMinutes`
|
||||
- `acp.controlPlane.store` (`sqlite` default)
|
||||
- `acp.controlPlane.storePath`
|
||||
- `acp.controlPlane.recovery.eagerActors`
|
||||
- `acp.controlPlane.recovery.reconcileRunningAfterMs`
|
||||
- `acp.controlPlane.checkpoint.flushEveryEvents`
|
||||
- `acp.controlPlane.checkpoint.flushEveryMs`
|
||||
- `acp.idempotency.ttlHours`
|
||||
- `channels.discord.threadBindings.spawnAcpSessions`
|
||||
|
||||
Plugin/backend keys (acpx plugin section):
|
||||
|
||||
- backend command/path overrides
|
||||
- backend env allowlist
|
||||
- backend per-agent presets
|
||||
- backend startup/stop timeouts
|
||||
- backend max inflight runs per session
|
||||
|
||||
## Implementation specification
|
||||
|
||||
### Control-plane modules (new)
|
||||
|
||||
Add dedicated ACP control-plane modules in core:
|
||||
|
||||
- `src/acp/control-plane/manager.ts`
|
||||
- owns ACP actors, lifecycle transitions, command serialization
|
||||
- `src/acp/control-plane/store.ts`
|
||||
- SQLite schema management, transactions, query helpers
|
||||
- `src/acp/control-plane/events.ts`
|
||||
- typed ACP event definitions and serialization
|
||||
- `src/acp/control-plane/checkpoint.ts`
|
||||
- durable delivery checkpoints and replay cursors
|
||||
- `src/acp/control-plane/idempotency.ts`
|
||||
- idempotency key reservation and response replay
|
||||
- `src/acp/control-plane/recovery.ts`
|
||||
- boot-time reconciliation and actor rehydrate plan
|
||||
|
||||
Compatibility bridge modules:
|
||||
|
||||
- `src/acp/runtime/session-meta.ts`
|
||||
- remains temporarily for projection into `SessionEntry.acp`
|
||||
- must stop being source-of-truth after migration cutover
|
||||
|
||||
### Required invariants (must enforce in code)
|
||||
|
||||
- ACP session creation and thread bind are atomic (single transaction)
|
||||
- there is at most one active run per ACP session actor at a time
|
||||
- event `seq` is strictly increasing per run
|
||||
- delivery checkpoint never advances past last committed event
|
||||
- idempotency replay returns previous success payload for duplicate command keys
|
||||
- stale/missing ACP metadata cannot route into normal non-ACP reply path
|
||||
|
||||
### Core touchpoints
|
||||
|
||||
Core files to change:
|
||||
|
||||
- `src/auto-reply/reply/dispatch-from-config.ts`
|
||||
- ACP branch calls `AcpSessionManager.submit` and event-projection delivery
|
||||
- remove direct ACP fallback that bypasses control-plane invariants
|
||||
- `src/auto-reply/reply/inbound-context.ts` (or nearest normalized context boundary)
|
||||
- expose normalized routing keys and idempotency seeds for ACP control plane
|
||||
- `src/config/sessions/types.ts`
|
||||
- keep `SessionEntry.acp` as projection-only compatibility field
|
||||
- `src/gateway/server-methods/sessions.ts`
|
||||
- reset/delete/archive must call ACP manager close/unbind transaction path
|
||||
- `src/infra/outbound/bound-delivery-router.ts`
|
||||
- enforce fail-closed destination behavior for ACP bound session turns
|
||||
- `src/discord/monitor/thread-bindings.ts`
|
||||
- add ACP stale-binding validation helpers wired to control-plane lookups
|
||||
- `src/auto-reply/reply/commands-acp.ts`
|
||||
- route spawn/cancel/close/steer through ACP manager APIs
|
||||
- `src/agents/acp-spawn.ts`
|
||||
- stop ad-hoc metadata writes; call ACP manager spawn transaction
|
||||
- `src/plugin-sdk/**` and plugin runtime bridge
|
||||
- expose ACP backend registration and health semantics cleanly
|
||||
|
||||
Core files explicitly not replaced:
|
||||
|
||||
- `src/discord/monitor/message-handler.preflight.ts`
|
||||
- keep thread binding override behavior as the canonical session-key resolver
|
||||
|
||||
### ACP runtime registry API
|
||||
|
||||
Add a core registry module:
|
||||
|
||||
- `src/acp/runtime/registry.ts`
|
||||
|
||||
Required API:
|
||||
|
||||
```ts
|
||||
export type AcpRuntimeBackend = {
|
||||
id: string;
|
||||
runtime: AcpRuntime;
|
||||
healthy?: () => boolean;
|
||||
};
|
||||
|
||||
export function registerAcpRuntimeBackend(backend: AcpRuntimeBackend): void;
|
||||
export function unregisterAcpRuntimeBackend(id: string): void;
|
||||
export function getAcpRuntimeBackend(id?: string): AcpRuntimeBackend | null;
|
||||
export function requireAcpRuntimeBackend(id?: string): AcpRuntimeBackend;
|
||||
```
|
||||
|
||||
Behavior:
|
||||
|
||||
- `requireAcpRuntimeBackend` throws a typed ACP backend missing error when unavailable
|
||||
- plugin service registers backend on `start` and unregisters on `stop`
|
||||
- runtime lookups are read-only and process-local
|
||||
|
||||
### acpx runtime plugin contract (implementation detail)
|
||||
|
||||
For the first production backend (`extensions/acpx`), OpenClaw and acpx are
|
||||
connected with a strict command contract:
|
||||
|
||||
- backend id: `acpx`
|
||||
- plugin service id: `acpx-runtime`
|
||||
- runtime handle encoding: `runtimeSessionName = acpx:v1:<base64url(json)>`
|
||||
- encoded payload fields:
|
||||
- `name` (acpx named session; uses OpenClaw `sessionKey`)
|
||||
- `agent` (acpx agent command)
|
||||
- `cwd` (session workspace root)
|
||||
- `mode` (`persistent | oneshot`)
|
||||
|
||||
Command mapping:
|
||||
|
||||
- ensure session:
|
||||
- `acpx --format json --json-strict --cwd <cwd> <agent> sessions ensure --name <name>`
|
||||
- prompt turn:
|
||||
- `acpx --format json --json-strict --cwd <cwd> <agent> prompt --session <name> --file -`
|
||||
- cancel:
|
||||
- `acpx --format json --json-strict --cwd <cwd> <agent> cancel --session <name>`
|
||||
- close:
|
||||
- `acpx --format json --json-strict --cwd <cwd> <agent> sessions close <name>`
|
||||
|
||||
Streaming:
|
||||
|
||||
- OpenClaw consumes ndjson events from `acpx --format json --json-strict`
|
||||
- `text` => `text_delta/output`
|
||||
- `thought` => `text_delta/thought`
|
||||
- `tool_call` => `tool_call`
|
||||
- `done` => `done`
|
||||
- `error` => `error`
|
||||
|
||||
### Session schema patch
|
||||
|
||||
Patch `SessionEntry` in `src/config/sessions/types.ts`:
|
||||
|
||||
```ts
|
||||
type SessionAcpMeta = {
|
||||
backend: string;
|
||||
agent: string;
|
||||
runtimeSessionName: string;
|
||||
mode: "persistent" | "oneshot";
|
||||
cwd?: string;
|
||||
state: "idle" | "running" | "error";
|
||||
lastActivityAt: number;
|
||||
lastError?: string;
|
||||
};
|
||||
```
|
||||
|
||||
Persisted field:
|
||||
|
||||
- `SessionEntry.acp?: SessionAcpMeta`
|
||||
|
||||
Migration rules:
|
||||
|
||||
- phase A: dual-write (`acp` projection + ACP SQLite source-of-truth)
|
||||
- phase B: read-primary from ACP SQLite, fallback-read from legacy `SessionEntry.acp`
|
||||
- phase C: migration command backfills missing ACP rows from valid legacy entries
|
||||
- phase D: remove fallback-read and keep projection optional for UX only
|
||||
- legacy fields (`cliSessionIds`, `claudeCliSessionId`) remain untouched
|
||||
|
||||
### Error contract
|
||||
|
||||
Add stable ACP error codes and user-facing messages:
|
||||
|
||||
- `ACP_BACKEND_MISSING`
|
||||
- message: `ACP runtime backend is not configured. Install and enable the acpx runtime plugin.`
|
||||
- `ACP_BACKEND_UNAVAILABLE`
|
||||
- message: `ACP runtime backend is currently unavailable. Try again in a moment.`
|
||||
- `ACP_SESSION_INIT_FAILED`
|
||||
- message: `Could not initialize ACP session runtime.`
|
||||
- `ACP_TURN_FAILED`
|
||||
- message: `ACP turn failed before completion.`
|
||||
|
||||
Rules:
|
||||
|
||||
- return actionable user-safe message in-thread
|
||||
- log detailed backend/system error only in runtime logs
|
||||
- never silently fall back to normal LLM path when ACP routing was explicitly selected
|
||||
|
||||
### Duplicate delivery arbitration
|
||||
|
||||
Single routing rule for ACP bound turns:
|
||||
|
||||
- if an active thread binding exists for the target ACP session and requester context, deliver only to that bound thread
|
||||
- do not also send to parent channel for the same turn
|
||||
- if bound destination selection is ambiguous, fail closed with explicit error (no implicit parent fallback)
|
||||
- if no active binding exists, use normal session destination behavior
|
||||
|
||||
### Observability and operational readiness
|
||||
|
||||
Required metrics:
|
||||
|
||||
- ACP spawn success/failure count by backend and error code
|
||||
- ACP run latency percentiles (queue wait, runtime turn time, delivery projection time)
|
||||
- ACP actor restart count and restart reason
|
||||
- stale-binding detection count
|
||||
- idempotency replay hit rate
|
||||
- Discord delivery retry and rate-limit counters
|
||||
|
||||
Required logs:
|
||||
|
||||
- structured logs keyed by `sessionKey`, `runId`, `backend`, `threadId`, `idempotencyKey`
|
||||
- explicit state transition logs for session and run state machines
|
||||
- adapter command logs with redaction-safe arguments and exit summary
|
||||
|
||||
Required diagnostics:
|
||||
|
||||
- `/acp sessions` includes state, active run, last error, and binding status
|
||||
- `/acp doctor` (or equivalent) validates backend registration, store health, and stale bindings
|
||||
|
||||
### Config precedence and effective values
|
||||
|
||||
ACP enablement precedence:
|
||||
|
||||
- account override: `channels.discord.accounts.<id>.threadBindings.spawnAcpSessions`
|
||||
- channel override: `channels.discord.threadBindings.spawnAcpSessions`
|
||||
- global ACP gate: `acp.enabled`
|
||||
- dispatch gate: `acp.dispatch.enabled`
|
||||
- backend availability: registered backend for `acp.backend`
|
||||
|
||||
Auto-enable behavior:
|
||||
|
||||
- when ACP is configured (`acp.enabled=true`, `acp.dispatch.enabled=true`, or
|
||||
`acp.backend=acpx`), plugin auto-enable marks `plugins.entries.acpx.enabled=true`
|
||||
unless denylisted or explicitly disabled
|
||||
|
||||
TTL effective value:
|
||||
|
||||
- `min(session ttl, discord thread binding ttl, acp runtime ttl)`
|
||||
|
||||
### Test map
|
||||
|
||||
Unit tests:
|
||||
|
||||
- `src/acp/runtime/registry.test.ts` (new)
|
||||
- `src/auto-reply/reply/dispatch-from-config.acp.test.ts` (new)
|
||||
- `src/infra/outbound/bound-delivery-router.test.ts` (extend ACP fail-closed cases)
|
||||
- `src/config/sessions/types.test.ts` or nearest session-store tests (ACP metadata persistence)
|
||||
|
||||
Integration tests:
|
||||
|
||||
- `src/discord/monitor/reply-delivery.test.ts` (bound ACP delivery target behavior)
|
||||
- `src/discord/monitor/message-handler.preflight*.test.ts` (bound ACP session-key routing continuity)
|
||||
- acpx plugin runtime tests in backend package (service register/start/stop + event normalization)
|
||||
|
||||
Gateway e2e tests:
|
||||
|
||||
- `src/gateway/server.sessions.gateway-server-sessions-a.e2e.test.ts` (extend ACP reset/delete lifecycle coverage)
|
||||
- ACP thread turn roundtrip e2e for spawn, message, stream, cancel, unfocus, restart recovery
|
||||
|
||||
### Rollout guard
|
||||
|
||||
Add independent ACP dispatch kill switch:
|
||||
|
||||
- `acp.dispatch.enabled` default `false` for first release
|
||||
- when disabled:
|
||||
- ACP spawn/focus control commands may still bind sessions
|
||||
- ACP dispatch path does not activate
|
||||
- user receives explicit message that ACP dispatch is disabled by policy
|
||||
- after canary validation, default can be flipped to `true` in a later release
|
||||
|
||||
## Command and UX plan
|
||||
|
||||
### New commands
|
||||
|
||||
- `/acp spawn <agent-id> [--mode persistent|oneshot] [--thread auto|here|off]`
|
||||
- `/acp cancel [session]`
|
||||
- `/acp steer <instruction>`
|
||||
- `/acp close [session]`
|
||||
- `/acp sessions`
|
||||
|
||||
### Existing command compatibility
|
||||
|
||||
- `/focus <sessionKey>` continues to support ACP targets
|
||||
- `/unfocus` keeps current semantics
|
||||
- `/session idle` and `/session max-age` replace the old TTL override
|
||||
|
||||
## Phased rollout
|
||||
|
||||
### Phase 0 ADR and schema freeze
|
||||
|
||||
- ship ADR for ACP control-plane ownership and adapter boundaries
|
||||
- freeze DB schema (`acp_sessions`, `acp_runs`, `acp_bindings`, `acp_events`, `acp_delivery_checkpoint`, `acp_idempotency`)
|
||||
- define stable ACP error codes, event contract, and state-transition guards
|
||||
|
||||
### Phase 1 Control-plane foundation in core
|
||||
|
||||
- implement `AcpSessionManager` and per-session actor runtime
|
||||
- implement ACP SQLite store and transaction helpers
|
||||
- implement idempotency store and replay helpers
|
||||
- implement event append + delivery checkpoint modules
|
||||
- wire spawn/cancel/close APIs to manager with transactional guarantees
|
||||
|
||||
### Phase 2 Core routing and lifecycle integration
|
||||
|
||||
- route thread-bound ACP turns from dispatch pipeline into ACP manager
|
||||
- enforce fail-closed routing when ACP binding/session invariants fail
|
||||
- integrate reset/delete/archive/unfocus lifecycle with ACP close/unbind transactions
|
||||
- add stale-binding detection and optional auto-unbind policy
|
||||
|
||||
### Phase 3 acpx backend adapter/plugin
|
||||
|
||||
- implement `acpx` adapter against runtime contract (`ensureSession`, `submit`, `stream`, `cancel`, `close`)
|
||||
- add backend health checks and startup/teardown registration
|
||||
- normalize acpx ndjson events into ACP runtime events
|
||||
- enforce backend timeouts, process supervision, and restart/backoff policy
|
||||
|
||||
### Phase 4 Delivery projection and channel UX (Discord first)
|
||||
|
||||
- implement event-driven channel projection with checkpoint resume (Discord first)
|
||||
- coalesce streaming chunks with rate-limit aware flush policy
|
||||
- guarantee exactly-once final completion message per run
|
||||
- ship `/acp spawn`, `/acp cancel`, `/acp steer`, `/acp close`, `/acp sessions`
|
||||
|
||||
### Phase 5 Migration and cutover
|
||||
|
||||
- introduce dual-write to `SessionEntry.acp` projection plus ACP SQLite source-of-truth
|
||||
- add migration utility for legacy ACP metadata rows
|
||||
- flip read path to ACP SQLite primary
|
||||
- remove legacy fallback routing that depends on missing `SessionEntry.acp`
|
||||
|
||||
### Phase 6 Hardening, SLOs, and scale limits
|
||||
|
||||
- enforce concurrency limits (global/account/session), queue policies, and timeout budgets
|
||||
- add full telemetry, dashboards, and alert thresholds
|
||||
- chaos-test crash recovery and duplicate-delivery suppression
|
||||
- publish runbook for backend outage, DB corruption, and stale-binding remediation
|
||||
|
||||
### Full implementation checklist
|
||||
|
||||
- core control-plane modules and tests
|
||||
- DB migrations and rollback plan
|
||||
- ACP manager API integration across dispatch and commands
|
||||
- adapter registration interface in plugin runtime bridge
|
||||
- acpx adapter implementation and tests
|
||||
- thread-capable channel delivery projection logic with checkpoint replay (Discord first)
|
||||
- lifecycle hooks for reset/delete/archive/unfocus
|
||||
- stale-binding detector and operator-facing diagnostics
|
||||
- config validation and precedence tests for all new ACP keys
|
||||
- operational docs and troubleshooting runbook
|
||||
|
||||
## Test plan
|
||||
|
||||
Unit tests:
|
||||
|
||||
- ACP DB transaction boundaries (spawn/bind/enqueue atomicity, cancel, close)
|
||||
- ACP state-machine transition guards for sessions and runs
|
||||
- idempotency reservation/replay semantics across all ACP commands
|
||||
- per-session actor serialization and queue ordering
|
||||
- acpx event parser and chunk coalescer
|
||||
- runtime supervisor restart and backoff policy
|
||||
- config precedence and effective TTL calculation
|
||||
- core ACP routing branch selection and fail-closed behavior when backend/session is invalid
|
||||
|
||||
Integration tests:
|
||||
|
||||
- fake ACP adapter process for deterministic streaming and cancel behavior
|
||||
- ACP manager + dispatch integration with transactional persistence
|
||||
- thread-bound inbound routing to ACP session key
|
||||
- thread-bound outbound delivery suppresses parent channel duplication
|
||||
- checkpoint replay recovers after delivery failure and resumes from last event
|
||||
- plugin service registration and teardown of ACP runtime backend
|
||||
|
||||
Gateway e2e tests:
|
||||
|
||||
- spawn ACP with thread, exchange multi-turn prompts, unfocus
|
||||
- gateway restart with persisted ACP DB and bindings, then continue same session
|
||||
- concurrent ACP sessions in multiple threads have no cross-talk
|
||||
- duplicate command retries (same idempotency key) do not create duplicate runs or replies
|
||||
- stale-binding scenario yields explicit error and optional auto-clean behavior
|
||||
|
||||
## Risks and mitigations
|
||||
|
||||
- Duplicate deliveries during transition
|
||||
- Mitigation: single destination resolver and idempotent event checkpoint
|
||||
- Runtime process churn under load
|
||||
- Mitigation: long lived per session owners + concurrency caps + backoff
|
||||
- Plugin absent or misconfigured
|
||||
- Mitigation: explicit operator-facing error and fail-closed ACP routing (no implicit fallback to normal session path)
|
||||
- Config confusion between subagent and ACP gates
|
||||
- Mitigation: explicit ACP keys and command feedback that includes effective policy source
|
||||
- Control-plane store corruption or migration bugs
|
||||
- Mitigation: WAL mode, backup/restore hooks, migration smoke tests, and read-only fallback diagnostics
|
||||
- Actor deadlocks or mailbox starvation
|
||||
- Mitigation: watchdog timers, actor health probes, and bounded mailbox depth with rejection telemetry
|
||||
|
||||
## Acceptance checklist
|
||||
|
||||
- ACP session spawn can create or bind a thread in a supported channel adapter (currently Discord)
|
||||
- all thread messages route to bound ACP session only
|
||||
- ACP outputs appear in the same thread identity with streaming or batches
|
||||
- no duplicate output in parent channel for bound turns
|
||||
- spawn+bind+initial enqueue are atomic in persistent store
|
||||
- ACP command retries are idempotent and do not duplicate runs or outputs
|
||||
- cancel, close, unfocus, archive, reset, and delete perform deterministic cleanup
|
||||
- crash restart preserves mapping and resumes multi turn continuity
|
||||
- concurrent thread bound ACP sessions work independently
|
||||
- ACP backend missing state produces clear actionable error
|
||||
- stale bindings are detected and surfaced explicitly (with optional safe auto-clean)
|
||||
- control-plane metrics and diagnostics are available for operators
|
||||
- new unit, integration, and e2e coverage passes
|
||||
|
||||
## Addendum: targeted refactors for current implementation (status)
|
||||
|
||||
These are non-blocking follow-ups to keep the ACP path maintainable after the current feature set lands.
|
||||
|
||||
### 1) Centralize ACP dispatch policy evaluation (completed)
|
||||
|
||||
- implemented via shared ACP policy helpers in `src/acp/policy.ts`
|
||||
- dispatch, ACP command lifecycle handlers, and ACP spawn path now consume shared policy logic
|
||||
|
||||
### 2) Split ACP command handler by subcommand domain (completed)
|
||||
|
||||
- `src/auto-reply/reply/commands-acp.ts` is now a thin router
|
||||
- subcommand behavior is split into:
|
||||
- `src/auto-reply/reply/commands-acp/lifecycle.ts`
|
||||
- `src/auto-reply/reply/commands-acp/runtime-options.ts`
|
||||
- `src/auto-reply/reply/commands-acp/diagnostics.ts`
|
||||
- shared helpers in `src/auto-reply/reply/commands-acp/shared.ts`
|
||||
|
||||
### 3) Split ACP session manager by responsibility (completed)
|
||||
|
||||
- manager is split into:
|
||||
- `src/acp/control-plane/manager.ts` (public facade + singleton)
|
||||
- `src/acp/control-plane/manager.core.ts` (manager implementation)
|
||||
- `src/acp/control-plane/manager.types.ts` (manager types/deps)
|
||||
- `src/acp/control-plane/manager.utils.ts` (normalization + helper functions)
|
||||
|
||||
### 4) Optional acpx runtime adapter cleanup
|
||||
|
||||
- `extensions/acpx/src/runtime.ts` can be split into:
|
||||
- process execution/supervision
|
||||
- ndjson event parsing/normalization
|
||||
- runtime API surface (`submit`, `cancel`, `close`, etc.)
|
||||
- improves testability and makes backend behavior easier to audit
|
||||
@ -1,96 +0,0 @@
|
||||
---
|
||||
summary: "Holy grail refactor plan for one unified runtime streaming pipeline across main, subagent, and ACP"
|
||||
owner: "onutc"
|
||||
status: "draft"
|
||||
last_updated: "2026-02-25"
|
||||
title: "Unified Runtime Streaming Refactor Plan"
|
||||
---
|
||||
|
||||
# Unified Runtime Streaming Refactor Plan
|
||||
|
||||
## Objective
|
||||
|
||||
Deliver one shared streaming pipeline for `main`, `subagent`, and `acp` so all runtimes get identical coalescing, chunking, delivery ordering, and crash recovery behavior.
|
||||
|
||||
## Why this exists
|
||||
|
||||
- Current behavior is split across multiple runtime-specific shaping paths.
|
||||
- Formatting/coalescing bugs can be fixed in one path but remain in others.
|
||||
- Delivery consistency, duplicate suppression, and recovery semantics are harder to reason about.
|
||||
|
||||
## Target architecture
|
||||
|
||||
Single pipeline, runtime-specific adapters:
|
||||
|
||||
1. Runtime adapters emit canonical events only.
|
||||
2. Shared stream assembler coalesces and finalizes text/tool/status events.
|
||||
3. Shared channel projector applies channel-specific chunking/formatting once.
|
||||
4. Shared delivery ledger enforces idempotent send/replay semantics.
|
||||
5. Outbound channel adapter executes sends and records delivery checkpoints.
|
||||
|
||||
Canonical event contract:
|
||||
|
||||
- `turn_started`
|
||||
- `text_delta`
|
||||
- `block_final`
|
||||
- `tool_started`
|
||||
- `tool_finished`
|
||||
- `status`
|
||||
- `turn_completed`
|
||||
- `turn_failed`
|
||||
- `turn_cancelled`
|
||||
|
||||
## Workstreams
|
||||
|
||||
### 1) Canonical streaming contract
|
||||
|
||||
- Define strict event schema + validation in core.
|
||||
- Add adapter contract tests to guarantee each runtime emits compatible events.
|
||||
- Reject malformed runtime events early and surface structured diagnostics.
|
||||
|
||||
### 2) Shared stream processor
|
||||
|
||||
- Replace runtime-specific coalescer/projector logic with one processor.
|
||||
- Processor owns text delta buffering, idle flush, max-chunk splitting, and completion flush.
|
||||
- Move ACP/main/subagent config resolution into one helper to prevent drift.
|
||||
|
||||
### 3) Shared channel projection
|
||||
|
||||
- Keep channel adapters dumb: accept finalized blocks and send.
|
||||
- Move Discord-specific chunking quirks to channel projector only.
|
||||
- Keep pipeline channel-agnostic before projection.
|
||||
|
||||
### 4) Delivery ledger + replay
|
||||
|
||||
- Add per-turn/per-chunk delivery IDs.
|
||||
- Record checkpoints before and after physical send.
|
||||
- On restart, replay pending chunks idempotently and avoid duplicates.
|
||||
|
||||
### 5) Migration and cutover
|
||||
|
||||
- Phase 1: shadow mode (new pipeline computes output but old path sends; compare).
|
||||
- Phase 2: runtime-by-runtime cutover (`acp`, then `subagent`, then `main` or reverse by risk).
|
||||
- Phase 3: delete legacy runtime-specific streaming code.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- No changes to ACP policy/permissions model in this refactor.
|
||||
- No channel-specific feature expansion outside projection compatibility fixes.
|
||||
- No transport/backend redesign (acpx plugin contract remains as-is unless needed for event parity).
|
||||
|
||||
## Risks and mitigations
|
||||
|
||||
- Risk: behavioral regressions in existing main/subagent paths.
|
||||
Mitigation: shadow mode diffing + adapter contract tests + channel e2e tests.
|
||||
- Risk: duplicate sends during crash recovery.
|
||||
Mitigation: durable delivery IDs + idempotent replay in delivery adapter.
|
||||
- Risk: runtime adapters diverge again.
|
||||
Mitigation: required shared contract test suite for all adapters.
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- All runtimes pass shared streaming contract tests.
|
||||
- Discord ACP/main/subagent produce equivalent spacing/chunking behavior for tiny deltas.
|
||||
- Crash/restart replay sends no duplicate chunk for the same delivery ID.
|
||||
- Legacy ACP projector/coalescer path is removed.
|
||||
- Streaming config resolution is shared and runtime-independent.
|
||||
@ -1,232 +0,0 @@
|
||||
---
|
||||
summary: "Plan: isolate browser act:evaluate from Playwright queue using CDP, with end-to-end deadlines and safer ref resolution"
|
||||
read_when:
|
||||
- Working on browser `act:evaluate` timeout, abort, or queue blocking issues
|
||||
- Planning CDP based isolation for evaluate execution
|
||||
owner: "openclaw"
|
||||
status: "draft"
|
||||
last_updated: "2026-02-10"
|
||||
title: "Browser Evaluate CDP Refactor"
|
||||
---
|
||||
|
||||
# Browser Evaluate CDP Refactor Plan
|
||||
|
||||
## Context
|
||||
|
||||
`act:evaluate` executes user provided JavaScript in the page. Today it runs via Playwright
|
||||
(`page.evaluate` or `locator.evaluate`). Playwright serializes CDP commands per page, so a
|
||||
stuck or long running evaluate can block the page command queue and make every later action
|
||||
on that tab look "stuck".
|
||||
|
||||
PR #13498 adds a pragmatic safety net (bounded evaluate, abort propagation, and best-effort
|
||||
recovery). This document describes a larger refactor that makes `act:evaluate` inherently
|
||||
isolated from Playwright so a stuck evaluate cannot wedge normal Playwright operations.
|
||||
|
||||
## Goals
|
||||
|
||||
- `act:evaluate` cannot permanently block later browser actions on the same tab.
|
||||
- Timeouts are single source of truth end to end so a caller can rely on a budget.
|
||||
- Abort and timeout are treated the same way across HTTP and in-process dispatch.
|
||||
- Element targeting for evaluate is supported without switching everything off Playwright.
|
||||
- Maintain backward compatibility for existing callers and payloads.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Replace all browser actions (click, type, wait, etc.) with CDP implementations.
|
||||
- Remove the existing safety net introduced in PR #13498 (it remains a useful fallback).
|
||||
- Introduce new unsafe capabilities beyond the existing `browser.evaluateEnabled` gate.
|
||||
- Add process isolation (worker process/thread) for evaluate. If we still see hard to recover
|
||||
stuck states after this refactor, that is a follow-up idea.
|
||||
|
||||
## Current Architecture (Why It Gets Stuck)
|
||||
|
||||
At a high level:
|
||||
|
||||
- Callers send `act:evaluate` to the browser control service.
|
||||
- The route handler calls into Playwright to execute the JavaScript.
|
||||
- Playwright serializes page commands, so an evaluate that never finishes blocks the queue.
|
||||
- A stuck queue means later click/type/wait operations on the tab can appear to hang.
|
||||
|
||||
## Proposed Architecture
|
||||
|
||||
### 1. Deadline Propagation
|
||||
|
||||
Introduce a single budget concept and derive everything from it:
|
||||
|
||||
- Caller sets `timeoutMs` (or a deadline in the future).
|
||||
- The outer request timeout, route handler logic, and the execution budget inside the page
|
||||
all use the same budget, with small headroom where needed for serialization overhead.
|
||||
- Abort is propagated as an `AbortSignal` everywhere so cancellation is consistent.
|
||||
|
||||
Implementation direction:
|
||||
|
||||
- Add a small helper (for example `createBudget({ timeoutMs, signal })`) that returns:
|
||||
- `signal`: the linked AbortSignal
|
||||
- `deadlineAtMs`: absolute deadline
|
||||
- `remainingMs()`: remaining budget for child operations
|
||||
- Use this helper in:
|
||||
- `src/browser/client-fetch.ts` (HTTP and in-process dispatch)
|
||||
- `src/node-host/runner.ts` (proxy path)
|
||||
- browser action implementations (Playwright and CDP)
|
||||
|
||||
### 2. Separate Evaluate Engine (CDP Path)
|
||||
|
||||
Add a CDP based evaluate implementation that does not share Playwright's per page command
|
||||
queue. The key property is that the evaluate transport is a separate WebSocket connection
|
||||
and a separate CDP session attached to the target.
|
||||
|
||||
Implementation direction:
|
||||
|
||||
- New module, for example `src/browser/cdp-evaluate.ts`, that:
|
||||
- Connects to the configured CDP endpoint (browser level socket).
|
||||
- Uses `Target.attachToTarget({ targetId, flatten: true })` to get a `sessionId`.
|
||||
- Runs either:
|
||||
- `Runtime.evaluate` for page level evaluate, or
|
||||
- `DOM.resolveNode` plus `Runtime.callFunctionOn` for element evaluate.
|
||||
- On timeout or abort:
|
||||
- Sends `Runtime.terminateExecution` best-effort for the session.
|
||||
- Closes the WebSocket and returns a clear error.
|
||||
|
||||
Notes:
|
||||
|
||||
- This still executes JavaScript in the page, so termination can have side effects. The win
|
||||
is that it does not wedge the Playwright queue, and it is cancelable at the transport
|
||||
layer by killing the CDP session.
|
||||
|
||||
### 3. Ref Story (Element Targeting Without A Full Rewrite)
|
||||
|
||||
The hard part is element targeting. CDP needs a DOM handle or `backendDOMNodeId`, while
|
||||
today most browser actions use Playwright locators based on refs from snapshots.
|
||||
|
||||
Recommended approach: keep existing refs, but attach an optional CDP resolvable id.
|
||||
|
||||
#### 3.1 Extend Stored Ref Info
|
||||
|
||||
Extend the stored role ref metadata to optionally include a CDP id:
|
||||
|
||||
- Today: `{ role, name, nth }`
|
||||
- Proposed: `{ role, name, nth, backendDOMNodeId?: number }`
|
||||
|
||||
This keeps all existing Playwright based actions working and allows CDP evaluate to accept
|
||||
the same `ref` value when the `backendDOMNodeId` is available.
|
||||
|
||||
#### 3.2 Populate backendDOMNodeId At Snapshot Time
|
||||
|
||||
When producing a role snapshot:
|
||||
|
||||
1. Generate the existing role ref map as today (role, name, nth).
|
||||
2. Fetch the AX tree via CDP (`Accessibility.getFullAXTree`) and compute a parallel map of
|
||||
`(role, name, nth) -> backendDOMNodeId` using the same duplicate handling rules.
|
||||
3. Merge the id back into the stored ref info for the current tab.
|
||||
|
||||
If mapping fails for a ref, leave `backendDOMNodeId` undefined. This makes the feature
|
||||
best-effort and safe to roll out.
|
||||
|
||||
#### 3.3 Evaluate Behavior With Ref
|
||||
|
||||
In `act:evaluate`:
|
||||
|
||||
- If `ref` is present and has `backendDOMNodeId`, run element evaluate via CDP.
|
||||
- If `ref` is present but has no `backendDOMNodeId`, fall back to the Playwright path (with
|
||||
the safety net).
|
||||
|
||||
Optional escape hatch:
|
||||
|
||||
- Extend the request shape to accept `backendDOMNodeId` directly for advanced callers (and
|
||||
for debugging), while keeping `ref` as the primary interface.
|
||||
|
||||
### 4. Keep A Last Resort Recovery Path
|
||||
|
||||
Even with CDP evaluate, there are other ways to wedge a tab or a connection. Keep the
|
||||
existing recovery mechanisms (terminate execution + disconnect Playwright) as a last resort
|
||||
for:
|
||||
|
||||
- legacy callers
|
||||
- environments where CDP attach is blocked
|
||||
- unexpected Playwright edge cases
|
||||
|
||||
## Implementation Plan (Single Iteration)
|
||||
|
||||
### Deliverables
|
||||
|
||||
- A CDP based evaluate engine that runs outside the Playwright per-page command queue.
|
||||
- A single end-to-end timeout/abort budget used consistently by callers and handlers.
|
||||
- Ref metadata that can optionally carry `backendDOMNodeId` for element evaluate.
|
||||
- `act:evaluate` prefers the CDP engine when possible and falls back to Playwright when not.
|
||||
- Tests that prove a stuck evaluate does not wedge later actions.
|
||||
- Logs/metrics that make failures and fallbacks visible.
|
||||
|
||||
### Implementation Checklist
|
||||
|
||||
1. Add a shared "budget" helper to link `timeoutMs` + upstream `AbortSignal` into:
|
||||
- a single `AbortSignal`
|
||||
- an absolute deadline
|
||||
- a `remainingMs()` helper for downstream operations
|
||||
2. Update all caller paths to use that helper so `timeoutMs` means the same thing everywhere:
|
||||
- `src/browser/client-fetch.ts` (HTTP and in-process dispatch)
|
||||
- `src/node-host/runner.ts` (node proxy path)
|
||||
- CLI wrappers that call `/act` (add `--timeout-ms` to `browser evaluate`)
|
||||
3. Implement `src/browser/cdp-evaluate.ts`:
|
||||
- connect to the browser-level CDP socket
|
||||
- `Target.attachToTarget` to get a `sessionId`
|
||||
- run `Runtime.evaluate` for page evaluate
|
||||
- run `DOM.resolveNode` + `Runtime.callFunctionOn` for element evaluate
|
||||
- on timeout/abort: best-effort `Runtime.terminateExecution` then close the socket
|
||||
4. Extend stored role ref metadata to optionally include `backendDOMNodeId`:
|
||||
- keep existing `{ role, name, nth }` behavior for Playwright actions
|
||||
- add `backendDOMNodeId?: number` for CDP element targeting
|
||||
5. Populate `backendDOMNodeId` during snapshot creation (best-effort):
|
||||
- fetch AX tree via CDP (`Accessibility.getFullAXTree`)
|
||||
- compute `(role, name, nth) -> backendDOMNodeId` and merge into the stored ref map
|
||||
- if mapping is ambiguous or missing, leave the id undefined
|
||||
6. Update `act:evaluate` routing:
|
||||
- if no `ref`: always use CDP evaluate
|
||||
- if `ref` resolves to a `backendDOMNodeId`: use CDP element evaluate
|
||||
- otherwise: fall back to Playwright evaluate (still bounded and abortable)
|
||||
7. Keep the existing "last resort" recovery path as a fallback, not the default path.
|
||||
8. Add tests:
|
||||
- stuck evaluate times out within budget and the next click/type succeeds
|
||||
- abort cancels evaluate (client disconnect or timeout) and unblocks subsequent actions
|
||||
- mapping failures cleanly fall back to Playwright
|
||||
9. Add observability:
|
||||
- evaluate duration and timeout counters
|
||||
- terminateExecution usage
|
||||
- fallback rate (CDP -> Playwright) and reasons
|
||||
|
||||
### Acceptance Criteria
|
||||
|
||||
- A deliberately hung `act:evaluate` returns within the caller budget and does not wedge the
|
||||
tab for later actions.
|
||||
- `timeoutMs` behaves consistently across CLI, agent tool, node proxy, and in-process calls.
|
||||
- If `ref` can be mapped to `backendDOMNodeId`, element evaluate uses CDP; otherwise the
|
||||
fallback path is still bounded and recoverable.
|
||||
|
||||
## Testing Plan
|
||||
|
||||
- Unit tests:
|
||||
- `(role, name, nth)` matching logic between role refs and AX tree nodes.
|
||||
- Budget helper behavior (headroom, remaining time math).
|
||||
- Integration tests:
|
||||
- CDP evaluate timeout returns within budget and does not block the next action.
|
||||
- Abort cancels evaluate and triggers termination best-effort.
|
||||
- Contract tests:
|
||||
- Ensure `BrowserActRequest` and `BrowserActResponse` remain compatible.
|
||||
|
||||
## Risks And Mitigations
|
||||
|
||||
- Mapping is imperfect:
|
||||
- Mitigation: best-effort mapping, fallback to Playwright evaluate, and add debug tooling.
|
||||
- `Runtime.terminateExecution` has side effects:
|
||||
- Mitigation: only use on timeout/abort and document the behavior in errors.
|
||||
- Extra overhead:
|
||||
- Mitigation: only fetch AX tree when snapshots are requested, cache per target, and keep
|
||||
CDP session short lived.
|
||||
- Extension relay limitations:
|
||||
- Mitigation: use browser level attach APIs when per page sockets are not available, and
|
||||
keep the current Playwright path as fallback.
|
||||
|
||||
## Open Questions
|
||||
|
||||
- Should the new engine be configurable as `playwright`, `cdp`, or `auto`?
|
||||
- Do we want to expose a new "nodeRef" format for advanced users, or keep `ref` only?
|
||||
- How should frame snapshots and selector scoped snapshots participate in AX mapping?
|
||||
@ -1,337 +0,0 @@
|
||||
---
|
||||
summary: "Status and next steps for decoupling Discord gateway listeners from long-running agent turns with a Discord-specific inbound worker"
|
||||
owner: "openclaw"
|
||||
status: "in_progress"
|
||||
last_updated: "2026-03-05"
|
||||
title: "Discord Async Inbound Worker Plan"
|
||||
---
|
||||
|
||||
# Discord Async Inbound Worker Plan
|
||||
|
||||
## Objective
|
||||
|
||||
Remove Discord listener timeout as a user-facing failure mode by making inbound Discord turns asynchronous:
|
||||
|
||||
1. Gateway listener accepts and normalizes inbound events quickly.
|
||||
2. A Discord run queue stores serialized jobs keyed by the same ordering boundary we use today.
|
||||
3. A worker executes the actual agent turn outside the Carbon listener lifetime.
|
||||
4. Replies are delivered back to the originating channel or thread after the run completes.
|
||||
|
||||
This is the long-term fix for queued Discord runs timing out at `channels.discord.eventQueue.listenerTimeout` while the agent run itself is still making progress.
|
||||
|
||||
## Current status
|
||||
|
||||
This plan is partially implemented.
|
||||
|
||||
Already done:
|
||||
|
||||
- Discord listener timeout and Discord run timeout are now separate settings.
|
||||
- Accepted inbound Discord turns are enqueued into `src/discord/monitor/inbound-worker.ts`.
|
||||
- The worker now owns the long-running turn instead of the Carbon listener.
|
||||
- Existing per-route ordering is preserved by queue key.
|
||||
- Timeout regression coverage exists for the Discord worker path.
|
||||
|
||||
What this means in plain language:
|
||||
|
||||
- the production timeout bug is fixed
|
||||
- the long-running turn no longer dies just because the Discord listener budget expires
|
||||
- the worker architecture is not finished yet
|
||||
|
||||
What is still missing:
|
||||
|
||||
- `DiscordInboundJob` is still only partially normalized and still carries live runtime references
|
||||
- command semantics (`stop`, `new`, `reset`, future session controls) are not yet fully worker-native
|
||||
- worker observability and operator status are still minimal
|
||||
- there is still no restart durability
|
||||
|
||||
## Why this exists
|
||||
|
||||
Current behavior ties the full agent turn to the listener lifetime:
|
||||
|
||||
- `src/discord/monitor/listeners.ts` applies the timeout and abort boundary.
|
||||
- `src/discord/monitor/message-handler.ts` keeps the queued run inside that boundary.
|
||||
- `src/discord/monitor/message-handler.process.ts` performs media loading, routing, dispatch, typing, draft streaming, and final reply delivery inline.
|
||||
|
||||
That architecture has two bad properties:
|
||||
|
||||
- long but healthy turns can be aborted by the listener watchdog
|
||||
- users can see no reply even when the downstream runtime would have produced one
|
||||
|
||||
Raising the timeout helps but does not change the failure mode.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Do not redesign non-Discord channels in this pass.
|
||||
- Do not broaden this into a generic all-channel worker framework in the first implementation.
|
||||
- Do not extract a shared cross-channel inbound worker abstraction yet; only share low-level primitives when duplication is obvious.
|
||||
- Do not add durable crash recovery in the first pass unless needed to land safely.
|
||||
- Do not change route selection, binding semantics, or ACP policy in this plan.
|
||||
|
||||
## Current constraints
|
||||
|
||||
The current Discord processing path still depends on some live runtime objects that should not stay inside the long-term job payload:
|
||||
|
||||
- Carbon `Client`
|
||||
- raw Discord event shapes
|
||||
- in-memory guild history map
|
||||
- thread binding manager callbacks
|
||||
- live typing and draft stream state
|
||||
|
||||
We already moved execution onto a worker queue, but the normalization boundary is still incomplete. Right now the worker is "run later in the same process with some of the same live objects," not a fully data-only job boundary.
|
||||
|
||||
## Target architecture
|
||||
|
||||
### 1. Listener stage
|
||||
|
||||
`DiscordMessageListener` remains the ingress point, but its job becomes:
|
||||
|
||||
- run preflight and policy checks
|
||||
- normalize accepted input into a serializable `DiscordInboundJob`
|
||||
- enqueue the job into a per-session or per-channel async queue
|
||||
- return immediately to Carbon once the enqueue succeeds
|
||||
|
||||
The listener should no longer own the end-to-end LLM turn lifetime.
|
||||
|
||||
### 2. Normalized job payload
|
||||
|
||||
Introduce a serializable job descriptor that contains only the data needed to run the turn later.
|
||||
|
||||
Minimum shape:
|
||||
|
||||
- route identity
|
||||
- `agentId`
|
||||
- `sessionKey`
|
||||
- `accountId`
|
||||
- `channel`
|
||||
- delivery identity
|
||||
- destination channel id
|
||||
- reply target message id
|
||||
- thread id if present
|
||||
- sender identity
|
||||
- sender id, label, username, tag
|
||||
- channel context
|
||||
- guild id
|
||||
- channel name or slug
|
||||
- thread metadata
|
||||
- resolved system prompt override
|
||||
- normalized message body
|
||||
- base text
|
||||
- effective message text
|
||||
- attachment descriptors or resolved media references
|
||||
- gating decisions
|
||||
- mention requirement outcome
|
||||
- command authorization outcome
|
||||
- bound session or agent metadata if applicable
|
||||
|
||||
The job payload must not contain live Carbon objects or mutable closures.
|
||||
|
||||
Current implementation status:
|
||||
|
||||
- partially done
|
||||
- `src/discord/monitor/inbound-job.ts` exists and defines the worker handoff
|
||||
- the payload still contains live Discord runtime context and should be reduced further
|
||||
|
||||
### 3. Worker stage
|
||||
|
||||
Add a Discord-specific worker runner responsible for:
|
||||
|
||||
- reconstructing the turn context from `DiscordInboundJob`
|
||||
- loading media and any additional channel metadata needed for the run
|
||||
- dispatching the agent turn
|
||||
- delivering final reply payloads
|
||||
- updating status and diagnostics
|
||||
|
||||
Recommended location:
|
||||
|
||||
- `src/discord/monitor/inbound-worker.ts`
|
||||
- `src/discord/monitor/inbound-job.ts`
|
||||
|
||||
### 4. Ordering model
|
||||
|
||||
Ordering must remain equivalent to today for a given route boundary.
|
||||
|
||||
Recommended key:
|
||||
|
||||
- use the same queue key logic as `resolveDiscordRunQueueKey(...)`
|
||||
|
||||
This preserves existing behavior:
|
||||
|
||||
- one bound agent conversation does not interleave with itself
|
||||
- different Discord channels can still progress independently
|
||||
|
||||
### 5. Timeout model
|
||||
|
||||
After cutover, there are two separate timeout classes:
|
||||
|
||||
- listener timeout
|
||||
- only covers normalization and enqueue
|
||||
- should be short
|
||||
- run timeout
|
||||
- optional, worker-owned, explicit, and user-visible
|
||||
- should not be inherited accidentally from Carbon listener settings
|
||||
|
||||
This removes the current accidental coupling between "Discord gateway listener stayed alive" and "agent run is healthy."
|
||||
|
||||
## Recommended implementation phases
|
||||
|
||||
### Phase 1: normalization boundary
|
||||
|
||||
- Status: partially implemented
|
||||
- Done:
|
||||
- extracted `buildDiscordInboundJob(...)`
|
||||
- added worker handoff tests
|
||||
- Remaining:
|
||||
- make `DiscordInboundJob` plain data only
|
||||
- move live runtime dependencies to worker-owned services instead of per-job payload
|
||||
- stop rebuilding process context by stitching live listener refs back into the job
|
||||
|
||||
### Phase 2: in-memory worker queue
|
||||
|
||||
- Status: implemented
|
||||
- Done:
|
||||
- added `DiscordInboundWorkerQueue` keyed by resolved run queue key
|
||||
- listener enqueues jobs instead of directly awaiting `processDiscordMessage(...)`
|
||||
- worker executes jobs in-process, in memory only
|
||||
|
||||
This is the first functional cutover.
|
||||
|
||||
### Phase 3: process split
|
||||
|
||||
- Status: not started
|
||||
- Move delivery, typing, and draft streaming ownership behind worker-facing adapters.
|
||||
- Replace direct use of live preflight context with worker context reconstruction.
|
||||
- Keep `processDiscordMessage(...)` temporarily as a facade if needed, then split it.
|
||||
|
||||
### Phase 4: command semantics
|
||||
|
||||
- Status: not started
|
||||
Make sure native Discord commands still behave correctly when work is queued:
|
||||
|
||||
- `stop`
|
||||
- `new`
|
||||
- `reset`
|
||||
- any future session-control commands
|
||||
|
||||
The worker queue must expose enough run state for commands to target the active or queued turn.
|
||||
|
||||
### Phase 5: observability and operator UX
|
||||
|
||||
- Status: not started
|
||||
- emit queue depth and active worker counts into monitor status
|
||||
- record enqueue time, start time, finish time, and timeout or cancellation reason
|
||||
- surface worker-owned timeout or delivery failures clearly in logs
|
||||
|
||||
### Phase 6: optional durability follow-up
|
||||
|
||||
- Status: not started
|
||||
Only after the in-memory version is stable:
|
||||
|
||||
- decide whether queued Discord jobs should survive gateway restart
|
||||
- if yes, persist job descriptors and delivery checkpoints
|
||||
- if no, document the explicit in-memory boundary
|
||||
|
||||
This should be a separate follow-up unless restart recovery is required to land.
|
||||
|
||||
## File impact
|
||||
|
||||
Current primary files:
|
||||
|
||||
- `src/discord/monitor/listeners.ts`
|
||||
- `src/discord/monitor/message-handler.ts`
|
||||
- `src/discord/monitor/message-handler.preflight.ts`
|
||||
- `src/discord/monitor/message-handler.process.ts`
|
||||
- `src/discord/monitor/status.ts`
|
||||
|
||||
Current worker files:
|
||||
|
||||
- `src/discord/monitor/inbound-job.ts`
|
||||
- `src/discord/monitor/inbound-worker.ts`
|
||||
- `src/discord/monitor/inbound-job.test.ts`
|
||||
- `src/discord/monitor/message-handler.queue.test.ts`
|
||||
|
||||
Likely next touch points:
|
||||
|
||||
- `src/auto-reply/dispatch.ts`
|
||||
- `src/discord/monitor/reply-delivery.ts`
|
||||
- `src/discord/monitor/thread-bindings.ts`
|
||||
- `src/discord/monitor/native-command.ts`
|
||||
|
||||
## Next step now
|
||||
|
||||
The next step is to make the worker boundary real instead of partial.
|
||||
|
||||
Do this next:
|
||||
|
||||
1. Move live runtime dependencies out of `DiscordInboundJob`
|
||||
2. Keep those dependencies on the Discord worker instance instead
|
||||
3. Reduce queued jobs to plain Discord-specific data:
|
||||
- route identity
|
||||
- delivery target
|
||||
- sender info
|
||||
- normalized message snapshot
|
||||
- gating and binding decisions
|
||||
4. Reconstruct worker execution context from that plain data inside the worker
|
||||
|
||||
In practice, that means:
|
||||
|
||||
- `client`
|
||||
- `threadBindings`
|
||||
- `guildHistories`
|
||||
- `discordRestFetch`
|
||||
- other mutable runtime-only handles
|
||||
|
||||
should stop living on each queued job and instead live on the worker itself or behind worker-owned adapters.
|
||||
|
||||
After that lands, the next follow-up should be command-state cleanup for `stop`, `new`, and `reset`.
|
||||
|
||||
## Testing plan
|
||||
|
||||
Keep the existing timeout repro coverage in:
|
||||
|
||||
- `src/discord/monitor/message-handler.queue.test.ts`
|
||||
|
||||
Add new tests for:
|
||||
|
||||
1. listener returns after enqueue without awaiting full turn
|
||||
2. per-route ordering is preserved
|
||||
3. different channels still run concurrently
|
||||
4. replies are delivered to the original message destination
|
||||
5. `stop` cancels the active worker-owned run
|
||||
6. worker failure produces visible diagnostics without blocking later jobs
|
||||
7. ACP-bound Discord channels still route correctly under worker execution
|
||||
|
||||
## Risks and mitigations
|
||||
|
||||
- Risk: command semantics drift from current synchronous behavior
|
||||
Mitigation: land command-state plumbing in the same cutover, not later
|
||||
|
||||
- Risk: reply delivery loses thread or reply-to context
|
||||
Mitigation: make delivery identity first-class in `DiscordInboundJob`
|
||||
|
||||
- Risk: duplicate sends during retries or queue restarts
|
||||
Mitigation: keep first pass in-memory only, or add explicit delivery idempotency before persistence
|
||||
|
||||
- Risk: `message-handler.process.ts` becomes harder to reason about during migration
|
||||
Mitigation: split into normalization, execution, and delivery helpers before or during worker cutover
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
The plan is complete when:
|
||||
|
||||
1. Discord listener timeout no longer aborts healthy long-running turns.
|
||||
2. Listener lifetime and agent-turn lifetime are separate concepts in code.
|
||||
3. Existing per-session ordering is preserved.
|
||||
4. ACP-bound Discord channels work through the same worker path.
|
||||
5. `stop` targets the worker-owned run instead of the old listener-owned call stack.
|
||||
6. Timeout and delivery failures become explicit worker outcomes, not silent listener drops.
|
||||
|
||||
## Remaining landing strategy
|
||||
|
||||
Finish this in follow-up PRs:
|
||||
|
||||
1. make `DiscordInboundJob` plain-data only and move live runtime refs onto the worker
|
||||
2. clean up command-state ownership for `stop`, `new`, and `reset`
|
||||
3. add worker observability and operator status
|
||||
4. decide whether durability is needed or explicitly document the in-memory boundary
|
||||
|
||||
This is still a bounded follow-up if kept Discord-only and if we continue to avoid a premature cross-channel worker abstraction.
|
||||
@ -1,126 +0,0 @@
|
||||
---
|
||||
summary: "Plan: Add OpenResponses /v1/responses endpoint and deprecate chat completions cleanly"
|
||||
read_when:
|
||||
- Designing or implementing `/v1/responses` gateway support
|
||||
- Planning migration from Chat Completions compatibility
|
||||
owner: "openclaw"
|
||||
status: "draft"
|
||||
last_updated: "2026-01-19"
|
||||
title: "OpenResponses Gateway Plan"
|
||||
---
|
||||
|
||||
# OpenResponses Gateway Integration Plan
|
||||
|
||||
## Context
|
||||
|
||||
OpenClaw Gateway currently exposes a minimal OpenAI-compatible Chat Completions endpoint at
|
||||
`/v1/chat/completions` (see [OpenAI Chat Completions](/gateway/openai-http-api)).
|
||||
|
||||
Open Responses is an open inference standard based on the OpenAI Responses API. It is designed
|
||||
for agentic workflows and uses item-based inputs plus semantic streaming events. The OpenResponses
|
||||
spec defines `/v1/responses`, not `/v1/chat/completions`.
|
||||
|
||||
## Goals
|
||||
|
||||
- Add a `/v1/responses` endpoint that adheres to OpenResponses semantics.
|
||||
- Keep Chat Completions as a compatibility layer that is easy to disable and eventually remove.
|
||||
- Standardize validation and parsing with isolated, reusable schemas.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Full OpenResponses feature parity in the first pass (images, files, hosted tools).
|
||||
- Replacing internal agent execution logic or tool orchestration.
|
||||
- Changing the existing `/v1/chat/completions` behavior during the first phase.
|
||||
|
||||
## Research Summary
|
||||
|
||||
Sources: OpenResponses OpenAPI, OpenResponses specification site, and the Hugging Face blog post.
|
||||
|
||||
Key points extracted:
|
||||
|
||||
- `POST /v1/responses` accepts `CreateResponseBody` fields like `model`, `input` (string or
|
||||
`ItemParam[]`), `instructions`, `tools`, `tool_choice`, `stream`, `max_output_tokens`, and
|
||||
`max_tool_calls`.
|
||||
- `ItemParam` is a discriminated union of:
|
||||
- `message` items with roles `system`, `developer`, `user`, `assistant`
|
||||
- `function_call` and `function_call_output`
|
||||
- `reasoning`
|
||||
- `item_reference`
|
||||
- Successful responses return a `ResponseResource` with `object: "response"`, `status`, and
|
||||
`output` items.
|
||||
- Streaming uses semantic events such as:
|
||||
- `response.created`, `response.in_progress`, `response.completed`, `response.failed`
|
||||
- `response.output_item.added`, `response.output_item.done`
|
||||
- `response.content_part.added`, `response.content_part.done`
|
||||
- `response.output_text.delta`, `response.output_text.done`
|
||||
- The spec requires:
|
||||
- `Content-Type: text/event-stream`
|
||||
- `event:` must match the JSON `type` field
|
||||
- terminal event must be literal `[DONE]`
|
||||
- Reasoning items may expose `content`, `encrypted_content`, and `summary`.
|
||||
- HF examples include `OpenResponses-Version: latest` in requests (optional header).
|
||||
|
||||
## Proposed Architecture
|
||||
|
||||
- Add `src/gateway/open-responses.schema.ts` containing Zod schemas only (no gateway imports).
|
||||
- Add `src/gateway/openresponses-http.ts` (or `open-responses-http.ts`) for `/v1/responses`.
|
||||
- Keep `src/gateway/openai-http.ts` intact as a legacy compatibility adapter.
|
||||
- Add config `gateway.http.endpoints.responses.enabled` (default `false`).
|
||||
- Keep `gateway.http.endpoints.chatCompletions.enabled` independent; allow both endpoints to be
|
||||
toggled separately.
|
||||
- Emit a startup warning when Chat Completions is enabled to signal legacy status.
|
||||
|
||||
## Deprecation Path for Chat Completions
|
||||
|
||||
- Maintain strict module boundaries: no shared schema types between responses and chat completions.
|
||||
- Make Chat Completions opt-in by config so it can be disabled without code changes.
|
||||
- Update docs to label Chat Completions as legacy once `/v1/responses` is stable.
|
||||
- Optional future step: map Chat Completions requests to the Responses handler for a simpler
|
||||
removal path.
|
||||
|
||||
## Phase 1 Support Subset
|
||||
|
||||
- Accept `input` as string or `ItemParam[]` with message roles and `function_call_output`.
|
||||
- Extract system and developer messages into `extraSystemPrompt`.
|
||||
- Use the most recent `user` or `function_call_output` as the current message for agent runs.
|
||||
- Reject unsupported content parts (image/file) with `invalid_request_error`.
|
||||
- Return a single assistant message with `output_text` content.
|
||||
- Return `usage` with zeroed values until token accounting is wired.
|
||||
|
||||
## Validation Strategy (No SDK)
|
||||
|
||||
- Implement Zod schemas for the supported subset of:
|
||||
- `CreateResponseBody`
|
||||
- `ItemParam` + message content part unions
|
||||
- `ResponseResource`
|
||||
- Streaming event shapes used by the gateway
|
||||
- Keep schemas in a single, isolated module to avoid drift and allow future codegen.
|
||||
|
||||
## Streaming Implementation (Phase 1)
|
||||
|
||||
- SSE lines with both `event:` and `data:`.
|
||||
- Required sequence (minimum viable):
|
||||
- `response.created`
|
||||
- `response.output_item.added`
|
||||
- `response.content_part.added`
|
||||
- `response.output_text.delta` (repeat as needed)
|
||||
- `response.output_text.done`
|
||||
- `response.content_part.done`
|
||||
- `response.completed`
|
||||
- `[DONE]`
|
||||
|
||||
## Tests and Verification Plan
|
||||
|
||||
- Add e2e coverage for `/v1/responses`:
|
||||
- Auth required
|
||||
- Non-stream response shape
|
||||
- Stream event ordering and `[DONE]`
|
||||
- Session routing with headers and `user`
|
||||
- Keep `src/gateway/openai-http.test.ts` unchanged.
|
||||
- Manual: curl to `/v1/responses` with `stream: true` and verify event ordering and terminal
|
||||
`[DONE]`.
|
||||
|
||||
## Doc Updates (Follow-up)
|
||||
|
||||
- Add a new docs page for `/v1/responses` usage and examples.
|
||||
- Update `/gateway/openai-http-api` with a legacy note and pointer to `/v1/responses`.
|
||||
@ -1,195 +0,0 @@
|
||||
---
|
||||
summary: "Production plan for reliable interactive process supervision (PTY + non-PTY) with explicit ownership, unified lifecycle, and deterministic cleanup"
|
||||
read_when:
|
||||
- Working on exec/process lifecycle ownership and cleanup
|
||||
- Debugging PTY and non-PTY supervision behavior
|
||||
owner: "openclaw"
|
||||
status: "in-progress"
|
||||
last_updated: "2026-02-15"
|
||||
title: "PTY and Process Supervision Plan"
|
||||
---
|
||||
|
||||
# PTY and Process Supervision Plan
|
||||
|
||||
## 1. Problem and goal
|
||||
|
||||
We need one reliable lifecycle for long-running command execution across:
|
||||
|
||||
- `exec` foreground runs
|
||||
- `exec` background runs
|
||||
- `process` follow up actions (`poll`, `log`, `send-keys`, `paste`, `submit`, `kill`, `remove`)
|
||||
- CLI agent runner subprocesses
|
||||
|
||||
The goal is not just to support PTY. The goal is predictable ownership, cancellation, timeout, and cleanup with no unsafe process matching heuristics.
|
||||
|
||||
## 2. Scope and boundaries
|
||||
|
||||
- Keep implementation internal in `src/process/supervisor`.
|
||||
- Do not create a new package for this.
|
||||
- Keep current behavior compatibility where practical.
|
||||
- Do not broaden scope to terminal replay or tmux style session persistence.
|
||||
|
||||
## 3. Implemented in this branch
|
||||
|
||||
### Supervisor baseline already present
|
||||
|
||||
- Supervisor module is in place under `src/process/supervisor/*`.
|
||||
- Exec runtime and CLI runner are already routed through supervisor spawn and wait.
|
||||
- Registry finalization is idempotent.
|
||||
|
||||
### This pass completed
|
||||
|
||||
1. Explicit PTY command contract
|
||||
|
||||
- `SpawnInput` is now a discriminated union in `src/process/supervisor/types.ts`.
|
||||
- PTY runs require `ptyCommand` instead of reusing generic `argv`.
|
||||
- Supervisor no longer rebuilds PTY command strings from argv joins in `src/process/supervisor/supervisor.ts`.
|
||||
- Exec runtime now passes `ptyCommand` directly in `src/agents/bash-tools.exec-runtime.ts`.
|
||||
|
||||
2. Process layer type decoupling
|
||||
|
||||
- Supervisor types no longer import `SessionStdin` from agents.
|
||||
- Process local stdin contract lives in `src/process/supervisor/types.ts` (`ManagedRunStdin`).
|
||||
- Adapters now depend only on process level types:
|
||||
- `src/process/supervisor/adapters/child.ts`
|
||||
- `src/process/supervisor/adapters/pty.ts`
|
||||
|
||||
3. Process tool lifecycle ownership improvement
|
||||
|
||||
- `src/agents/bash-tools.process.ts` now requests cancellation through supervisor first.
|
||||
- `process kill/remove` now use process-tree fallback termination when supervisor lookup misses.
|
||||
- `remove` keeps deterministic remove behavior by dropping running session entries immediately after termination is requested.
|
||||
|
||||
4. Single source watchdog defaults
|
||||
|
||||
- Added shared defaults in `src/agents/cli-watchdog-defaults.ts`.
|
||||
- `src/agents/cli-backends.ts` consumes the shared defaults.
|
||||
- `src/agents/cli-runner/reliability.ts` consumes the same shared defaults.
|
||||
|
||||
5. Dead helper cleanup
|
||||
|
||||
- Removed unused `killSession` helper path from `src/agents/bash-tools.shared.ts`.
|
||||
|
||||
6. Direct supervisor path tests added
|
||||
|
||||
- Added `src/agents/bash-tools.process.supervisor.test.ts` to cover kill and remove routing through supervisor cancellation.
|
||||
|
||||
7. Reliability gap fixes completed
|
||||
|
||||
- `src/agents/bash-tools.process.ts` now falls back to real OS-level process termination when supervisor lookup misses.
|
||||
- `src/process/supervisor/adapters/child.ts` now uses process-tree termination semantics for default cancel/timeout kill paths.
|
||||
- Added shared process-tree utility in `src/process/kill-tree.ts`.
|
||||
|
||||
8. PTY contract edge-case coverage added
|
||||
|
||||
- Added `src/process/supervisor/supervisor.pty-command.test.ts` for verbatim PTY command forwarding and empty-command rejection.
|
||||
- Added `src/process/supervisor/adapters/child.test.ts` for process-tree kill behavior in child adapter cancellation.
|
||||
|
||||
## 4. Remaining gaps and decisions
|
||||
|
||||
### Reliability status
|
||||
|
||||
The two required reliability gaps for this pass are now closed:
|
||||
|
||||
- `process kill/remove` now has a real OS termination fallback when supervisor lookup misses.
|
||||
- child cancel/timeout now uses process-tree kill semantics for default kill path.
|
||||
- Regression tests were added for both behaviors.
|
||||
|
||||
### Durability and startup reconciliation
|
||||
|
||||
Restart behavior is now explicitly defined as in-memory lifecycle only.
|
||||
|
||||
- `reconcileOrphans()` remains a no-op in `src/process/supervisor/supervisor.ts` by design.
|
||||
- Active runs are not recovered after process restart.
|
||||
- This boundary is intentional for this implementation pass to avoid partial persistence risks.
|
||||
|
||||
### Maintainability follow-ups
|
||||
|
||||
1. `runExecProcess` in `src/agents/bash-tools.exec-runtime.ts` still handles multiple responsibilities and can be split into focused helpers in a follow-up.
|
||||
|
||||
## 5. Implementation plan
|
||||
|
||||
The implementation pass for required reliability and contract items is complete.
|
||||
|
||||
Completed:
|
||||
|
||||
- `process kill/remove` fallback real termination
|
||||
- process-tree cancellation for child adapter default kill path
|
||||
- regression tests for fallback kill and child adapter kill path
|
||||
- PTY command edge-case tests under explicit `ptyCommand`
|
||||
- explicit in-memory restart boundary with `reconcileOrphans()` no-op by design
|
||||
|
||||
Optional follow-up:
|
||||
|
||||
- split `runExecProcess` into focused helpers with no behavior drift
|
||||
|
||||
## 6. File map
|
||||
|
||||
### Process supervisor
|
||||
|
||||
- `src/process/supervisor/types.ts` updated with discriminated spawn input and process local stdin contract.
|
||||
- `src/process/supervisor/supervisor.ts` updated to use explicit `ptyCommand`.
|
||||
- `src/process/supervisor/adapters/child.ts` and `src/process/supervisor/adapters/pty.ts` decoupled from agent types.
|
||||
- `src/process/supervisor/registry.ts` idempotent finalize unchanged and retained.
|
||||
|
||||
### Exec and process integration
|
||||
|
||||
- `src/agents/bash-tools.exec-runtime.ts` updated to pass PTY command explicitly and keep fallback path.
|
||||
- `src/agents/bash-tools.process.ts` updated to cancel via supervisor with real process-tree fallback termination.
|
||||
- `src/agents/bash-tools.shared.ts` removed direct kill helper path.
|
||||
|
||||
### CLI reliability
|
||||
|
||||
- `src/agents/cli-watchdog-defaults.ts` added as shared baseline.
|
||||
- `src/agents/cli-backends.ts` and `src/agents/cli-runner/reliability.ts` now consume same defaults.
|
||||
|
||||
## 7. Validation run in this pass
|
||||
|
||||
Unit tests:
|
||||
|
||||
- `pnpm vitest src/process/supervisor/registry.test.ts`
|
||||
- `pnpm vitest src/process/supervisor/supervisor.test.ts`
|
||||
- `pnpm vitest src/process/supervisor/supervisor.pty-command.test.ts`
|
||||
- `pnpm vitest src/process/supervisor/adapters/child.test.ts`
|
||||
- `pnpm vitest src/agents/cli-backends.test.ts`
|
||||
- `pnpm vitest src/agents/bash-tools.exec.pty-cleanup.test.ts`
|
||||
- `pnpm vitest src/agents/bash-tools.process.poll-timeout.test.ts`
|
||||
- `pnpm vitest src/agents/bash-tools.process.supervisor.test.ts`
|
||||
- `pnpm vitest src/process/exec.test.ts`
|
||||
|
||||
E2E targets:
|
||||
|
||||
- `pnpm vitest src/agents/cli-runner.test.ts`
|
||||
- `pnpm vitest run src/agents/bash-tools.exec.pty-fallback.test.ts src/agents/bash-tools.exec.background-abort.test.ts src/agents/bash-tools.process.send-keys.test.ts`
|
||||
|
||||
Typecheck note:
|
||||
|
||||
- Use `pnpm build` (and `pnpm check` for full lint/docs gate) in this repo. Older notes that mention `pnpm tsgo` are obsolete.
|
||||
|
||||
## 8. Operational guarantees preserved
|
||||
|
||||
- Exec env hardening behavior is unchanged.
|
||||
- Approval and allowlist flow is unchanged.
|
||||
- Output sanitization and output caps are unchanged.
|
||||
- PTY adapter still guarantees wait settlement on forced kill and listener disposal.
|
||||
|
||||
## 9. Definition of done
|
||||
|
||||
1. Supervisor is lifecycle owner for managed runs.
|
||||
2. PTY spawn uses explicit command contract with no argv reconstruction.
|
||||
3. Process layer has no type dependency on agent layer for supervisor stdin contracts.
|
||||
4. Watchdog defaults are single source.
|
||||
5. Targeted unit and e2e tests remain green.
|
||||
6. Restart durability boundary is explicitly documented or fully implemented.
|
||||
|
||||
## 10. Summary
|
||||
|
||||
The branch now has a coherent and safer supervision shape:
|
||||
|
||||
- explicit PTY contract
|
||||
- cleaner process layering
|
||||
- supervisor driven cancellation path for process operations
|
||||
- real fallback termination when supervisor lookup misses
|
||||
- process-tree cancellation for child-run default kill paths
|
||||
- unified watchdog defaults
|
||||
- explicit in-memory restart boundary (no orphan reconciliation across restart in this pass)
|
||||
@ -1,226 +0,0 @@
|
||||
---
|
||||
summary: "Channel agnostic session binding architecture and iteration 1 delivery scope"
|
||||
read_when:
|
||||
- Refactoring channel-agnostic session routing and bindings
|
||||
- Investigating duplicate, stale, or missing session delivery across channels
|
||||
owner: "onutc"
|
||||
status: "in-progress"
|
||||
last_updated: "2026-02-21"
|
||||
title: "Session Binding Channel Agnostic Plan"
|
||||
---
|
||||
|
||||
# Session Binding Channel Agnostic Plan
|
||||
|
||||
## Overview
|
||||
|
||||
This document defines the long term channel agnostic session binding model and the concrete scope for the next implementation iteration.
|
||||
|
||||
Goal:
|
||||
|
||||
- make subagent bound session routing a core capability
|
||||
- keep channel specific behavior in adapters
|
||||
- avoid regressions in normal Discord behavior
|
||||
|
||||
## Why this exists
|
||||
|
||||
Current behavior mixes:
|
||||
|
||||
- completion content policy
|
||||
- destination routing policy
|
||||
- Discord specific details
|
||||
|
||||
This caused edge cases such as:
|
||||
|
||||
- duplicate main and thread delivery under concurrent runs
|
||||
- stale token usage on reused binding managers
|
||||
- missing activity accounting for webhook sends
|
||||
|
||||
## Iteration 1 scope
|
||||
|
||||
This iteration is intentionally limited.
|
||||
|
||||
### 1. Add channel agnostic core interfaces
|
||||
|
||||
Add core types and service interfaces for bindings and routing.
|
||||
|
||||
Proposed core types:
|
||||
|
||||
```ts
|
||||
export type BindingTargetKind = "subagent" | "session";
|
||||
export type BindingStatus = "active" | "ending" | "ended";
|
||||
|
||||
export type ConversationRef = {
|
||||
channel: string;
|
||||
accountId: string;
|
||||
conversationId: string;
|
||||
parentConversationId?: string;
|
||||
};
|
||||
|
||||
export type SessionBindingRecord = {
|
||||
bindingId: string;
|
||||
targetSessionKey: string;
|
||||
targetKind: BindingTargetKind;
|
||||
conversation: ConversationRef;
|
||||
status: BindingStatus;
|
||||
boundAt: number;
|
||||
expiresAt?: number;
|
||||
metadata?: Record<string, unknown>;
|
||||
};
|
||||
```
|
||||
|
||||
Core service contract:
|
||||
|
||||
```ts
|
||||
export interface SessionBindingService {
|
||||
bind(input: {
|
||||
targetSessionKey: string;
|
||||
targetKind: BindingTargetKind;
|
||||
conversation: ConversationRef;
|
||||
metadata?: Record<string, unknown>;
|
||||
ttlMs?: number;
|
||||
}): Promise<SessionBindingRecord>;
|
||||
|
||||
listBySession(targetSessionKey: string): SessionBindingRecord[];
|
||||
resolveByConversation(ref: ConversationRef): SessionBindingRecord | null;
|
||||
touch(bindingId: string, at?: number): void;
|
||||
unbind(input: {
|
||||
bindingId?: string;
|
||||
targetSessionKey?: string;
|
||||
reason: string;
|
||||
}): Promise<SessionBindingRecord[]>;
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Add one core delivery router for subagent completions
|
||||
|
||||
Add a single destination resolution path for completion events.
|
||||
|
||||
Router contract:
|
||||
|
||||
```ts
|
||||
export interface BoundDeliveryRouter {
|
||||
resolveDestination(input: {
|
||||
eventKind: "task_completion";
|
||||
targetSessionKey: string;
|
||||
requester?: ConversationRef;
|
||||
failClosed: boolean;
|
||||
}): {
|
||||
binding: SessionBindingRecord | null;
|
||||
mode: "bound" | "fallback";
|
||||
reason: string;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
For this iteration:
|
||||
|
||||
- only `task_completion` is routed through this new path
|
||||
- existing paths for other event kinds remain as-is
|
||||
|
||||
### 3. Keep Discord as adapter
|
||||
|
||||
Discord remains the first adapter implementation.
|
||||
|
||||
Adapter responsibilities:
|
||||
|
||||
- create/reuse thread conversations
|
||||
- send bound messages via webhook or channel send
|
||||
- validate thread state (archived/deleted)
|
||||
- map adapter metadata (webhook identity, thread ids)
|
||||
|
||||
### 4. Fix currently known correctness issues
|
||||
|
||||
Required in this iteration:
|
||||
|
||||
- refresh token usage when reusing existing thread binding manager
|
||||
- record outbound activity for webhook based Discord sends
|
||||
- stop implicit main channel fallback when a bound thread destination is selected for session mode completion
|
||||
|
||||
### 5. Preserve current runtime safety defaults
|
||||
|
||||
No behavior change for users with thread bound spawn disabled.
|
||||
|
||||
Defaults stay:
|
||||
|
||||
- `channels.discord.threadBindings.spawnSubagentSessions = false`
|
||||
|
||||
Result:
|
||||
|
||||
- normal Discord users stay on current behavior
|
||||
- new core path affects only bound session completion routing where enabled
|
||||
|
||||
## Not in iteration 1
|
||||
|
||||
Explicitly deferred:
|
||||
|
||||
- ACP binding targets (`targetKind: "acp"`)
|
||||
- new channel adapters beyond Discord
|
||||
- global replacement of all delivery paths (`spawn_ack`, future `subagent_message`)
|
||||
- protocol level changes
|
||||
- store migration/versioning redesign for all binding persistence
|
||||
|
||||
Notes on ACP:
|
||||
|
||||
- interface design keeps room for ACP
|
||||
- ACP implementation is not started in this iteration
|
||||
|
||||
## Routing invariants
|
||||
|
||||
These invariants are mandatory for iteration 1.
|
||||
|
||||
- destination selection and content generation are separate steps
|
||||
- if session mode completion resolves to an active bound destination, delivery must target that destination
|
||||
- no hidden reroute from bound destination to main channel
|
||||
- fallback behavior must be explicit and observable
|
||||
|
||||
## Compatibility and rollout
|
||||
|
||||
Compatibility target:
|
||||
|
||||
- no regression for users with thread bound spawning off
|
||||
- no change to non-Discord channels in this iteration
|
||||
|
||||
Rollout:
|
||||
|
||||
1. Land interfaces and router behind current feature gates.
|
||||
2. Route Discord completion mode bound deliveries through router.
|
||||
3. Keep legacy path for non-bound flows.
|
||||
4. Verify with targeted tests and canary runtime logs.
|
||||
|
||||
## Tests required in iteration 1
|
||||
|
||||
Unit and integration coverage required:
|
||||
|
||||
- manager token rotation uses latest token after manager reuse
|
||||
- webhook sends update channel activity timestamps
|
||||
- two active bound sessions in same requester channel do not duplicate to main channel
|
||||
- completion for bound session mode run resolves to thread destination only
|
||||
- disabled spawn flag keeps legacy behavior unchanged
|
||||
|
||||
## Proposed implementation files
|
||||
|
||||
Core:
|
||||
|
||||
- `src/infra/outbound/session-binding-service.ts` (new)
|
||||
- `src/infra/outbound/bound-delivery-router.ts` (new)
|
||||
- `src/agents/subagent-announce.ts` (completion destination resolution integration)
|
||||
|
||||
Discord adapter and runtime:
|
||||
|
||||
- `src/discord/monitor/thread-bindings.manager.ts`
|
||||
- `src/discord/monitor/reply-delivery.ts`
|
||||
- `src/discord/send.outbound.ts`
|
||||
|
||||
Tests:
|
||||
|
||||
- `src/discord/monitor/provider*.test.ts`
|
||||
- `src/discord/monitor/reply-delivery.test.ts`
|
||||
- `src/agents/subagent-announce.format.test.ts`
|
||||
|
||||
## Done criteria for iteration 1
|
||||
|
||||
- core interfaces exist and are wired for completion routing
|
||||
- correctness fixes above are merged with tests
|
||||
- no main and thread duplicate completion delivery in session mode bound runs
|
||||
- no behavior change for disabled bound spawn deployments
|
||||
- ACP remains explicitly deferred
|
||||
@ -1,89 +0,0 @@
|
||||
---
|
||||
summary: "Proposal: long-term command authorization model for ACP-bound conversations"
|
||||
read_when:
|
||||
- Designing native command auth behavior in Telegram/Discord ACP-bound channels/topics
|
||||
title: "ACP Bound Command Authorization (Proposal)"
|
||||
---
|
||||
|
||||
# ACP Bound Command Authorization (Proposal)
|
||||
|
||||
Status: Proposed, **not implemented yet**.
|
||||
|
||||
This document describes a long-term authorization model for native commands in
|
||||
ACP-bound conversations. It is an experiments proposal and does not replace
|
||||
current production behavior.
|
||||
|
||||
For implemented behavior, read source and tests in:
|
||||
|
||||
- `src/telegram/bot-native-commands.ts`
|
||||
- `src/discord/monitor/native-command.ts`
|
||||
- `src/auto-reply/reply/commands-core.ts`
|
||||
|
||||
## Problem
|
||||
|
||||
Today we have command-specific checks (for example `/new` and `/reset`) that
|
||||
need to work inside ACP-bound channels/topics even when allowlists are empty.
|
||||
This solves immediate UX pain, but command-name-based exceptions do not scale.
|
||||
|
||||
## Long-term shape
|
||||
|
||||
Move command authorization from ad-hoc handler logic to command metadata plus a
|
||||
shared policy evaluator.
|
||||
|
||||
### 1) Add auth policy metadata to command definitions
|
||||
|
||||
Each command definition should declare an auth policy. Example shape:
|
||||
|
||||
```ts
|
||||
type CommandAuthPolicy =
|
||||
| { mode: "owner_or_allowlist" } // default, current strict behavior
|
||||
| { mode: "bound_acp_or_owner_or_allowlist" } // allow in explicitly bound ACP conversations
|
||||
| { mode: "owner_only" };
|
||||
```
|
||||
|
||||
`/new` and `/reset` would use `bound_acp_or_owner_or_allowlist`.
|
||||
Most other commands would remain `owner_or_allowlist`.
|
||||
|
||||
### 2) Share one evaluator across channels
|
||||
|
||||
Introduce one helper that evaluates command auth using:
|
||||
|
||||
- command policy metadata
|
||||
- sender authorization state
|
||||
- resolved conversation binding state
|
||||
|
||||
Both Telegram and Discord native handlers should call the same helper to avoid
|
||||
behavior drift.
|
||||
|
||||
### 3) Use binding-match as the bypass boundary
|
||||
|
||||
When policy allows bound ACP bypass, authorize only if a configured binding
|
||||
match was resolved for the current conversation (not just because current
|
||||
session key looks ACP-like).
|
||||
|
||||
This keeps the boundary explicit and minimizes accidental widening.
|
||||
|
||||
## Why this is better
|
||||
|
||||
- Scales to future commands without adding more command-name conditionals.
|
||||
- Keeps behavior consistent across channels.
|
||||
- Preserves current security model by requiring explicit binding match.
|
||||
- Keeps allowlists optional hardening instead of a universal requirement.
|
||||
|
||||
## Rollout plan (future)
|
||||
|
||||
1. Add command auth policy field to command registry types and command data.
|
||||
2. Implement shared evaluator and migrate Telegram + Discord native handlers.
|
||||
3. Move `/new` and `/reset` to metadata-driven policy.
|
||||
4. Add tests per policy mode and channel surface.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- This proposal does not change ACP session lifecycle behavior.
|
||||
- This proposal does not require allowlists for all ACP-bound commands.
|
||||
- This proposal does not change existing route binding semantics.
|
||||
|
||||
## Note
|
||||
|
||||
This proposal is intentionally additive and does not delete or replace existing
|
||||
experiments documents.
|
||||
@ -1,36 +0,0 @@
|
||||
---
|
||||
summary: "Exploration: model config, auth profiles, and fallback behavior"
|
||||
read_when:
|
||||
- Exploring future model selection + auth profile ideas
|
||||
title: "Model Config Exploration"
|
||||
---
|
||||
|
||||
# Model Config (Exploration)
|
||||
|
||||
This document captures **ideas** for future model configuration. It is not a
|
||||
shipping spec. For current behavior, see:
|
||||
|
||||
- [Models](/concepts/models)
|
||||
- [Model failover](/concepts/model-failover)
|
||||
- [OAuth + profiles](/concepts/oauth)
|
||||
|
||||
## Motivation
|
||||
|
||||
Operators want:
|
||||
|
||||
- Multiple auth profiles per provider (personal vs work).
|
||||
- Simple `/model` selection with predictable fallbacks.
|
||||
- Clear separation between text models and image-capable models.
|
||||
|
||||
## Possible direction (high level)
|
||||
|
||||
- Keep model selection simple: `provider/model` with optional aliases.
|
||||
- Let providers have multiple auth profiles, with an explicit order.
|
||||
- Use a global fallback list so all sessions fail over consistently.
|
||||
- Only override image routing when explicitly configured.
|
||||
|
||||
## Open questions
|
||||
|
||||
- Should profile rotation be per-provider or per-model?
|
||||
- How should the UI surface profile selection for a session?
|
||||
- What is the safest migration path from legacy config keys?
|
||||
@ -1,228 +0,0 @@
|
||||
---
|
||||
summary: "Research notes: offline memory system for Clawd workspaces (Markdown source-of-truth + derived index)"
|
||||
read_when:
|
||||
- Designing workspace memory (~/.openclaw/workspace) beyond daily Markdown logs
|
||||
- Deciding: standalone CLI vs deep OpenClaw integration
|
||||
- Adding offline recall + reflection (retain/recall/reflect)
|
||||
title: "Workspace Memory Research"
|
||||
---
|
||||
|
||||
# Workspace Memory v2 (offline): research notes
|
||||
|
||||
Target: Clawd-style workspace (`agents.defaults.workspace`, default `~/.openclaw/workspace`) where “memory” is stored as one Markdown file per day (`memory/YYYY-MM-DD.md`) plus a small set of stable files (e.g. `memory.md`, `SOUL.md`).
|
||||
|
||||
This doc proposes an **offline-first** memory architecture that keeps Markdown as the canonical, reviewable source of truth, but adds **structured recall** (search, entity summaries, confidence updates) via a derived index.
|
||||
|
||||
## Why change?
|
||||
|
||||
The current setup (one file per day) is excellent for:
|
||||
|
||||
- “append-only” journaling
|
||||
- human editing
|
||||
- git-backed durability + auditability
|
||||
- low-friction capture (“just write it down”)
|
||||
|
||||
It’s weak for:
|
||||
|
||||
- high-recall retrieval (“what did we decide about X?”, “last time we tried Y?”)
|
||||
- entity-centric answers (“tell me about Alice / The Castle / warelay”) without rereading many files
|
||||
- opinion/preference stability (and evidence when it changes)
|
||||
- time constraints (“what was true during Nov 2025?”) and conflict resolution
|
||||
|
||||
## Design goals
|
||||
|
||||
- **Offline**: works without network; can run on laptop/Castle; no cloud dependency.
|
||||
- **Explainable**: retrieved items should be attributable (file + location) and separable from inference.
|
||||
- **Low ceremony**: daily logging stays Markdown, no heavy schema work.
|
||||
- **Incremental**: v1 is useful with FTS only; semantic/vector and graphs are optional upgrades.
|
||||
- **Agent-friendly**: makes “recall within token budgets” easy (return small bundles of facts).
|
||||
|
||||
## North star model (Hindsight × Letta)
|
||||
|
||||
Two pieces to blend:
|
||||
|
||||
1. **Letta/MemGPT-style control loop**
|
||||
|
||||
- keep a small “core” always in context (persona + key user facts)
|
||||
- everything else is out-of-context and retrieved via tools
|
||||
- memory writes are explicit tool calls (append/replace/insert), persisted, then re-injected next turn
|
||||
|
||||
2. **Hindsight-style memory substrate**
|
||||
|
||||
- separate what’s observed vs what’s believed vs what’s summarized
|
||||
- support retain/recall/reflect
|
||||
- confidence-bearing opinions that can evolve with evidence
|
||||
- entity-aware retrieval + temporal queries (even without full knowledge graphs)
|
||||
|
||||
## Proposed architecture (Markdown source-of-truth + derived index)
|
||||
|
||||
### Canonical store (git-friendly)
|
||||
|
||||
Keep `~/.openclaw/workspace` as canonical human-readable memory.
|
||||
|
||||
Suggested workspace layout:
|
||||
|
||||
```
|
||||
~/.openclaw/workspace/
|
||||
memory.md # small: durable facts + preferences (core-ish)
|
||||
memory/
|
||||
YYYY-MM-DD.md # daily log (append; narrative)
|
||||
bank/ # “typed” memory pages (stable, reviewable)
|
||||
world.md # objective facts about the world
|
||||
experience.md # what the agent did (first-person)
|
||||
opinions.md # subjective prefs/judgments + confidence + evidence pointers
|
||||
entities/
|
||||
Peter.md
|
||||
The-Castle.md
|
||||
warelay.md
|
||||
...
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- **Daily log stays daily log**. No need to turn it into JSON.
|
||||
- The `bank/` files are **curated**, produced by reflection jobs, and can still be edited by hand.
|
||||
- `memory.md` remains “small + core-ish”: the things you want Clawd to see every session.
|
||||
|
||||
### Derived store (machine recall)
|
||||
|
||||
Add a derived index under the workspace (not necessarily git tracked):
|
||||
|
||||
```
|
||||
~/.openclaw/workspace/.memory/index.sqlite
|
||||
```
|
||||
|
||||
Back it with:
|
||||
|
||||
- SQLite schema for facts + entity links + opinion metadata
|
||||
- SQLite **FTS5** for lexical recall (fast, tiny, offline)
|
||||
- optional embeddings table for semantic recall (still offline)
|
||||
|
||||
The index is always **rebuildable from Markdown**.
|
||||
|
||||
## Retain / Recall / Reflect (operational loop)
|
||||
|
||||
### Retain: normalize daily logs into “facts”
|
||||
|
||||
Hindsight’s key insight that matters here: store **narrative, self-contained facts**, not tiny snippets.
|
||||
|
||||
Practical rule for `memory/YYYY-MM-DD.md`:
|
||||
|
||||
- at end of day (or during), add a `## Retain` section with 2–5 bullets that are:
|
||||
- narrative (cross-turn context preserved)
|
||||
- self-contained (standalone makes sense later)
|
||||
- tagged with type + entity mentions
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
## Retain
|
||||
- W @Peter: Currently in Marrakech (Nov 27–Dec 1, 2025) for Andy’s birthday.
|
||||
- B @warelay: I fixed the Baileys WS crash by wrapping connection.update handlers in try/catch (see memory/2025-11-27.md).
|
||||
- O(c=0.95) @Peter: Prefers concise replies (<1500 chars) on WhatsApp; long content goes into files.
|
||||
```
|
||||
|
||||
Minimal parsing:
|
||||
|
||||
- Type prefix: `W` (world), `B` (experience/biographical), `O` (opinion), `S` (observation/summary; usually generated)
|
||||
- Entities: `@Peter`, `@warelay`, etc (slugs map to `bank/entities/*.md`)
|
||||
- Opinion confidence: `O(c=0.0..1.0)` optional
|
||||
|
||||
If you don’t want authors to think about it: the reflect job can infer these bullets from the rest of the log, but having an explicit `## Retain` section is the easiest “quality lever”.
|
||||
|
||||
### Recall: queries over the derived index
|
||||
|
||||
Recall should support:
|
||||
|
||||
- **lexical**: “find exact terms / names / commands” (FTS5)
|
||||
- **entity**: “tell me about X” (entity pages + entity-linked facts)
|
||||
- **temporal**: “what happened around Nov 27” / “since last week”
|
||||
- **opinion**: “what does Peter prefer?” (with confidence + evidence)
|
||||
|
||||
Return format should be agent-friendly and cite sources:
|
||||
|
||||
- `kind` (`world|experience|opinion|observation`)
|
||||
- `timestamp` (source day, or extracted time range if present)
|
||||
- `entities` (`["Peter","warelay"]`)
|
||||
- `content` (the narrative fact)
|
||||
- `source` (`memory/2025-11-27.md#L12` etc)
|
||||
|
||||
### Reflect: produce stable pages + update beliefs
|
||||
|
||||
Reflection is a scheduled job (daily or heartbeat `ultrathink`) that:
|
||||
|
||||
- updates `bank/entities/*.md` from recent facts (entity summaries)
|
||||
- updates `bank/opinions.md` confidence based on reinforcement/contradiction
|
||||
- optionally proposes edits to `memory.md` (“core-ish” durable facts)
|
||||
|
||||
Opinion evolution (simple, explainable):
|
||||
|
||||
- each opinion has:
|
||||
- statement
|
||||
- confidence `c ∈ [0,1]`
|
||||
- last_updated
|
||||
- evidence links (supporting + contradicting fact IDs)
|
||||
- when new facts arrive:
|
||||
- find candidate opinions by entity overlap + similarity (FTS first, embeddings later)
|
||||
- update confidence by small deltas; big jumps require strong contradiction + repeated evidence
|
||||
|
||||
## CLI integration: standalone vs deep integration
|
||||
|
||||
Recommendation: **deep integration in OpenClaw**, but keep a separable core library.
|
||||
|
||||
### Why integrate into OpenClaw?
|
||||
|
||||
- OpenClaw already knows:
|
||||
- the workspace path (`agents.defaults.workspace`)
|
||||
- the session model + heartbeats
|
||||
- logging + troubleshooting patterns
|
||||
- You want the agent itself to call the tools:
|
||||
- `openclaw memory recall "…" --k 25 --since 30d`
|
||||
- `openclaw memory reflect --since 7d`
|
||||
|
||||
### Why still split a library?
|
||||
|
||||
- keep memory logic testable without gateway/runtime
|
||||
- reuse from other contexts (local scripts, future desktop app, etc.)
|
||||
|
||||
Shape:
|
||||
The memory tooling is intended to be a small CLI + library layer, but this is exploratory only.
|
||||
|
||||
## “S-Collide” / SuCo: when to use it (research)
|
||||
|
||||
If “S-Collide” refers to **SuCo (Subspace Collision)**: it’s an ANN retrieval approach that targets strong recall/latency tradeoffs by using learned/structured collisions in subspaces (paper: arXiv 2411.14754, 2024).
|
||||
|
||||
Pragmatic take for `~/.openclaw/workspace`:
|
||||
|
||||
- **don’t start** with SuCo.
|
||||
- start with SQLite FTS + (optional) simple embeddings; you’ll get most UX wins immediately.
|
||||
- consider SuCo/HNSW/ScaNN-class solutions only once:
|
||||
- corpus is big (tens/hundreds of thousands of chunks)
|
||||
- brute-force embedding search becomes too slow
|
||||
- recall quality is meaningfully bottlenecked by lexical search
|
||||
|
||||
Offline-friendly alternatives (in increasing complexity):
|
||||
|
||||
- SQLite FTS5 + metadata filters (zero ML)
|
||||
- Embeddings + brute force (works surprisingly far if chunk count is low)
|
||||
- HNSW index (common, robust; needs a library binding)
|
||||
- SuCo (research-grade; attractive if there’s a solid implementation you can embed)
|
||||
|
||||
Open question:
|
||||
|
||||
- what’s the **best** offline embedding model for “personal assistant memory” on your machines (laptop + desktop)?
|
||||
- if you already have Ollama: embed with a local model; otherwise ship a small embedding model in the toolchain.
|
||||
|
||||
## Smallest useful pilot
|
||||
|
||||
If you want a minimal, still-useful version:
|
||||
|
||||
- Add `bank/` entity pages and a `## Retain` section in daily logs.
|
||||
- Use SQLite FTS for recall with citations (path + line numbers).
|
||||
- Add embeddings only if recall quality or scale demands it.
|
||||
|
||||
## References
|
||||
|
||||
- Letta / MemGPT concepts: “core memory blocks” + “archival memory” + tool-driven self-editing memory.
|
||||
- Hindsight Technical Report: “retain / recall / reflect”, four-network memory, narrative fact extraction, opinion confidence evolution.
|
||||
- SuCo: arXiv 2411.14754 (2024): “Subspace Collision” approximate nearest neighbor retrieval.
|
||||
@ -159,7 +159,7 @@ Use `--agent <id>` to target a specific agent; omit it to use the configured def
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### “No credentials found”
|
||||
### "No credentials found"
|
||||
|
||||
If the Anthropic token profile is missing, run `claude setup-token` on the
|
||||
**gateway host**, then re-check:
|
||||
|
||||
@ -12,7 +12,7 @@ OpenClaw uses Bonjour (mDNS / DNS‑SD) as a **LAN‑only convenience** to disco
|
||||
an active Gateway (WebSocket endpoint). It is best‑effort and does **not** replace SSH or
|
||||
Tailnet-based connectivity.
|
||||
|
||||
## Wide‑area Bonjour (Unicast DNS‑SD) over Tailscale
|
||||
## Wide-area Bonjour (Unicast DNS-SD) over Tailscale
|
||||
|
||||
If the node and gateway are on different networks, multicast mDNS won’t cross the
|
||||
boundary. You can keep the same discovery UX by switching to **unicast DNS‑SD**
|
||||
@ -38,7 +38,7 @@ iOS/Android nodes browse both `local.` and your configured wide‑area domain.
|
||||
}
|
||||
```
|
||||
|
||||
### One‑time DNS server setup (gateway host)
|
||||
### One-time DNS server setup (gateway host)
|
||||
|
||||
```bash
|
||||
openclaw dns setup --apply
|
||||
@ -84,7 +84,7 @@ Only the Gateway advertises `_openclaw-gw._tcp`.
|
||||
|
||||
- `_openclaw-gw._tcp` — gateway transport beacon (used by macOS/iOS/Android nodes).
|
||||
|
||||
## TXT keys (non‑secret hints)
|
||||
## TXT keys (non-secret hints)
|
||||
|
||||
The Gateway advertises small non‑secret hints to make UI flows convenient:
|
||||
|
||||
|
||||
@ -905,7 +905,9 @@ Time format in system prompt. Default: `auto` (OS preference).
|
||||
- Also used as fallback routing when the selected/default model cannot accept image input.
|
||||
- `imageGenerationModel`: accepts either a string (`"provider/model"`) or an object (`{ primary, fallbacks }`).
|
||||
- Used by the shared image-generation capability and any future tool/plugin surface that generates images.
|
||||
- Typical values: `google/gemini-3-pro-image-preview` for the native Nano Banana-style flow, `fal/fal-ai/flux/dev` for fal, or `openai/gpt-image-1` for OpenAI Images.
|
||||
- If omitted, `image_generate` can still infer a best-effort provider default from compatible auth-backed image-generation providers.
|
||||
- Typical values: `google/gemini-3-pro-image-preview`, `fal/fal-ai/flux/dev`, `openai/gpt-image-1`.
|
||||
- `pdfModel`: accepts either a string (`"provider/model"`) or an object (`{ primary, fallbacks }`).
|
||||
- Used by the `pdf` tool for model routing.
|
||||
- If omitted, the PDF tool falls back to `imageModel`, then to best-effort provider defaults.
|
||||
|
||||
@ -46,7 +46,7 @@ See the [full reference](/gateway/configuration-reference) for every available f
|
||||
```bash
|
||||
openclaw config get agents.defaults.workspace
|
||||
openclaw config set agents.defaults.heartbeat.every "2h"
|
||||
openclaw config unset tools.web.search.apiKey
|
||||
openclaw config unset plugins.entries.brave.config.webSearch.apiKey
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Control UI">
|
||||
|
||||
@ -29,7 +29,7 @@ Protocol details:
|
||||
- [Gateway protocol](/gateway/protocol)
|
||||
- [Bridge protocol (legacy)](/gateway/bridge-protocol)
|
||||
|
||||
## Why we keep both “direct” and SSH
|
||||
## Why we keep both "direct" and SSH
|
||||
|
||||
- **Direct WS** is the best UX on the same network and within a tailnet:
|
||||
- auto-discovery on LAN via Bonjour
|
||||
|
||||
@ -5,6 +5,8 @@ read_when:
|
||||
title: "Network model"
|
||||
---
|
||||
|
||||
# Network Model
|
||||
|
||||
Most operations flow through the Gateway (`openclaw gateway`), a single long-running
|
||||
process that owns channel connections and the WebSocket control plane.
|
||||
|
||||
|
||||
307
docs/gateway/openshell.md
Normal file
307
docs/gateway/openshell.md
Normal file
@ -0,0 +1,307 @@
|
||||
---
|
||||
title: OpenShell
|
||||
summary: "Use OpenShell as a managed sandbox backend for OpenClaw agents"
|
||||
read_when:
|
||||
- You want cloud-managed sandboxes instead of local Docker
|
||||
- You are setting up the OpenShell plugin
|
||||
- You need to choose between mirror and remote workspace modes
|
||||
---
|
||||
|
||||
# OpenShell
|
||||
|
||||
OpenShell is a managed sandbox backend for OpenClaw. Instead of running Docker
|
||||
containers locally, OpenClaw delegates sandbox lifecycle to the `openshell` CLI,
|
||||
which provisions remote environments with SSH-based command execution.
|
||||
|
||||
The OpenShell plugin reuses the same core SSH transport and remote filesystem
|
||||
bridge as the generic [SSH backend](/gateway/sandboxing#ssh-backend). It adds
|
||||
OpenShell-specific lifecycle (`sandbox create/get/delete`, `sandbox ssh-config`)
|
||||
and an optional `mirror` workspace mode.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- The `openshell` CLI installed and on `PATH` (or set a custom path via
|
||||
`plugins.entries.openshell.config.command`)
|
||||
- An OpenShell account with sandbox access
|
||||
- OpenClaw Gateway running on the host
|
||||
|
||||
## Quick start
|
||||
|
||||
1. Enable the plugin and set the sandbox backend:
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: {
|
||||
defaults: {
|
||||
sandbox: {
|
||||
mode: "all",
|
||||
backend: "openshell",
|
||||
scope: "session",
|
||||
workspaceAccess: "rw",
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
entries: {
|
||||
openshell: {
|
||||
enabled: true,
|
||||
config: {
|
||||
from: "openclaw",
|
||||
mode: "remote",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
2. Restart the Gateway. On the next agent turn, OpenClaw creates an OpenShell
|
||||
sandbox and routes tool execution through it.
|
||||
|
||||
3. Verify:
|
||||
|
||||
```bash
|
||||
openclaw sandbox list
|
||||
openclaw sandbox explain
|
||||
```
|
||||
|
||||
## Workspace modes
|
||||
|
||||
This is the most important decision when using OpenShell.
|
||||
|
||||
### `mirror`
|
||||
|
||||
Use `plugins.entries.openshell.config.mode: "mirror"` when you want the **local
|
||||
workspace to stay canonical**.
|
||||
|
||||
Behavior:
|
||||
|
||||
- Before `exec`, OpenClaw syncs the local workspace into the OpenShell sandbox.
|
||||
- After `exec`, OpenClaw syncs the remote workspace back to the local workspace.
|
||||
- File tools still operate through the sandbox bridge, but the local workspace
|
||||
remains the source of truth between turns.
|
||||
|
||||
Best for:
|
||||
|
||||
- You edit files locally outside OpenClaw and want those changes visible in the
|
||||
sandbox automatically.
|
||||
- You want the OpenShell sandbox to behave as much like the Docker backend as
|
||||
possible.
|
||||
- You want the host workspace to reflect sandbox writes after each exec turn.
|
||||
|
||||
Tradeoff: extra sync cost before and after each exec.
|
||||
|
||||
### `remote`
|
||||
|
||||
Use `plugins.entries.openshell.config.mode: "remote"` when you want the
|
||||
**OpenShell workspace to become canonical**.
|
||||
|
||||
Behavior:
|
||||
|
||||
- When the sandbox is first created, OpenClaw seeds the remote workspace from
|
||||
the local workspace once.
|
||||
- After that, `exec`, `read`, `write`, `edit`, and `apply_patch` operate
|
||||
directly against the remote OpenShell workspace.
|
||||
- OpenClaw does **not** sync remote changes back into the local workspace.
|
||||
- Prompt-time media reads still work because file and media tools read through
|
||||
the sandbox bridge.
|
||||
|
||||
Best for:
|
||||
|
||||
- The sandbox should live primarily on the remote side.
|
||||
- You want lower per-turn sync overhead.
|
||||
- You do not want host-local edits to silently overwrite remote sandbox state.
|
||||
|
||||
Important: if you edit files on the host outside OpenClaw after the initial seed,
|
||||
the remote sandbox does **not** see those changes. Use
|
||||
`openclaw sandbox recreate` to re-seed.
|
||||
|
||||
### Choosing a mode
|
||||
|
||||
| | `mirror` | `remote` |
|
||||
| ------------------------ | -------------------------- | ------------------------- |
|
||||
| **Canonical workspace** | Local host | Remote OpenShell |
|
||||
| **Sync direction** | Bidirectional (each exec) | One-time seed |
|
||||
| **Per-turn overhead** | Higher (upload + download) | Lower (direct remote ops) |
|
||||
| **Local edits visible?** | Yes, on next exec | No, until recreate |
|
||||
| **Best for** | Development workflows | Long-running agents, CI |
|
||||
|
||||
## Configuration reference
|
||||
|
||||
All OpenShell config lives under `plugins.entries.openshell.config`:
|
||||
|
||||
| Key | Type | Default | Description |
|
||||
| ------------------------- | ------------------------ | ------------- | ----------------------------------------------------- |
|
||||
| `mode` | `"mirror"` or `"remote"` | `"mirror"` | Workspace sync mode |
|
||||
| `command` | `string` | `"openshell"` | Path or name of the `openshell` CLI |
|
||||
| `from` | `string` | `"openclaw"` | Sandbox source for first-time create |
|
||||
| `gateway` | `string` | — | OpenShell gateway name (`--gateway`) |
|
||||
| `gatewayEndpoint` | `string` | — | OpenShell gateway endpoint URL (`--gateway-endpoint`) |
|
||||
| `policy` | `string` | — | OpenShell policy ID for sandbox creation |
|
||||
| `providers` | `string[]` | `[]` | Provider names to attach when sandbox is created |
|
||||
| `gpu` | `boolean` | `false` | Request GPU resources |
|
||||
| `autoProviders` | `boolean` | `true` | Pass `--auto-providers` during sandbox create |
|
||||
| `remoteWorkspaceDir` | `string` | `"/sandbox"` | Primary writable workspace inside the sandbox |
|
||||
| `remoteAgentWorkspaceDir` | `string` | `"/agent"` | Agent workspace mount path (for read-only access) |
|
||||
| `timeoutSeconds` | `number` | `120` | Timeout for `openshell` CLI operations |
|
||||
|
||||
Sandbox-level settings (`mode`, `scope`, `workspaceAccess`) are configured under
|
||||
`agents.defaults.sandbox` as with any backend. See
|
||||
[Sandboxing](/gateway/sandboxing) for the full matrix.
|
||||
|
||||
## Examples
|
||||
|
||||
### Minimal remote setup
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: {
|
||||
defaults: {
|
||||
sandbox: {
|
||||
mode: "all",
|
||||
backend: "openshell",
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
entries: {
|
||||
openshell: {
|
||||
enabled: true,
|
||||
config: {
|
||||
from: "openclaw",
|
||||
mode: "remote",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Mirror mode with GPU
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: {
|
||||
defaults: {
|
||||
sandbox: {
|
||||
mode: "all",
|
||||
backend: "openshell",
|
||||
scope: "agent",
|
||||
workspaceAccess: "rw",
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
entries: {
|
||||
openshell: {
|
||||
enabled: true,
|
||||
config: {
|
||||
from: "openclaw",
|
||||
mode: "mirror",
|
||||
gpu: true,
|
||||
providers: ["openai"],
|
||||
timeoutSeconds: 180,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Per-agent OpenShell with custom gateway
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: {
|
||||
defaults: {
|
||||
sandbox: { mode: "off" },
|
||||
},
|
||||
list: [
|
||||
{
|
||||
id: "researcher",
|
||||
sandbox: {
|
||||
mode: "all",
|
||||
backend: "openshell",
|
||||
scope: "agent",
|
||||
workspaceAccess: "rw",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
plugins: {
|
||||
entries: {
|
||||
openshell: {
|
||||
enabled: true,
|
||||
config: {
|
||||
from: "openclaw",
|
||||
mode: "remote",
|
||||
gateway: "lab",
|
||||
gatewayEndpoint: "https://lab.example",
|
||||
policy: "strict",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Lifecycle management
|
||||
|
||||
OpenShell sandboxes are managed through the normal sandbox CLI:
|
||||
|
||||
```bash
|
||||
# List all sandbox runtimes (Docker + OpenShell)
|
||||
openclaw sandbox list
|
||||
|
||||
# Inspect effective policy
|
||||
openclaw sandbox explain
|
||||
|
||||
# Recreate (deletes remote workspace, re-seeds on next use)
|
||||
openclaw sandbox recreate --all
|
||||
```
|
||||
|
||||
For `remote` mode, **recreate is especially important**: it deletes the canonical
|
||||
remote workspace for that scope. The next use seeds a fresh remote workspace from
|
||||
the local workspace.
|
||||
|
||||
For `mirror` mode, recreate mainly resets the remote execution environment because
|
||||
the local workspace remains canonical.
|
||||
|
||||
### When to recreate
|
||||
|
||||
Recreate after changing any of these:
|
||||
|
||||
- `agents.defaults.sandbox.backend`
|
||||
- `plugins.entries.openshell.config.from`
|
||||
- `plugins.entries.openshell.config.mode`
|
||||
- `plugins.entries.openshell.config.policy`
|
||||
|
||||
```bash
|
||||
openclaw sandbox recreate --all
|
||||
```
|
||||
|
||||
## Current limitations
|
||||
|
||||
- Sandbox browser is not supported on the OpenShell backend.
|
||||
- `sandbox.docker.binds` does not apply to OpenShell.
|
||||
- Docker-specific runtime knobs under `sandbox.docker.*` apply only to the Docker
|
||||
backend.
|
||||
|
||||
## How it works
|
||||
|
||||
1. OpenClaw calls `openshell sandbox create` (with `--from`, `--gateway`,
|
||||
`--policy`, `--providers`, `--gpu` flags as configured).
|
||||
2. OpenClaw calls `openshell sandbox ssh-config <name>` to get SSH connection
|
||||
details for the sandbox.
|
||||
3. Core writes the SSH config to a temp file and opens an SSH session using the
|
||||
same remote filesystem bridge as the generic SSH backend.
|
||||
4. In `mirror` mode: sync local to remote before exec, run, sync back after exec.
|
||||
5. In `remote` mode: seed once on create, then operate directly on the remote
|
||||
workspace.
|
||||
|
||||
## See also
|
||||
|
||||
- [Sandboxing](/gateway/sandboxing) -- modes, scopes, and backend comparison
|
||||
- [Sandbox vs Tool Policy vs Elevated](/gateway/sandbox-vs-tool-policy-vs-elevated) -- debugging blocked tools
|
||||
- [Multi-Agent Sandbox and Tools](/tools/multi-agent-sandbox-tools) -- per-agent overrides
|
||||
- [Sandbox CLI](/cli/sandbox) -- `openclaw sandbox` commands
|
||||
@ -126,7 +126,7 @@ WebChat no longer uses a separate HTTP port. The SwiftUI chat UI connects direct
|
||||
- Forward `18789` over SSH (see above), then connect clients to `ws://127.0.0.1:18789`.
|
||||
- On macOS, prefer the app’s “Remote over SSH” mode, which manages the tunnel automatically.
|
||||
|
||||
## macOS app “Remote over SSH”
|
||||
## macOS app "Remote over SSH"
|
||||
|
||||
The macOS menu bar app can drive the same setup end-to-end (remote status checks, WebChat, and Voice Wake forwarding).
|
||||
|
||||
|
||||
@ -95,7 +95,7 @@ Available groups:
|
||||
- `group:nodes`: `nodes`
|
||||
- `group:openclaw`: all built-in OpenClaw tools (excludes provider plugins)
|
||||
|
||||
## Elevated: exec-only “run on host”
|
||||
## Elevated: exec-only "run on host"
|
||||
|
||||
Elevated does **not** grant extra tools; it only affects `exec`.
|
||||
|
||||
@ -112,9 +112,9 @@ Gates:
|
||||
|
||||
See [Elevated Mode](/tools/elevated).
|
||||
|
||||
## Common “sandbox jail” fixes
|
||||
## Common "sandbox jail" fixes
|
||||
|
||||
### “Tool X blocked by sandbox tool policy”
|
||||
### "Tool X blocked by sandbox tool policy"
|
||||
|
||||
Fix-it keys (pick one):
|
||||
|
||||
@ -123,6 +123,12 @@ Fix-it keys (pick one):
|
||||
- remove it from `tools.sandbox.tools.deny` (or per-agent `agents.list[].tools.sandbox.tools.deny`)
|
||||
- or add it to `tools.sandbox.tools.allow` (or per-agent allow)
|
||||
|
||||
### “I thought this was main, why is it sandboxed?”
|
||||
### "I thought this was main, why is it sandboxed?"
|
||||
|
||||
In `"non-main"` mode, group/channel keys are _not_ main. Use the main session key (shown by `sandbox explain`) or switch mode to `"off"`.
|
||||
|
||||
## See also
|
||||
|
||||
- [Sandboxing](/gateway/sandboxing) -- full sandbox reference (modes, scopes, backends, images)
|
||||
- [Multi-Agent Sandbox & Tools](/tools/multi-agent-sandbox-tools) -- per-agent overrides and precedence
|
||||
- [Elevated Mode](/tools/elevated)
|
||||
|
||||
@ -65,6 +65,18 @@ Not sandboxed:
|
||||
SSH-specific config lives under `agents.defaults.sandbox.ssh`.
|
||||
OpenShell-specific config lives under `plugins.entries.openshell.config`.
|
||||
|
||||
### Choosing a backend
|
||||
|
||||
| | Docker | SSH | OpenShell |
|
||||
| ------------------- | -------------------------------- | ------------------------------ | --------------------------------------------------- |
|
||||
| **Where it runs** | Local container | Any SSH-accessible host | OpenShell managed sandbox |
|
||||
| **Setup** | `scripts/sandbox-setup.sh` | SSH key + target host | OpenShell plugin enabled |
|
||||
| **Workspace model** | Bind-mount or copy | Remote-canonical (seed once) | `mirror` or `remote` |
|
||||
| **Network control** | `docker.network` (default: none) | Depends on remote host | Depends on OpenShell |
|
||||
| **Browser sandbox** | Supported | Not supported | Not supported yet |
|
||||
| **Bind mounts** | `docker.binds` | N/A | N/A |
|
||||
| **Best for** | Local dev, full isolation | Offloading to a remote machine | Managed remote sandboxes with optional two-way sync |
|
||||
|
||||
### SSH backend
|
||||
|
||||
Use `backend: "ssh"` when you want OpenClaw to sandbox `exec`, file tools, and media reads on
|
||||
@ -120,6 +132,18 @@ Important consequences:
|
||||
- Browser sandboxing is not supported on the SSH backend.
|
||||
- `sandbox.docker.*` settings do not apply to the SSH backend.
|
||||
|
||||
### OpenShell backend
|
||||
|
||||
Use `backend: "openshell"` when you want OpenClaw to sandbox tools in an
|
||||
OpenShell-managed remote environment. For the full setup guide, configuration
|
||||
reference, and workspace mode comparison, see the dedicated
|
||||
[OpenShell page](/gateway/openshell).
|
||||
|
||||
OpenShell reuses the same core SSH transport and remote filesystem bridge as the
|
||||
generic SSH backend, and adds OpenShell-specific lifecycle
|
||||
(`sandbox create/get/delete`, `sandbox ssh-config`) plus the optional `mirror`
|
||||
workspace mode.
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: {
|
||||
@ -153,9 +177,6 @@ OpenShell modes:
|
||||
- `mirror` (default): local workspace stays canonical. OpenClaw syncs local files into OpenShell before exec and syncs the remote workspace back after exec.
|
||||
- `remote`: OpenShell workspace is canonical after the sandbox is created. OpenClaw seeds the remote workspace once from the local workspace, then file tools and exec run directly against the remote sandbox without syncing changes back.
|
||||
|
||||
OpenShell reuses the same core SSH transport and remote filesystem bridge as the generic SSH backend.
|
||||
The plugin adds OpenShell-specific lifecycle (`sandbox create/get/delete`, `sandbox ssh-config`) and the optional `mirror` mode.
|
||||
|
||||
Remote transport details:
|
||||
|
||||
- OpenClaw asks OpenShell for sandbox-specific SSH config via `openshell sandbox ssh-config <name>`.
|
||||
@ -168,11 +189,11 @@ Current OpenShell limitations:
|
||||
- `sandbox.docker.binds` is not supported on the OpenShell backend
|
||||
- Docker-specific runtime knobs under `sandbox.docker.*` still apply only to the Docker backend
|
||||
|
||||
## OpenShell workspace modes
|
||||
#### Workspace modes
|
||||
|
||||
OpenShell has two workspace models. This is the part that matters most in practice.
|
||||
|
||||
### `mirror`
|
||||
##### `mirror`
|
||||
|
||||
Use `plugins.entries.openshell.config.mode: "mirror"` when you want the **local workspace to stay canonical**.
|
||||
|
||||
@ -192,7 +213,7 @@ Tradeoff:
|
||||
|
||||
- extra sync cost before and after exec
|
||||
|
||||
### `remote`
|
||||
##### `remote`
|
||||
|
||||
Use `plugins.entries.openshell.config.mode: "remote"` when you want the **OpenShell workspace to become canonical**.
|
||||
|
||||
@ -219,7 +240,7 @@ Use this when:
|
||||
Choose `mirror` if you think of the sandbox as a temporary execution environment.
|
||||
Choose `remote` if you think of the sandbox as the real workspace.
|
||||
|
||||
## OpenShell lifecycle
|
||||
#### OpenShell lifecycle
|
||||
|
||||
OpenShell sandboxes are still managed through the normal sandbox lifecycle:
|
||||
|
||||
@ -441,6 +462,8 @@ See [Multi-Agent Sandbox & Tools](/tools/multi-agent-sandbox-tools) for preceden
|
||||
|
||||
## Related docs
|
||||
|
||||
- [OpenShell](/gateway/openshell) -- managed sandbox backend setup, workspace modes, and config reference
|
||||
- [Sandbox Configuration](/gateway/configuration#agentsdefaults-sandbox)
|
||||
- [Multi-Agent Sandbox & Tools](/tools/multi-agent-sandbox-tools)
|
||||
- [Sandbox vs Tool Policy vs Elevated](/gateway/sandbox-vs-tool-policy-vs-elevated) -- debugging "why is this blocked?"
|
||||
- [Multi-Agent Sandbox & Tools](/tools/multi-agent-sandbox-tools) -- per-agent overrides and precedence
|
||||
- [Security](/gateway/security)
|
||||
|
||||
@ -81,6 +81,12 @@ Invalid plan target path for models.providers.apiKey: models.providers.openai.ba
|
||||
|
||||
No writes are committed for an invalid plan.
|
||||
|
||||
## Exec provider consent behavior
|
||||
|
||||
- `--dry-run` skips exec SecretRef checks by default.
|
||||
- Plans containing exec SecretRefs/providers are rejected in write mode unless `--allow-exec` is set.
|
||||
- When validating/applying exec-containing plans, pass `--allow-exec` in both dry-run and write commands.
|
||||
|
||||
## Runtime and audit scope notes
|
||||
|
||||
- Ref-only `auth-profiles.json` entries (`keyRef`/`tokenRef`) are included in runtime resolution and audit coverage.
|
||||
@ -94,6 +100,10 @@ openclaw secrets apply --from /tmp/openclaw-secrets-plan.json --dry-run
|
||||
|
||||
# Then apply for real
|
||||
openclaw secrets apply --from /tmp/openclaw-secrets-plan.json
|
||||
|
||||
# For exec-containing plans, opt in explicitly in both modes
|
||||
openclaw secrets apply --from /tmp/openclaw-secrets-plan.json --dry-run --allow-exec
|
||||
openclaw secrets apply --from /tmp/openclaw-secrets-plan.json --allow-exec
|
||||
```
|
||||
|
||||
If apply fails with an invalid target path message, regenerate the plan with `openclaw secrets configure` or fix the target path to a supported shape above.
|
||||
|
||||
@ -414,6 +414,11 @@ Findings include:
|
||||
- precedence shadowing (`auth-profiles.json` taking priority over `openclaw.json` refs)
|
||||
- legacy residues (`auth.json`, OAuth reminders)
|
||||
|
||||
Exec note:
|
||||
|
||||
- By default, audit skips exec SecretRef resolvability checks to avoid command side effects.
|
||||
- Use `openclaw secrets audit --allow-exec` to execute exec providers during audit.
|
||||
|
||||
Header residue note:
|
||||
|
||||
- Sensitive provider header detection is name-heuristic based (common auth/credential header names and fragments such as `authorization`, `x-api-key`, `token`, `secret`, `password`, and `credential`).
|
||||
@ -429,6 +434,11 @@ Interactive helper that:
|
||||
- runs preflight resolution
|
||||
- can apply immediately
|
||||
|
||||
Exec note:
|
||||
|
||||
- Preflight skips exec SecretRef checks unless `--allow-exec` is set.
|
||||
- If you apply directly from `configure --apply` and the plan includes exec refs/providers, keep `--allow-exec` set for the apply step too.
|
||||
|
||||
Helpful modes:
|
||||
|
||||
- `openclaw secrets configure --providers-only`
|
||||
@ -447,9 +457,16 @@ Apply a saved plan:
|
||||
|
||||
```bash
|
||||
openclaw secrets apply --from /tmp/openclaw-secrets-plan.json
|
||||
openclaw secrets apply --from /tmp/openclaw-secrets-plan.json --allow-exec
|
||||
openclaw secrets apply --from /tmp/openclaw-secrets-plan.json --dry-run
|
||||
openclaw secrets apply --from /tmp/openclaw-secrets-plan.json --dry-run --allow-exec
|
||||
```
|
||||
|
||||
Exec note:
|
||||
|
||||
- dry-run skips exec checks unless `--allow-exec` is set.
|
||||
- write mode rejects plans containing exec SecretRefs/providers unless `--allow-exec` is set.
|
||||
|
||||
For strict target/path contract details and exact rejection rules, see:
|
||||
|
||||
- [Secrets Apply Plan Contract](/gateway/secrets-plan-contract)
|
||||
|
||||
@ -499,7 +499,7 @@ Treat the snippet above as **secure DM mode**:
|
||||
|
||||
If you run multiple accounts on the same channel, use `per-account-channel-peer` instead. If the same person contacts you on multiple channels, use `session.identityLinks` to collapse those DM sessions into one canonical identity. See [Session Management](/concepts/session) and [Configuration](/gateway/configuration).
|
||||
|
||||
## Allowlists (DM + groups) — terminology
|
||||
## Allowlists (DM + groups) - terminology
|
||||
|
||||
OpenClaw has two separate “who can trigger me?” layers:
|
||||
|
||||
@ -840,7 +840,7 @@ Avoid:
|
||||
- Exposing relay/control ports over LAN or public Internet.
|
||||
- Tailscale Funnel for browser control endpoints (public exposure).
|
||||
|
||||
### 0.7) Secrets on disk (what’s sensitive)
|
||||
### 0.7) Secrets on disk (sensitive data)
|
||||
|
||||
Assume anything under `~/.openclaw/` (or `$OPENCLAW_STATE_DIR/`) may contain secrets or private data:
|
||||
|
||||
|
||||
@ -13,8 +13,8 @@ Quick answers plus deeper troubleshooting for real-world setups (local dev, VPS,
|
||||
## Table of contents
|
||||
|
||||
- [Quick start and first-run setup]
|
||||
- [Im stuck what's the fastest way to get unstuck?](#im-stuck-whats-the-fastest-way-to-get-unstuck)
|
||||
- [What's the recommended way to install and set up OpenClaw?](#whats-the-recommended-way-to-install-and-set-up-openclaw)
|
||||
- [I am stuck - fastest way to get unstuck](#i-am-stuck---fastest-way-to-get-unstuck)
|
||||
- [Recommended way to install and set up OpenClaw](#recommended-way-to-install-and-set-up-openclaw)
|
||||
- [How do I open the dashboard after onboarding?](#how-do-i-open-the-dashboard-after-onboarding)
|
||||
- [How do I authenticate the dashboard (token) on localhost vs remote?](#how-do-i-authenticate-the-dashboard-token-on-localhost-vs-remote)
|
||||
- [What runtime do I need?](#what-runtime-do-i-need)
|
||||
@ -23,15 +23,15 @@ Quick answers plus deeper troubleshooting for real-world setups (local dev, VPS,
|
||||
- [It is stuck on "wake up my friend" / onboarding will not hatch. What now?](#it-is-stuck-on-wake-up-my-friend-onboarding-will-not-hatch-what-now)
|
||||
- [Can I migrate my setup to a new machine (Mac mini) without redoing onboarding?](#can-i-migrate-my-setup-to-a-new-machine-mac-mini-without-redoing-onboarding)
|
||||
- [Where do I see what is new in the latest version?](#where-do-i-see-what-is-new-in-the-latest-version)
|
||||
- [I can't access docs.openclaw.ai (SSL error). What now?](#i-cant-access-docsopenclawai-ssl-error-what-now)
|
||||
- [What's the difference between stable and beta?](#whats-the-difference-between-stable-and-beta)
|
||||
- [How do I install the beta version, and what's the difference between beta and dev?](#how-do-i-install-the-beta-version-and-whats-the-difference-between-beta-and-dev)
|
||||
- [Cannot access docs.openclaw.ai (SSL error)](#cannot-access-docsopenclawai-ssl-error)
|
||||
- [Difference between stable and beta](#difference-between-stable-and-beta)
|
||||
- [How do I install the beta version and what is the difference between beta and dev](#how-do-i-install-the-beta-version-and-what-is-the-difference-between-beta-and-dev)
|
||||
- [How do I try the latest bits?](#how-do-i-try-the-latest-bits)
|
||||
- [How long does install and onboarding usually take?](#how-long-does-install-and-onboarding-usually-take)
|
||||
- [Installer stuck? How do I get more feedback?](#installer-stuck-how-do-i-get-more-feedback)
|
||||
- [Windows install says git not found or openclaw not recognized](#windows-install-says-git-not-found-or-openclaw-not-recognized)
|
||||
- [Windows exec output shows garbled Chinese text what should I do](#windows-exec-output-shows-garbled-chinese-text-what-should-i-do)
|
||||
- [The docs didn't answer my question - how do I get a better answer?](#the-docs-didnt-answer-my-question-how-do-i-get-a-better-answer)
|
||||
- [The docs did not answer my question - how do I get a better answer](#the-docs-did-not-answer-my-question---how-do-i-get-a-better-answer)
|
||||
- [How do I install OpenClaw on Linux?](#how-do-i-install-openclaw-on-linux)
|
||||
- [How do I install OpenClaw on a VPS?](#how-do-i-install-openclaw-on-a-vps)
|
||||
- [Where are the cloud/VPS install guides?](#where-are-the-cloudvps-install-guides)
|
||||
@ -57,7 +57,7 @@ Quick answers plus deeper troubleshooting for real-world setups (local dev, VPS,
|
||||
- [Can multiple people use one WhatsApp number with different OpenClaw instances?](#can-multiple-people-use-one-whatsapp-number-with-different-openclaw-instances)
|
||||
- [Can I run a "fast chat" agent and an "Opus for coding" agent?](#can-i-run-a-fast-chat-agent-and-an-opus-for-coding-agent)
|
||||
- [Does Homebrew work on Linux?](#does-homebrew-work-on-linux)
|
||||
- [What's the difference between the hackable (git) install and npm install?](#whats-the-difference-between-the-hackable-git-install-and-npm-install)
|
||||
- [Difference between the hackable git install and npm install](#difference-between-the-hackable-git-install-and-npm-install)
|
||||
- [Can I switch between npm and git installs later?](#can-i-switch-between-npm-and-git-installs-later)
|
||||
- [Should I run the Gateway on my laptop or a VPS?](#should-i-run-the-gateway-on-my-laptop-or-a-vps)
|
||||
- [How important is it to run OpenClaw on a dedicated machine?](#how-important-is-it-to-run-openclaw-on-a-dedicated-machine)
|
||||
@ -65,7 +65,7 @@ Quick answers plus deeper troubleshooting for real-world setups (local dev, VPS,
|
||||
- [Can I run OpenClaw in a VM and what are the requirements](#can-i-run-openclaw-in-a-vm-and-what-are-the-requirements)
|
||||
- [What is OpenClaw?](#what-is-openclaw)
|
||||
- [What is OpenClaw, in one paragraph?](#what-is-openclaw-in-one-paragraph)
|
||||
- [What's the value proposition?](#whats-the-value-proposition)
|
||||
- [Value proposition](#value-proposition)
|
||||
- [I just set it up what should I do first](#i-just-set-it-up-what-should-i-do-first)
|
||||
- [What are the top five everyday use cases for OpenClaw](#what-are-the-top-five-everyday-use-cases-for-openclaw)
|
||||
- [Can OpenClaw help with lead gen outreach ads and blogs for a SaaS](#can-openclaw-help-with-lead-gen-outreach-ads-and-blogs-for-a-saas)
|
||||
@ -92,7 +92,7 @@ Quick answers plus deeper troubleshooting for real-world setups (local dev, VPS,
|
||||
- [Is all data used with OpenClaw saved locally?](#is-all-data-used-with-openclaw-saved-locally)
|
||||
- [Where does OpenClaw store its data?](#where-does-openclaw-store-its-data)
|
||||
- [Where should AGENTS.md / SOUL.md / USER.md / MEMORY.md live?](#where-should-agentsmd-soulmd-usermd-memorymd-live)
|
||||
- [What's the recommended backup strategy?](#whats-the-recommended-backup-strategy)
|
||||
- [Recommended backup strategy](#recommended-backup-strategy)
|
||||
- [How do I completely uninstall OpenClaw?](#how-do-i-completely-uninstall-openclaw)
|
||||
- [Can agents work outside the workspace?](#can-agents-work-outside-the-workspace)
|
||||
- [I'm in remote mode - where is the session store?](#im-in-remote-mode-where-is-the-session-store)
|
||||
@ -116,7 +116,7 @@ Quick answers plus deeper troubleshooting for real-world setups (local dev, VPS,
|
||||
- [Is there a benefit to using a node on my personal laptop instead of SSH from a VPS?](#is-there-a-benefit-to-using-a-node-on-my-personal-laptop-instead-of-ssh-from-a-vps)
|
||||
- [Do nodes run a gateway service?](#do-nodes-run-a-gateway-service)
|
||||
- [Is there an API / RPC way to apply config?](#is-there-an-api-rpc-way-to-apply-config)
|
||||
- [What's a minimal "sane" config for a first install?](#whats-a-minimal-sane-config-for-a-first-install)
|
||||
- [Minimal sane config for a first install](#minimal-sane-config-for-a-first-install)
|
||||
- [How do I set up Tailscale on a VPS and connect from my Mac?](#how-do-i-set-up-tailscale-on-a-vps-and-connect-from-my-mac)
|
||||
- [How do I connect a Mac node to a remote Gateway (Tailscale Serve)?](#how-do-i-connect-a-mac-node-to-a-remote-gateway-tailscale-serve)
|
||||
- [Should I install on a second laptop or just add a node?](#should-i-install-on-a-second-laptop-or-just-add-a-node)
|
||||
@ -135,7 +135,7 @@ Quick answers plus deeper troubleshooting for real-world setups (local dev, VPS,
|
||||
- [Why am I getting heartbeat messages every 30 minutes?](#why-am-i-getting-heartbeat-messages-every-30-minutes)
|
||||
- [Do I need to add a "bot account" to a WhatsApp group?](#do-i-need-to-add-a-bot-account-to-a-whatsapp-group)
|
||||
- [How do I get the JID of a WhatsApp group?](#how-do-i-get-the-jid-of-a-whatsapp-group)
|
||||
- [Why doesn't OpenClaw reply in a group?](#why-doesnt-openclaw-reply-in-a-group)
|
||||
- [Why does OpenClaw not reply in a group](#why-does-openclaw-not-reply-in-a-group)
|
||||
- [Do groups/threads share context with DMs?](#do-groupsthreads-share-context-with-dms)
|
||||
- [How many workspaces and agents can I create?](#how-many-workspaces-and-agents-can-i-create)
|
||||
- [Can I run multiple bots or chats at the same time (Slack), and how should I set that up?](#can-i-run-multiple-bots-or-chats-at-the-same-time-slack-and-how-should-i-set-that-up)
|
||||
@ -162,7 +162,7 @@ Quick answers plus deeper troubleshooting for real-world setups (local dev, VPS,
|
||||
- [What is an auth profile?](#what-is-an-auth-profile)
|
||||
- [What are typical profile IDs?](#what-are-typical-profile-ids)
|
||||
- [Can I control which auth profile is tried first?](#can-i-control-which-auth-profile-is-tried-first)
|
||||
- [OAuth vs API key: what's the difference?](#oauth-vs-api-key-whats-the-difference)
|
||||
- [OAuth vs API key - what is the difference](#oauth-vs-api-key---what-is-the-difference)
|
||||
- [Gateway: ports, "already running", and remote mode](#gateway-ports-already-running-and-remote-mode)
|
||||
- [What port does the Gateway use?](#what-port-does-the-gateway-use)
|
||||
- [Why does `openclaw gateway status` say `Runtime: running` but `RPC probe: failed`?](#why-does-openclaw-gateway-status-say-runtime-running-but-rpc-probe-failed)
|
||||
@ -170,7 +170,7 @@ Quick answers plus deeper troubleshooting for real-world setups (local dev, VPS,
|
||||
- [What does "another gateway instance is already listening" mean?](#what-does-another-gateway-instance-is-already-listening-mean)
|
||||
- [How do I run OpenClaw in remote mode (client connects to a Gateway elsewhere)?](#how-do-i-run-openclaw-in-remote-mode-client-connects-to-a-gateway-elsewhere)
|
||||
- [The Control UI says "unauthorized" (or keeps reconnecting). What now?](#the-control-ui-says-unauthorized-or-keeps-reconnecting-what-now)
|
||||
- [I set `gateway.bind: "tailnet"` but it can't bind / nothing listens](#i-set-gatewaybind-tailnet-but-it-cant-bind-nothing-listens)
|
||||
- [I set gateway.bind tailnet but it cannot bind and nothing listens](#i-set-gatewaybind-tailnet-but-it-cannot-bind-and-nothing-listens)
|
||||
- [Can I run multiple Gateways on the same host?](#can-i-run-multiple-gateways-on-the-same-host)
|
||||
- [What does "invalid handshake" / code 1008 mean?](#what-does-invalid-handshake-code-1008-mean)
|
||||
- [Logging and debugging](#logging-and-debugging)
|
||||
@ -183,7 +183,7 @@ Quick answers plus deeper troubleshooting for real-world setups (local dev, VPS,
|
||||
- [TUI shows no output. What should I check?](#tui-shows-no-output-what-should-i-check)
|
||||
- [How do I completely stop then start the Gateway?](#how-do-i-completely-stop-then-start-the-gateway)
|
||||
- [ELI5: `openclaw gateway restart` vs `openclaw gateway`](#eli5-openclaw-gateway-restart-vs-openclaw-gateway)
|
||||
- [What's the fastest way to get more details when something fails?](#whats-the-fastest-way-to-get-more-details-when-something-fails)
|
||||
- [Fastest way to get more details when something fails](#fastest-way-to-get-more-details-when-something-fails)
|
||||
- [Media and attachments](#media-and-attachments)
|
||||
- [My skill generated an image/PDF, but nothing was sent](#my-skill-generated-an-imagepdf-but-nothing-was-sent)
|
||||
- [Security and access control](#security-and-access-control)
|
||||
@ -192,15 +192,15 @@ Quick answers plus deeper troubleshooting for real-world setups (local dev, VPS,
|
||||
- [Should my bot have its own email GitHub account or phone number](#should-my-bot-have-its-own-email-github-account-or-phone-number)
|
||||
- [Can I give it autonomy over my text messages and is that safe](#can-i-give-it-autonomy-over-my-text-messages-and-is-that-safe)
|
||||
- [Can I use cheaper models for personal assistant tasks?](#can-i-use-cheaper-models-for-personal-assistant-tasks)
|
||||
- [I ran `/start` in Telegram but didn't get a pairing code](#i-ran-start-in-telegram-but-didnt-get-a-pairing-code)
|
||||
- [I ran /start in Telegram but did not get a pairing code](#i-ran-start-in-telegram-but-did-not-get-a-pairing-code)
|
||||
- [WhatsApp: will it message my contacts? How does pairing work?](#whatsapp-will-it-message-my-contacts-how-does-pairing-work)
|
||||
- [Chat commands, aborting tasks, and "it won't stop"](#chat-commands-aborting-tasks-and-it-wont-stop)
|
||||
- [Chat commands, aborting tasks, and "it will not stop"](#chat-commands-aborting-tasks-and-it-will-not-stop)
|
||||
- [How do I stop internal system messages from showing in chat](#how-do-i-stop-internal-system-messages-from-showing-in-chat)
|
||||
- [How do I stop/cancel a running task?](#how-do-i-stopcancel-a-running-task)
|
||||
- [How do I send a Discord message from Telegram? ("Cross-context messaging denied")](#how-do-i-send-a-discord-message-from-telegram-crosscontext-messaging-denied)
|
||||
- [Why does it feel like the bot "ignores" rapid-fire messages?](#why-does-it-feel-like-the-bot-ignores-rapidfire-messages)
|
||||
|
||||
## First 60 seconds if something's broken
|
||||
## First 60 seconds if something is broken
|
||||
|
||||
1. **Quick status (first check)**
|
||||
|
||||
@ -267,7 +267,7 @@ Quick answers plus deeper troubleshooting for real-world setups (local dev, VPS,
|
||||
|
||||
## Quick start and first-run setup
|
||||
|
||||
### Im stuck what's the fastest way to get unstuck
|
||||
### I am stuck - fastest way to get unstuck
|
||||
|
||||
Use a local AI agent that can **see your machine**. That is far more effective than asking
|
||||
in Discord, because most "I'm stuck" cases are **local config or environment issues** that
|
||||
@ -312,10 +312,10 @@ What they do:
|
||||
Other useful CLI checks: `openclaw status --all`, `openclaw logs --follow`,
|
||||
`openclaw gateway status`, `openclaw health --verbose`.
|
||||
|
||||
Quick debug loop: [First 60 seconds if something's broken](#first-60-seconds-if-somethings-broken).
|
||||
Quick debug loop: [First 60 seconds if something is broken](#first-60-seconds-if-something-is-broken).
|
||||
Install docs: [Install](/install), [Installer flags](/install/installer), [Updating](/install/updating).
|
||||
|
||||
### What's the recommended way to install and set up OpenClaw
|
||||
### Recommended way to install and set up OpenClaw
|
||||
|
||||
The repo recommends running from source and using onboarding:
|
||||
|
||||
@ -445,7 +445,7 @@ Newest entries are at the top. If the top section is marked **Unreleased**, the
|
||||
section is the latest shipped version. Entries are grouped by **Highlights**, **Changes**, and
|
||||
**Fixes** (plus docs/other sections when needed).
|
||||
|
||||
### I can't access docs.openclaw.ai SSL error What now
|
||||
### Cannot access docs.openclaw.ai (SSL error)
|
||||
|
||||
Some Comcast/Xfinity connections incorrectly block `docs.openclaw.ai` via Xfinity
|
||||
Advanced Security. Disable it or allowlist `docs.openclaw.ai`, then retry. More
|
||||
@ -455,7 +455,7 @@ Please help us unblock it by reporting here: [https://spa.xfinity.com/check_url_
|
||||
If you still can't reach the site, the docs are mirrored on GitHub:
|
||||
[https://github.com/openclaw/openclaw/tree/main/docs](https://github.com/openclaw/openclaw/tree/main/docs)
|
||||
|
||||
### What's the difference between stable and beta
|
||||
### Difference between stable and beta
|
||||
|
||||
**Stable** and **beta** are **npm dist-tags**, not separate code lines:
|
||||
|
||||
@ -469,7 +469,7 @@ that same version to `latest`**. That's why beta and stable can point at the
|
||||
See what changed:
|
||||
[https://github.com/openclaw/openclaw/blob/main/CHANGELOG.md](https://github.com/openclaw/openclaw/blob/main/CHANGELOG.md)
|
||||
|
||||
### How do I install the beta version and what's the difference between beta and dev
|
||||
### How do I install the beta version and what is the difference between beta and dev
|
||||
|
||||
**Beta** is the npm dist-tag `beta` (may match `latest`).
|
||||
**Dev** is the moving head of `main` (git); when published, it uses the npm dist-tag `dev`.
|
||||
@ -497,7 +497,7 @@ Rough guide:
|
||||
- **Onboarding:** 5-15 minutes depending on how many channels/models you configure
|
||||
|
||||
If it hangs, use [Installer stuck](/help/faq#installer-stuck-how-do-i-get-more-feedback)
|
||||
and the fast debug loop in [Im stuck](/help/faq#im-stuck--whats-the-fastest-way-to-get-unstuck).
|
||||
and the fast debug loop in [I am stuck](/help/faq#i-am-stuck---fastest-way-to-get-unstuck).
|
||||
|
||||
### How do I try the latest bits
|
||||
|
||||
@ -614,7 +614,7 @@ If you still reproduce this on latest OpenClaw, track/report it in:
|
||||
|
||||
- [Issue #30640](https://github.com/openclaw/openclaw/issues/30640)
|
||||
|
||||
### The docs didn't answer my question how do I get a better answer
|
||||
### The docs did not answer my question - how do I get a better answer
|
||||
|
||||
Use the **hackable (git) install** so you have the full source and docs locally, then ask
|
||||
your bot (or Claude/Codex) _from that folder_ so it can read the repo and answer precisely.
|
||||
@ -882,7 +882,7 @@ brew install <formula>
|
||||
If you run OpenClaw via systemd, ensure the service PATH includes `/home/linuxbrew/.linuxbrew/bin` (or your brew prefix) so `brew`-installed tools resolve in non-login shells.
|
||||
Recent builds also prepend common user bin dirs on Linux systemd services (for example `~/.local/bin`, `~/.npm-global/bin`, `~/.local/share/pnpm`, `~/.bun/bin`) and honor `PNPM_HOME`, `NPM_CONFIG_PREFIX`, `BUN_INSTALL`, `VOLTA_HOME`, `ASDF_DATA_DIR`, `NVM_DIR`, and `FNM_DIR` when set.
|
||||
|
||||
### What's the difference between the hackable git install and npm install
|
||||
### Difference between the hackable git install and npm install
|
||||
|
||||
- **Hackable (git) install:** full source checkout, editable, best for contributors.
|
||||
You run builds locally and can patch code/docs.
|
||||
@ -918,7 +918,7 @@ openclaw gateway restart
|
||||
|
||||
Doctor detects a gateway service entrypoint mismatch and offers to rewrite the service config to match the current install (use `--repair` in automation).
|
||||
|
||||
Backup tips: see [Backup strategy](/help/faq#whats-the-recommended-backup-strategy).
|
||||
Backup tips: see [Backup strategy](/help/faq#recommended-backup-strategy).
|
||||
|
||||
### Should I run the Gateway on my laptop or a VPS
|
||||
|
||||
@ -981,7 +981,7 @@ If you are running macOS in a VM, see [macOS VM](/install/macos-vm).
|
||||
|
||||
OpenClaw is a personal AI assistant you run on your own devices. It replies on the messaging surfaces you already use (WhatsApp, Telegram, Slack, Mattermost (plugin), Discord, Google Chat, Signal, iMessage, WebChat) and can also do voice + a live Canvas on supported platforms. The **Gateway** is the always-on control plane; the assistant is the product.
|
||||
|
||||
### What's the value proposition
|
||||
### Value proposition
|
||||
|
||||
OpenClaw is not "just a Claude wrapper." It's a **local-first control plane** that lets you run a
|
||||
capable assistant on **your own hardware**, reachable from the chat apps you already use, with
|
||||
@ -1381,7 +1381,7 @@ AGENTS.md or MEMORY.md** rather than relying on chat history.
|
||||
|
||||
See [Agent workspace](/concepts/agent-workspace) and [Memory](/concepts/memory).
|
||||
|
||||
### What's the recommended backup strategy
|
||||
### Recommended backup strategy
|
||||
|
||||
Put your **agent workspace** in a **private** git repo and back it up somewhere
|
||||
private (for example GitHub private). This captures memory + AGENTS/SOUL/USER
|
||||
@ -1505,12 +1505,22 @@ Environment alternatives:
|
||||
|
||||
```json5
|
||||
{
|
||||
plugins: {
|
||||
entries: {
|
||||
brave: {
|
||||
config: {
|
||||
webSearch: {
|
||||
apiKey: "BRAVE_API_KEY_HERE",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
tools: {
|
||||
web: {
|
||||
search: {
|
||||
enabled: true,
|
||||
provider: "brave",
|
||||
apiKey: "BRAVE_API_KEY_HERE",
|
||||
maxResults: 5,
|
||||
},
|
||||
fetch: {
|
||||
@ -1521,6 +1531,9 @@ Environment alternatives:
|
||||
}
|
||||
```
|
||||
|
||||
Provider-specific web-search config now lives under `plugins.entries.<plugin>.config.webSearch.*`.
|
||||
Legacy `tools.web.search.*` provider paths still load temporarily for compatibility, but they should not be used for new configs.
|
||||
|
||||
Notes:
|
||||
|
||||
- If you use allowlists, add `web_search`/`web_fetch` or `group:web`.
|
||||
@ -1714,7 +1727,7 @@ Avoid it:
|
||||
|
||||
Docs: [Config](/cli/config), [Configure](/cli/configure), [Doctor](/gateway/doctor).
|
||||
|
||||
### What's a minimal sane config for a first install
|
||||
### Minimal sane config for a first install
|
||||
|
||||
```json5
|
||||
{
|
||||
@ -2006,7 +2019,7 @@ openclaw directory groups list --channel whatsapp
|
||||
|
||||
Docs: [WhatsApp](/channels/whatsapp), [Directory](/cli/directory), [Logs](/cli/logs).
|
||||
|
||||
### Why doesn't OpenClaw reply in a group
|
||||
### Why does OpenClaw not reply in a group
|
||||
|
||||
Two common causes:
|
||||
|
||||
@ -2449,7 +2462,7 @@ To target a specific agent:
|
||||
openclaw models auth order set --provider anthropic --agent main anthropic:default
|
||||
```
|
||||
|
||||
### OAuth vs API key what's the difference
|
||||
### OAuth vs API key - what is the difference
|
||||
|
||||
OpenClaw supports both:
|
||||
|
||||
@ -2541,7 +2554,7 @@ Fix:
|
||||
- `openclaw devices rotate --device <id> --role operator`
|
||||
- Still stuck? Run `openclaw status --all` and follow [Troubleshooting](/gateway/troubleshooting). See [Dashboard](/web/dashboard) for auth details.
|
||||
|
||||
### I set gatewaybind tailnet but it can't bind nothing listens
|
||||
### I set gateway.bind tailnet but it cannot bind and nothing listens
|
||||
|
||||
`tailnet` bind picks a Tailscale IP from your network interfaces (100.64.0.0/10). If the machine isn't on Tailscale (or the interface is down), there's nothing to bind to.
|
||||
|
||||
@ -2772,7 +2785,7 @@ Docs: [Gateway service runbook](/gateway).
|
||||
If you installed the service, use the gateway commands. Use `openclaw gateway` when
|
||||
you want a one-off, foreground run.
|
||||
|
||||
### What's the fastest way to get more details when something fails
|
||||
### Fastest way to get more details when something fails
|
||||
|
||||
Start the Gateway with `--verbose` to get more console detail. Then inspect the log file for channel auth, model routing, and RPC errors.
|
||||
|
||||
@ -2854,7 +2867,7 @@ more susceptible to instruction hijacking, so avoid them for tool-enabled agents
|
||||
or when reading untrusted content. If you must use a smaller model, lock down
|
||||
tools and run inside a sandbox. See [Security](/gateway/security).
|
||||
|
||||
### I ran start in Telegram but didn't get a pairing code
|
||||
### I ran start in Telegram but did not get a pairing code
|
||||
|
||||
Pairing codes are sent **only** when an unknown sender messages the bot and
|
||||
`dmPolicy: "pairing"` is enabled. `/start` by itself doesn't generate a code.
|
||||
@ -2886,7 +2899,7 @@ openclaw pairing list whatsapp
|
||||
|
||||
Wizard phone number prompt: it's used to set your **allowlist/owner** so your own DMs are permitted. It's not used for auto-sending. If you run on your personal WhatsApp number, use that number and enable `channels.whatsapp.selfChatMode`.
|
||||
|
||||
## Chat commands, aborting tasks, and "it won't stop"
|
||||
## Chat commands, aborting tasks, and "it will not stop"
|
||||
|
||||
### How do I stop internal system messages from showing in chat
|
||||
|
||||
|
||||
@ -52,17 +52,21 @@ Think of the suites as “increasing realism” (and increasing flakiness/cost):
|
||||
- Runs in CI
|
||||
- No real keys required
|
||||
- Should be fast and stable
|
||||
- Scheduler note:
|
||||
- `pnpm test` now keeps a small checked-in behavioral manifest for true pool/isolation overrides and a separate timing snapshot for the slowest unit files.
|
||||
- Shared unit coverage stays on, but the wrapper peels the heaviest measured files into dedicated lanes instead of relying on a growing hand-maintained exclusion list.
|
||||
- Refresh the timing snapshot with `pnpm test:perf:update-timings` after major suite shape changes.
|
||||
- Embedded runner note:
|
||||
- When you change message-tool discovery inputs or compaction runtime context,
|
||||
keep both levels of coverage.
|
||||
- Add focused helper regressions for pure routing/normalization seams.
|
||||
- Add focused helper regressions for pure routing/normalization boundaries.
|
||||
- Also keep the embedded runner integration suites healthy:
|
||||
`src/agents/pi-embedded-runner/compact.hooks.test.ts`,
|
||||
`src/agents/pi-embedded-runner/run.overflow-compaction.test.ts`, and
|
||||
`src/agents/pi-embedded-runner/run.overflow-compaction.loop.test.ts`.
|
||||
- Those suites verify that scoped ids and compaction behavior still flow
|
||||
through the real `run.ts` / `compact.ts` paths; helper-only tests are not a
|
||||
sufficient substitute for those seams.
|
||||
sufficient substitute for those integration paths.
|
||||
- Pool note:
|
||||
- OpenClaw uses Vitest `vmForks` on Node 22, 23, and 24 for faster unit shards.
|
||||
- On Node 25+, OpenClaw automatically falls back to regular `forks` until the repo is re-validated there.
|
||||
@ -176,7 +180,7 @@ Live tests are split into two layers so we can isolate failures:
|
||||
- Separates “provider API is broken / key is invalid” from “gateway agent pipeline is broken”
|
||||
- Contains small, isolated regressions (example: OpenAI Responses/Codex Responses reasoning replay + tool-call flows)
|
||||
|
||||
### Layer 2: Gateway + dev agent smoke (what “@openclaw” actually does)
|
||||
### Layer 2: Gateway + dev agent smoke (what "@openclaw" actually does)
|
||||
|
||||
- Test: `src/gateway/gateway-models.profiles.live.test.ts`
|
||||
- Goal:
|
||||
@ -395,7 +399,7 @@ If you want to rely on env keys (e.g. exported in your `~/.profile`), run local
|
||||
- Optional auth behavior:
|
||||
- `OPENCLAW_LIVE_REQUIRE_PROFILE_KEYS=1` to force profile-store auth and ignore env-only overrides
|
||||
|
||||
## Docker runners (optional “works in Linux” checks)
|
||||
## Docker runners (optional "works in Linux" checks)
|
||||
|
||||
These run `pnpm test:live` inside the repo Docker image, mounting your local config dir and workspace (and sourcing `~/.profile` if mounted). They also bind-mount CLI auth homes like `~/.codex`, `~/.claude`, `~/.qwen`, and `~/.minimax` when present, then copy them into the container home before the run so external-CLI OAuth can refresh tokens without mutating the host auth store:
|
||||
|
||||
@ -457,6 +461,55 @@ Future evals should stay deterministic first:
|
||||
- A small suite of skill-focused scenarios (use vs avoid, gating, prompt injection).
|
||||
- Optional live evals (opt-in, env-gated) only after the CI-safe suite is in place.
|
||||
|
||||
## Contract tests (plugin and channel shape)
|
||||
|
||||
Contract tests verify that every registered plugin and channel conforms to its
|
||||
interface contract. They iterate over all discovered plugins and run a suite of
|
||||
shape and behavior assertions.
|
||||
|
||||
### Commands
|
||||
|
||||
- All contracts: `pnpm test:contracts`
|
||||
- Channel contracts only: `pnpm test:contracts:channels`
|
||||
- Provider contracts only: `pnpm test:contracts:plugins`
|
||||
|
||||
### Channel contracts
|
||||
|
||||
Located in `src/channels/plugins/contracts/*.contract.test.ts`:
|
||||
|
||||
- **plugin** - Basic plugin shape (id, name, capabilities)
|
||||
- **setup** - Setup wizard contract
|
||||
- **session-binding** - Session binding behavior
|
||||
- **outbound-payload** - Message payload structure
|
||||
- **inbound** - Inbound message handling
|
||||
- **actions** - Channel action handlers
|
||||
- **threading** - Thread ID handling
|
||||
- **directory** - Directory/roster API
|
||||
- **group-policy** - Group policy enforcement
|
||||
- **status** - Channel status probes
|
||||
- **registry** - Plugin registry shape
|
||||
|
||||
### Provider contracts
|
||||
|
||||
Located in `src/plugins/contracts/*.contract.test.ts`:
|
||||
|
||||
- **auth** - Auth flow contract
|
||||
- **auth-choice** - Auth choice/selection
|
||||
- **catalog** - Model catalog API
|
||||
- **discovery** - Plugin discovery
|
||||
- **loader** - Plugin loading
|
||||
- **runtime** - Provider runtime
|
||||
- **shape** - Plugin shape/interface
|
||||
- **wizard** - Setup wizard
|
||||
|
||||
### When to run
|
||||
|
||||
- After changing plugin-sdk exports or subpaths
|
||||
- After adding or modifying a channel or provider plugin
|
||||
- After refactoring plugin registration or discovery
|
||||
|
||||
Contract tests run in CI and do not require real API keys.
|
||||
|
||||
## Adding regressions (guidance)
|
||||
|
||||
When you fix a provider/model issue discovered in live:
|
||||
|
||||
@ -3,7 +3,7 @@ summary: "Symptom first troubleshooting hub for OpenClaw"
|
||||
read_when:
|
||||
- OpenClaw is not working and you need the fastest path to a fix
|
||||
- You want a triage flow before diving into deep runbooks
|
||||
title: "Troubleshooting"
|
||||
title: "General Troubleshooting"
|
||||
---
|
||||
|
||||
# Troubleshooting
|
||||
@ -63,7 +63,7 @@ Example:
|
||||
}
|
||||
```
|
||||
|
||||
Reference: [/tools/plugin#distribution-npm](/tools/plugin#distribution-npm)
|
||||
Reference: [Plugin architecture](/plugins/architecture)
|
||||
|
||||
## Decision tree
|
||||
|
||||
|
||||
@ -154,7 +154,7 @@ If you're locked out:
|
||||
- SSH access (port 22) is always allowed
|
||||
- The gateway is **only** accessible via Tailscale by design
|
||||
|
||||
### Service won't start
|
||||
### Service will not start
|
||||
|
||||
```bash
|
||||
# Check logs
|
||||
|
||||
@ -112,7 +112,7 @@ After setup completes, enable SSH:
|
||||
|
||||
---
|
||||
|
||||
## 4) Get the VM's IP address
|
||||
## 4) Get the VM IP address
|
||||
|
||||
```bash
|
||||
lume get openclaw
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
---
|
||||
summary: "Move (migrate) a OpenClaw install from one machine to another"
|
||||
summary: "Move (migrate) an OpenClaw install from one machine to another"
|
||||
read_when:
|
||||
- You are moving OpenClaw to a new laptop/server
|
||||
- You want to preserve sessions, auth, and channel logins (WhatsApp, etc.)
|
||||
@ -8,7 +8,7 @@ title: "Migration Guide"
|
||||
|
||||
# Migrating OpenClaw to a new machine
|
||||
|
||||
This guide migrates a OpenClaw Gateway from one machine to another **without redoing onboarding**.
|
||||
This guide migrates an OpenClaw Gateway from one machine to another **without redoing onboarding**.
|
||||
|
||||
The migration is simple conceptually:
|
||||
|
||||
@ -67,7 +67,7 @@ Those live under `$OPENCLAW_STATE_DIR`.
|
||||
|
||||
## Migration steps (recommended)
|
||||
|
||||
### Step 0 — Make a backup (old machine)
|
||||
### Step 0 - Make a backup (old machine)
|
||||
|
||||
On the **old** machine, stop the gateway first so files aren’t changing mid-copy:
|
||||
|
||||
@ -87,7 +87,7 @@ tar -czf openclaw-workspace.tgz .openclaw/workspace
|
||||
|
||||
If you have multiple profiles/state dirs (e.g. `~/.openclaw-main`, `~/.openclaw-work`), archive each.
|
||||
|
||||
### Step 1 — Install OpenClaw on the new machine
|
||||
### Step 1 - Install OpenClaw on the new machine
|
||||
|
||||
On the **new** machine, install the CLI (and Node if needed):
|
||||
|
||||
@ -95,7 +95,7 @@ On the **new** machine, install the CLI (and Node if needed):
|
||||
|
||||
At this stage, it’s OK if onboarding creates a fresh `~/.openclaw/` — you will overwrite it in the next step.
|
||||
|
||||
### Step 2 — Copy the state dir + workspace to the new machine
|
||||
### Step 2 - Copy the state dir + workspace to the new machine
|
||||
|
||||
Copy **both**:
|
||||
|
||||
@ -113,7 +113,7 @@ After copying, ensure:
|
||||
- Hidden directories were included (e.g. `.openclaw/`)
|
||||
- File ownership is correct for the user running the gateway
|
||||
|
||||
### Step 3 — Run Doctor (migrations + service repair)
|
||||
### Step 3 - Run Doctor (migrations + service repair)
|
||||
|
||||
On the **new** machine:
|
||||
|
||||
|
||||
@ -135,7 +135,7 @@ This downloads a portable backup you can restore on any OpenClaw host.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Service won't start
|
||||
### Service will not start
|
||||
|
||||
Check the deploy logs in the Render Dashboard. Common issues:
|
||||
|
||||
|
||||
@ -268,7 +268,7 @@ git checkout main
|
||||
git pull
|
||||
```
|
||||
|
||||
## If you’re stuck
|
||||
## If you are stuck
|
||||
|
||||
- Run `openclaw doctor` again and read the output carefully (it often tells you the fix).
|
||||
- Check: [Troubleshooting](/gateway/troubleshooting)
|
||||
|
||||
@ -67,7 +67,7 @@ openclaw agents add <name>
|
||||
</Note>
|
||||
|
||||
<Tip>
|
||||
推奨:エージェントが `web_search` を使用できるように、Brave Search APIキーを設定してください(`web_fetch` はキーなしで動作します)。最も簡単な方法:`openclaw configure --section web` を実行すると `tools.web.search.apiKey` が保存されます。ドキュメント:[Webツール](/tools/web)。
|
||||
推奨:エージェントが `web_search` を使用できるように、Brave Search APIキーを設定してください(`web_fetch` はキーなしで動作します)。最も簡単な方法:`openclaw configure --section web` を実行すると `plugins.entries.brave.config.webSearch.apiKey` に保存されます。旧 `tools.web.search.apiKey` パスは互換用に引き続き読み込まれますが、新しい設定では使用しないでください。ドキュメント:[Webツール](/tools/web)。
|
||||
</Tip>
|
||||
|
||||
## 関連ドキュメント
|
||||
|
||||
@ -5,7 +5,7 @@ read_when:
|
||||
title: "Audio and Voice Notes"
|
||||
---
|
||||
|
||||
# Audio / Voice Notes — 2026-01-17
|
||||
# Audio / Voice Notes (2026-01-17)
|
||||
|
||||
## What works
|
||||
|
||||
|
||||
@ -5,7 +5,7 @@ read_when:
|
||||
title: "Image and Media Support"
|
||||
---
|
||||
|
||||
# Image & Media Support — 2025-12-05
|
||||
# Image & Media Support (2025-12-05)
|
||||
|
||||
The WhatsApp channel runs via **Baileys Web**. This document captures the current media handling rules for send, gateway, and agent replies.
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@ read_when:
|
||||
title: "Media Understanding"
|
||||
---
|
||||
|
||||
# Media Understanding (Inbound) — 2026-01-17
|
||||
# Media Understanding - Inbound (2026-01-17)
|
||||
|
||||
OpenClaw can **summarize inbound media** (image/audio/video) before the reply pipeline runs. It auto‑detects when local tools or provider keys are available, and can be disabled or customized. If understanding is off, models still receive the original files/URLs as usual.
|
||||
|
||||
@ -21,7 +21,7 @@ integration.
|
||||
- Support **provider APIs** and **CLI fallbacks**.
|
||||
- Allow multiple models with ordered fallback (error/size/timeout).
|
||||
|
||||
## High‑level behavior
|
||||
## High-level behavior
|
||||
|
||||
1. Collect inbound attachments (`MediaPaths`, `MediaUrls`, `MediaTypes`).
|
||||
2. For each enabled capability (image/audio/video), select attachments per policy (default: **first**).
|
||||
@ -334,7 +334,7 @@ When `mode: "all"`, outputs are labeled `[Image 1/2]`, `[Audio 2/2]`, etc.
|
||||
}
|
||||
```
|
||||
|
||||
### 4) Multi‑modal single entry (explicit capabilities)
|
||||
### 4) Multi-modal single entry (explicit capabilities)
|
||||
|
||||
```json5
|
||||
{
|
||||
|
||||
@ -12,7 +12,7 @@ OpenClaw supports Perplexity Search API as a `web_search` provider.
|
||||
It returns structured results with `title`, `url`, and `snippet` fields.
|
||||
|
||||
For compatibility, OpenClaw also supports legacy Perplexity Sonar/OpenRouter setups.
|
||||
If you use `OPENROUTER_API_KEY`, an `sk-or-...` key in `tools.web.search.perplexity.apiKey`, or set `tools.web.search.perplexity.baseUrl` / `model`, the provider switches to the chat-completions path and returns AI-synthesized answers with citations instead of structured Search API results.
|
||||
If you use `OPENROUTER_API_KEY`, an `sk-or-...` key in `plugins.entries.perplexity.config.webSearch.apiKey`, or set `plugins.entries.perplexity.config.webSearch.baseUrl` / `model`, the provider switches to the chat-completions path and returns AI-synthesized answers with citations instead of structured Search API results.
|
||||
|
||||
## Getting a Perplexity API key
|
||||
|
||||
@ -22,12 +22,12 @@ If you use `OPENROUTER_API_KEY`, an `sk-or-...` key in `tools.web.search.perplex
|
||||
|
||||
## OpenRouter compatibility
|
||||
|
||||
If you were already using OpenRouter for Perplexity Sonar, keep `provider: "perplexity"` and set `OPENROUTER_API_KEY` in the Gateway environment, or store an `sk-or-...` key in `tools.web.search.perplexity.apiKey`.
|
||||
If you were already using OpenRouter for Perplexity Sonar, keep `provider: "perplexity"` and set `OPENROUTER_API_KEY` in the Gateway environment, or store an `sk-or-...` key in `plugins.entries.perplexity.config.webSearch.apiKey`.
|
||||
|
||||
Optional legacy controls:
|
||||
Optional compatibility controls:
|
||||
|
||||
- `tools.web.search.perplexity.baseUrl`
|
||||
- `tools.web.search.perplexity.model`
|
||||
- `plugins.entries.perplexity.config.webSearch.baseUrl`
|
||||
- `plugins.entries.perplexity.config.webSearch.model`
|
||||
|
||||
## Config examples
|
||||
|
||||
@ -35,13 +35,21 @@ Optional legacy controls:
|
||||
|
||||
```json5
|
||||
{
|
||||
plugins: {
|
||||
entries: {
|
||||
perplexity: {
|
||||
config: {
|
||||
webSearch: {
|
||||
apiKey: "pplx-...",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
tools: {
|
||||
web: {
|
||||
search: {
|
||||
provider: "perplexity",
|
||||
perplexity: {
|
||||
apiKey: "pplx-...",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -52,15 +60,23 @@ Optional legacy controls:
|
||||
|
||||
```json5
|
||||
{
|
||||
plugins: {
|
||||
entries: {
|
||||
perplexity: {
|
||||
config: {
|
||||
webSearch: {
|
||||
apiKey: "<openrouter-api-key>",
|
||||
baseUrl: "https://openrouter.ai/api/v1",
|
||||
model: "perplexity/sonar-pro",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
tools: {
|
||||
web: {
|
||||
search: {
|
||||
provider: "perplexity",
|
||||
perplexity: {
|
||||
apiKey: "<openrouter-api-key>",
|
||||
baseUrl: "https://openrouter.ai/api/v1",
|
||||
model: "perplexity/sonar-pro",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -70,7 +86,7 @@ Optional legacy controls:
|
||||
## Where to set the key
|
||||
|
||||
**Via config:** run `openclaw configure --section web`. It stores the key in
|
||||
`~/.openclaw/openclaw.json` under `tools.web.search.perplexity.apiKey`.
|
||||
`~/.openclaw/openclaw.json` under `plugins.entries.perplexity.config.webSearch.apiKey`.
|
||||
That field also accepts SecretRef objects.
|
||||
|
||||
**Via environment:** set `PERPLEXITY_API_KEY` or `OPENROUTER_API_KEY`
|
||||
@ -151,7 +167,7 @@ await web_search({
|
||||
## Notes
|
||||
|
||||
- Perplexity Search API returns structured web search results (`title`, `url`, `snippet`)
|
||||
- OpenRouter or explicit `baseUrl` / `model` switches Perplexity back to Sonar chat completions for compatibility
|
||||
- OpenRouter or explicit `plugins.entries.perplexity.config.webSearch.baseUrl` / `model` switches Perplexity back to Sonar chat completions for compatibility
|
||||
- Results are cached for 15 minutes by default (configurable via `cacheTtlMinutes`)
|
||||
|
||||
See [Web tools](/tools/web) for the full web_search configuration.
|
||||
|
||||
@ -76,5 +76,5 @@ If you only want to reset sessions, delete `agents/<agentId>/sessions/` and `age
|
||||
|
||||
## References
|
||||
|
||||
- [https://docs.openclaw.ai/testing](https://docs.openclaw.ai/testing)
|
||||
- [https://docs.openclaw.ai/start/getting-started](https://docs.openclaw.ai/start/getting-started)
|
||||
- [Testing](/help/testing)
|
||||
- [Getting Started](/start/getting-started)
|
||||
|
||||
@ -231,7 +231,7 @@ For the full setup guide, see [Oracle Cloud](/platforms/oracle). For signup tips
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Gateway won't start
|
||||
### Gateway will not start
|
||||
|
||||
```bash
|
||||
openclaw gateway status
|
||||
|
||||
@ -97,7 +97,7 @@ If the gateway status stays on "Starting...", check if a zombie process is holdi
|
||||
openclaw gateway status
|
||||
openclaw gateway stop
|
||||
|
||||
# If you’re not using a LaunchAgent (dev mode / manual runs), find the listener:
|
||||
# If you're not using a LaunchAgent (dev mode / manual runs), find the listener:
|
||||
lsof -nP -iTCP:18789 -sTCP:LISTEN
|
||||
```
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
summary: "How the macOS app reports gateway/Baileys health states"
|
||||
read_when:
|
||||
- Debugging mac app health indicators
|
||||
title: "Health Checks"
|
||||
title: "Health Checks (macOS)"
|
||||
---
|
||||
|
||||
# Health Checks on macOS
|
||||
|
||||
@ -13,7 +13,7 @@ OpenClaw can host **PeekabooBridge** as a local, permission‑aware UI automatio
|
||||
broker. This lets the `peekaboo` CLI drive UI automation while reusing the
|
||||
macOS app’s TCC permissions.
|
||||
|
||||
## What this is (and isn’t)
|
||||
## What this is (and is not)
|
||||
|
||||
- **Host**: OpenClaw.app can act as a PeekabooBridge host.
|
||||
- **Client**: use the `peekaboo` CLI (no separate `openclaw ui ...` surface).
|
||||
|
||||
@ -7,7 +7,7 @@ title: "Remote Control"
|
||||
|
||||
# Remote OpenClaw (macOS ⇄ remote host)
|
||||
|
||||
This flow lets the macOS app act as a full remote control for a OpenClaw gateway running on another host (desktop/server). It’s the app’s **Remote over SSH** (remote run) feature. All features—health checks, Voice Wake forwarding, and Web Chat—reuse the same remote SSH configuration from _Settings → General_.
|
||||
This flow lets the macOS app act as a full remote control for an OpenClaw gateway running on another host (desktop/server). It’s the app’s **Remote over SSH** (remote run) feature. All features—health checks, Voice Wake forwarding, and Web Chat—reuse the same remote SSH configuration from _Settings → General_.
|
||||
|
||||
## Modes
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@ summary: "macOS Skills settings UI and gateway-backed status"
|
||||
read_when:
|
||||
- Updating the macOS Skills settings UI
|
||||
- Changing skills gating or install behavior
|
||||
title: "Skills"
|
||||
title: "Skills (macOS)"
|
||||
---
|
||||
|
||||
# Skills (macOS)
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
summary: "Voice wake and push-to-talk modes plus routing details in the mac app"
|
||||
read_when:
|
||||
- Working on voice wake or PTT pathways
|
||||
title: "Voice Wake"
|
||||
title: "Voice Wake (macOS)"
|
||||
---
|
||||
|
||||
# Voice Wake & Push-to-Talk
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
summary: "How the mac app embeds the gateway WebChat and how to debug it"
|
||||
read_when:
|
||||
- Debugging mac WebChat view or loopback port
|
||||
title: "WebChat"
|
||||
title: "WebChat (macOS)"
|
||||
---
|
||||
|
||||
# WebChat (macOS app)
|
||||
@ -26,7 +26,7 @@ agent (with a session switcher for other sessions).
|
||||
|
||||
- Logs: `./scripts/clawlog.sh` (subsystem `ai.openclaw`, category `WebChatSwiftUI`).
|
||||
|
||||
## How it’s wired
|
||||
## How it is wired
|
||||
|
||||
- Data plane: Gateway WS methods `chat.history`, `chat.send`, `chat.abort`,
|
||||
`chat.inject` and events `chat`, `agent`, `presence`, `tick`, `health`.
|
||||
|
||||
@ -180,7 +180,7 @@ With the VCN locked down (only UDP 41641 open) and the Gateway bound to loopback
|
||||
|
||||
This setup often removes the _need_ for extra host-based firewall rules purely to stop Internet-wide SSH brute force — but you should still keep the OS updated, run `openclaw security audit`, and verify you aren’t accidentally listening on public interfaces.
|
||||
|
||||
### What's Already Protected
|
||||
### Already protected
|
||||
|
||||
| Traditional Step | Needed? | Why |
|
||||
| ------------------ | ----------- | ---------------------------------------------------------------------------- |
|
||||
@ -236,7 +236,7 @@ Free tier ARM instances are popular. Try:
|
||||
- Retry during off-peak hours (early morning)
|
||||
- Use the "Always Free" filter when selecting shape
|
||||
|
||||
### Tailscale won't connect
|
||||
### Tailscale will not connect
|
||||
|
||||
```bash
|
||||
# Check status
|
||||
@ -246,7 +246,7 @@ sudo tailscale status
|
||||
sudo tailscale up --ssh --hostname=openclaw --reset
|
||||
```
|
||||
|
||||
### Gateway won't start
|
||||
### Gateway will not start
|
||||
|
||||
```bash
|
||||
openclaw gateway status
|
||||
@ -254,7 +254,7 @@ openclaw doctor --non-interactive
|
||||
journalctl --user -u openclaw-gateway -n 50
|
||||
```
|
||||
|
||||
### Can't reach Control UI
|
||||
### Cannot reach Control UI
|
||||
|
||||
```bash
|
||||
# Verify Tailscale Serve is running
|
||||
|
||||
@ -33,7 +33,7 @@ Perfect for:
|
||||
**Minimum specs:** 1GB RAM, 1 core, 500MB disk
|
||||
**Recommended:** 2GB+ RAM, 64-bit OS, 16GB+ SD card (or USB SSD)
|
||||
|
||||
## What You'll Need
|
||||
## What you need
|
||||
|
||||
- Raspberry Pi 4 or 5 (2GB+ recommended)
|
||||
- MicroSD card (16GB+) or USB SSD (better performance)
|
||||
@ -354,7 +354,7 @@ free -h
|
||||
- Disable unused services: `sudo systemctl disable cups bluetooth avahi-daemon`
|
||||
- Check CPU throttling: `vcgencmd get_throttled` (should return `0x0`)
|
||||
|
||||
### Service Won't Start
|
||||
### Service will not start
|
||||
|
||||
```bash
|
||||
# Check logs
|
||||
|
||||
@ -35,7 +35,7 @@ export default function (api) {
|
||||
}
|
||||
```
|
||||
|
||||
## Optional tool (opt‑in)
|
||||
## Optional tool (opt-in)
|
||||
|
||||
Optional tools are **never** auto‑enabled. Users must add them to an agent
|
||||
allowlist.
|
||||
|
||||
1344
docs/plugins/architecture.md
Normal file
1344
docs/plugins/architecture.md
Normal file
File diff suppressed because it is too large
Load Diff
196
docs/plugins/building-extensions.md
Normal file
196
docs/plugins/building-extensions.md
Normal file
@ -0,0 +1,196 @@
|
||||
---
|
||||
title: "Building Extensions"
|
||||
summary: "Step-by-step guide for creating OpenClaw channel and provider extensions"
|
||||
read_when:
|
||||
- You want to create a new OpenClaw plugin or extension
|
||||
- You need to understand the plugin SDK import patterns
|
||||
- You are adding a new channel or provider to OpenClaw
|
||||
---
|
||||
|
||||
# Building Extensions
|
||||
|
||||
This guide walks through creating an OpenClaw extension from scratch. Extensions
|
||||
can add channels, model providers, tools, or other capabilities.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- OpenClaw repository cloned and dependencies installed (`pnpm install`)
|
||||
- Familiarity with TypeScript (ESM)
|
||||
|
||||
## Extension structure
|
||||
|
||||
Every extension lives under `extensions/<name>/` and follows this layout:
|
||||
|
||||
```
|
||||
extensions/my-channel/
|
||||
├── package.json # npm metadata + openclaw config
|
||||
├── index.ts # Entry point (defineChannelPluginEntry)
|
||||
├── setup-entry.ts # Setup wizard (optional)
|
||||
├── api.ts # Public contract barrel (optional)
|
||||
├── runtime-api.ts # Internal runtime barrel (optional)
|
||||
└── src/
|
||||
├── channel.ts # Channel adapter implementation
|
||||
├── runtime.ts # Runtime wiring
|
||||
└── *.test.ts # Colocated tests
|
||||
```
|
||||
|
||||
## Step 1: Create the package
|
||||
|
||||
Create `extensions/my-channel/package.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "@openclaw/my-channel",
|
||||
"version": "2026.1.1",
|
||||
"description": "OpenClaw My Channel plugin",
|
||||
"type": "module",
|
||||
"dependencies": {},
|
||||
"openclaw": {
|
||||
"extensions": ["./index.ts"],
|
||||
"setupEntry": "./setup-entry.ts",
|
||||
"channel": {
|
||||
"id": "my-channel",
|
||||
"label": "My Channel",
|
||||
"selectionLabel": "My Channel (plugin)",
|
||||
"docsPath": "/channels/my-channel",
|
||||
"docsLabel": "my-channel",
|
||||
"blurb": "Short description of the channel.",
|
||||
"order": 80
|
||||
},
|
||||
"install": {
|
||||
"npmSpec": "@openclaw/my-channel",
|
||||
"localPath": "extensions/my-channel"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The `openclaw` field tells the plugin system what your extension provides.
|
||||
For provider plugins, use `providers` instead of `channel`.
|
||||
|
||||
## Step 2: Define the entry point
|
||||
|
||||
Create `extensions/my-channel/index.ts`:
|
||||
|
||||
```typescript
|
||||
import { defineChannelPluginEntry } from "openclaw/plugin-sdk/core";
|
||||
|
||||
export default defineChannelPluginEntry({
|
||||
id: "my-channel",
|
||||
name: "My Channel",
|
||||
description: "Connects OpenClaw to My Channel",
|
||||
plugin: {
|
||||
// Channel adapter implementation
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
For provider plugins, use `definePluginEntry` instead.
|
||||
|
||||
## Step 3: Import from focused subpaths
|
||||
|
||||
The plugin SDK exposes 70+ focused subpaths. Always import from specific
|
||||
subpaths rather than the monolithic root:
|
||||
|
||||
```typescript
|
||||
// Correct: focused subpaths
|
||||
import { defineChannelPluginEntry } from "openclaw/plugin-sdk/core";
|
||||
import { resolveOutboundSendDep } from "openclaw/plugin-sdk/channel-runtime";
|
||||
import { createPluginRuntimeStore } from "openclaw/plugin-sdk/runtime-store";
|
||||
import { resolveChannelGroupRequireMention } from "openclaw/plugin-sdk/channel-policy";
|
||||
|
||||
// Wrong: monolithic root (lint will reject this)
|
||||
import { ... } from "openclaw/plugin-sdk";
|
||||
```
|
||||
|
||||
Common subpaths:
|
||||
|
||||
| Subpath | Purpose |
|
||||
| ---------------------------------- | ------------------------------------ |
|
||||
| `plugin-sdk/core` | Plugin entry definitions, base types |
|
||||
| `plugin-sdk/channel-runtime` | Channel runtime helpers |
|
||||
| `plugin-sdk/channel-config-schema` | Config schema builders |
|
||||
| `plugin-sdk/channel-policy` | Group/DM policy helpers |
|
||||
| `plugin-sdk/setup` | Setup wizard adapters |
|
||||
| `plugin-sdk/runtime-store` | Persistent plugin storage |
|
||||
| `plugin-sdk/allow-from` | Allowlist resolution |
|
||||
| `plugin-sdk/reply-payload` | Message reply types |
|
||||
| `plugin-sdk/testing` | Test utilities |
|
||||
|
||||
## Step 4: Use local barrels for internal imports
|
||||
|
||||
Within your extension, create barrel files for internal code sharing instead
|
||||
of importing through the plugin SDK:
|
||||
|
||||
```typescript
|
||||
// api.ts — public contract for this extension
|
||||
export { MyChannelConfig } from "./src/config.js";
|
||||
export { MyChannelRuntime } from "./src/runtime.js";
|
||||
|
||||
// runtime-api.ts — internal-only exports (not for production consumers)
|
||||
export { internalHelper } from "./src/helpers.js";
|
||||
```
|
||||
|
||||
**Self-import guardrail**: never import your own extension back through its
|
||||
published SDK contract path from production files. Route internal imports
|
||||
through `./api.ts` or `./runtime-api.ts` instead. The SDK contract is for
|
||||
external consumers only.
|
||||
|
||||
## Step 5: Add a plugin manifest
|
||||
|
||||
Create `openclaw.plugin.json` in your extension root:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "my-channel",
|
||||
"kind": "channel",
|
||||
"channels": ["my-channel"],
|
||||
"name": "My Channel Plugin",
|
||||
"description": "Connects OpenClaw to My Channel"
|
||||
}
|
||||
```
|
||||
|
||||
See [Plugin manifest](/plugins/manifest) for the full schema.
|
||||
|
||||
## Step 6: Test with contract tests
|
||||
|
||||
OpenClaw runs contract tests against all registered plugins. After adding your
|
||||
extension, run:
|
||||
|
||||
```bash
|
||||
pnpm test:contracts:channels # channel plugins
|
||||
pnpm test:contracts:plugins # provider plugins
|
||||
```
|
||||
|
||||
Contract tests verify your plugin conforms to the expected interface (setup
|
||||
wizard, session binding, message handling, group policy, etc.).
|
||||
|
||||
For unit tests, import test helpers from the public testing surface:
|
||||
|
||||
```typescript
|
||||
import { createTestRuntime } from "openclaw/plugin-sdk/testing";
|
||||
```
|
||||
|
||||
## Lint enforcement
|
||||
|
||||
Three scripts enforce SDK boundaries:
|
||||
|
||||
1. **No monolithic root imports** — `openclaw/plugin-sdk` root is rejected
|
||||
2. **No direct src/ imports** — extensions cannot import `../../src/` directly
|
||||
3. **No self-imports** — extensions cannot import their own `plugin-sdk/<name>` subpath
|
||||
|
||||
Run `pnpm check` to verify all boundaries before committing.
|
||||
|
||||
## Checklist
|
||||
|
||||
Before submitting your extension:
|
||||
|
||||
- [ ] `package.json` has correct `openclaw` metadata
|
||||
- [ ] Entry point uses `defineChannelPluginEntry` or `definePluginEntry`
|
||||
- [ ] All imports use focused `plugin-sdk/<subpath>` paths
|
||||
- [ ] Internal imports use local barrels, not SDK self-imports
|
||||
- [ ] `openclaw.plugin.json` manifest is present and valid
|
||||
- [ ] Contract tests pass (`pnpm test:contracts`)
|
||||
- [ ] Unit tests colocated as `*.test.ts`
|
||||
- [ ] `pnpm check` passes (lint + format)
|
||||
- [ ] Doc page created under `docs/channels/` or `docs/plugins/`
|
||||
@ -1,7 +1,7 @@
|
||||
---
|
||||
summary: "Plugin manifest + JSON schema requirements (strict config validation)"
|
||||
read_when:
|
||||
- You are building a OpenClaw plugin
|
||||
- You are building an OpenClaw plugin
|
||||
- You need to ship a plugin config schema or debug plugin validation errors
|
||||
title: "Plugin Manifest"
|
||||
---
|
||||
@ -32,7 +32,8 @@ Every native OpenClaw plugin **must** ship a `openclaw.plugin.json` file in the
|
||||
plugin errors and block config validation.
|
||||
|
||||
See the full plugin system guide: [Plugins](/tools/plugin).
|
||||
For the public capability model: [Capability model](/tools/plugin#public-capability-model).
|
||||
For the native capability model and current external-compatibility guidance:
|
||||
[Capability model](/plugins/architecture#public-capability-model).
|
||||
|
||||
## Required fields
|
||||
|
||||
@ -120,6 +121,8 @@ Example:
|
||||
- If plugin config exists but the plugin is **disabled**, the config is kept and
|
||||
a **warning** is surfaced in Doctor + logs.
|
||||
|
||||
See [Configuration reference](/configuration) for the full `plugins.*` schema.
|
||||
|
||||
## Notes
|
||||
|
||||
- The manifest is **required for native OpenClaw plugins**, including local filesystem loads.
|
||||
@ -130,7 +133,9 @@ Example:
|
||||
runtime just to inspect env names.
|
||||
- `providerAuthChoices` is the cheap metadata path for auth-choice pickers,
|
||||
`--auth-choice` resolution, preferred-provider mapping, and simple onboarding
|
||||
CLI flag registration before provider runtime loads.
|
||||
CLI flag registration before provider runtime loads. For runtime wizard
|
||||
metadata that requires provider code, see
|
||||
[Provider runtime hooks](/plugins/architecture#provider-runtime-hooks).
|
||||
- Exclusive plugin kinds are selected through `plugins.slots.*`.
|
||||
- `kind: "memory"` is selected by `plugins.slots.memory`.
|
||||
- `kind: "context-engine"` is selected by `plugins.slots.contextEngine`
|
||||
|
||||
@ -312,14 +312,21 @@ Auto-responses use the agent system. Tune with:
|
||||
|
||||
```bash
|
||||
openclaw voicecall call --to "+15555550123" --message "Hello from OpenClaw"
|
||||
openclaw voicecall start --to "+15555550123" # alias for call
|
||||
openclaw voicecall continue --call-id <id> --message "Any questions?"
|
||||
openclaw voicecall speak --call-id <id> --message "One moment"
|
||||
openclaw voicecall end --call-id <id>
|
||||
openclaw voicecall status --call-id <id>
|
||||
openclaw voicecall tail
|
||||
openclaw voicecall latency # summarize turn latency from logs
|
||||
openclaw voicecall expose --mode funnel
|
||||
```
|
||||
|
||||
`latency` reads `calls.jsonl` from the default voice-call storage path. Use
|
||||
`--file <path>` to point at a different log and `--last <n>` to limit analysis
|
||||
to the last N records (default 200). Output includes p50/p90/p99 for turn
|
||||
latency and listen-wait times.
|
||||
|
||||
## Agent tool
|
||||
|
||||
Tool name: `voice_call`
|
||||
|
||||
@ -12,7 +12,7 @@ OpenClaw can use **Amazon Bedrock** models via pi‑ai’s **Bedrock Converse**
|
||||
streaming provider. Bedrock auth uses the **AWS SDK default credential chain**,
|
||||
not an API key.
|
||||
|
||||
## What pi‑ai supports
|
||||
## What pi-ai supports
|
||||
|
||||
- Provider: `amazon-bedrock`
|
||||
- API: `bedrock-converse-stream`
|
||||
|
||||
78
docs/providers/google.md
Normal file
78
docs/providers/google.md
Normal file
@ -0,0 +1,78 @@
|
||||
---
|
||||
title: "Google (Gemini)"
|
||||
summary: "Google Gemini setup (API key + OAuth, image generation, media understanding, web search)"
|
||||
read_when:
|
||||
- You want to use Google Gemini models with OpenClaw
|
||||
- You need the API key or OAuth auth flow
|
||||
---
|
||||
|
||||
# Google (Gemini)
|
||||
|
||||
The Google plugin provides access to Gemini models through Google AI Studio, plus
|
||||
image generation, media understanding (image/audio/video), and web search via
|
||||
Gemini Grounding.
|
||||
|
||||
- Provider: `google`
|
||||
- Auth: `GEMINI_API_KEY` or `GOOGLE_API_KEY`
|
||||
- API: Google Gemini API
|
||||
- Alternative provider: `google-gemini-cli` (OAuth)
|
||||
|
||||
## Quick start
|
||||
|
||||
1. Set the API key:
|
||||
|
||||
```bash
|
||||
openclaw onboard --auth-choice google-api-key
|
||||
```
|
||||
|
||||
2. Set a default model:
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: {
|
||||
defaults: {
|
||||
model: { primary: "google/gemini-3.1-pro-preview" },
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Non-interactive example
|
||||
|
||||
```bash
|
||||
openclaw onboard --non-interactive \
|
||||
--mode local \
|
||||
--auth-choice google-api-key \
|
||||
--gemini-api-key "$GEMINI_API_KEY"
|
||||
```
|
||||
|
||||
## OAuth (Gemini CLI)
|
||||
|
||||
An alternative provider `google-gemini-cli` uses PKCE OAuth instead of an API
|
||||
key. This is an unofficial integration; some users report account
|
||||
restrictions. Use at your own risk.
|
||||
|
||||
Environment variables:
|
||||
|
||||
- `OPENCLAW_GEMINI_OAUTH_CLIENT_ID`
|
||||
- `OPENCLAW_GEMINI_OAUTH_CLIENT_SECRET`
|
||||
|
||||
(Or the `GEMINI_CLI_*` variants.)
|
||||
|
||||
## Capabilities
|
||||
|
||||
| Capability | Supported |
|
||||
| ---------------------- | ----------------- |
|
||||
| Chat completions | Yes |
|
||||
| Image generation | Yes |
|
||||
| Image understanding | Yes |
|
||||
| Audio transcription | Yes |
|
||||
| Video understanding | Yes |
|
||||
| Web search (Grounding) | Yes |
|
||||
| Thinking/reasoning | Yes (Gemini 3.1+) |
|
||||
|
||||
## Environment note
|
||||
|
||||
If the Gateway runs as a daemon (launchd/systemd), make sure `GEMINI_API_KEY`
|
||||
is available to that process (for example, in `~/.openclaw/.env` or via
|
||||
`env.shellEnv`).
|
||||
@ -3,7 +3,7 @@ summary: "Model providers (LLMs) supported by OpenClaw"
|
||||
read_when:
|
||||
- You want to choose a model provider
|
||||
- You need a quick overview of supported LLM backends
|
||||
title: "Model Providers"
|
||||
title: "Provider Directory"
|
||||
---
|
||||
|
||||
# Model Providers
|
||||
@ -30,23 +30,29 @@ Looking for chat channel docs (WhatsApp/Telegram/Discord/Slack/Mattermost (plugi
|
||||
- [Anthropic (API + Claude Code CLI)](/providers/anthropic)
|
||||
- [Cloudflare AI Gateway](/providers/cloudflare-ai-gateway)
|
||||
- [GLM models](/providers/glm)
|
||||
- [Google (Gemini)](/providers/google)
|
||||
- [Hugging Face (Inference)](/providers/huggingface)
|
||||
- [Kilocode](/providers/kilocode)
|
||||
- [LiteLLM (unified gateway)](/providers/litellm)
|
||||
- [MiniMax](/providers/minimax)
|
||||
- [Mistral](/providers/mistral)
|
||||
- [Model Studio (Alibaba Cloud)](/providers/modelstudio)
|
||||
- [Moonshot AI (Kimi + Kimi Coding)](/providers/moonshot)
|
||||
- [NVIDIA](/providers/nvidia)
|
||||
- [Ollama (cloud + local models)](/providers/ollama)
|
||||
- [OpenAI (API + Codex)](/providers/openai)
|
||||
- [OpenCode (Zen + Go)](/providers/opencode)
|
||||
- [OpenRouter](/providers/openrouter)
|
||||
- [Perplexity (web search)](/providers/perplexity-provider)
|
||||
- [Qianfan](/providers/qianfan)
|
||||
- [Qwen (OAuth)](/providers/qwen)
|
||||
- [SGLang (local models)](/providers/sglang)
|
||||
- [Together AI](/providers/together)
|
||||
- [Vercel AI Gateway](/providers/vercel-ai-gateway)
|
||||
- [Venice (Venice AI, privacy-focused)](/providers/venice)
|
||||
- [vLLM (local models)](/providers/vllm)
|
||||
- [Volcengine (Doubao)](/providers/volcengine)
|
||||
- [xAI](/providers/xai)
|
||||
- [Xiaomi](/providers/xiaomi)
|
||||
- [Z.AI](/providers/zai)
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user