mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-26 09:12:13 +08:00
Compare commits
2 Commits
fix/minima
...
cache/cont
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1358cba962 | ||
|
|
6aa591ba56 |
@@ -55,8 +55,6 @@ Check in this order:
|
||||
- Was it fixed before release?
|
||||
3. Exploit path
|
||||
- Does the report show a real boundary bypass, not just prompt injection, local same-user control, or helper-level semantics?
|
||||
- If data only moves between trusted workspace-memory files called out in `SECURITY.md`, do not treat "injection markers" alone as a security bug.
|
||||
- In that case, frame sanitization as optional hardening only if it preserves expected memory workflows.
|
||||
4. Functional tradeoff
|
||||
- If a hardening change would reduce intended user functionality, call that out before proposing it.
|
||||
- Prefer fixes that preserve user workflows over deny-by-default regressions unless the boundary demands it.
|
||||
@@ -106,6 +104,5 @@ gh search prs --repo openclaw/openclaw --match title,body,comments -- "<terms>"
|
||||
- “fixed on main, unreleased” is usually not a close.
|
||||
- “needs attacker-controlled trusted local state first” is usually out of scope.
|
||||
- “same-host same-user process can already read/write local state” is usually out of scope.
|
||||
- “trusted workspace memory promotes/reindexes trusted workspace memory” is usually out of scope unless it crosses a documented boundary.
|
||||
- “helper function behaves differently than documented config semantics” is usually invalid.
|
||||
- If only the severity is wrong but the bug is real, keep it open and narrow the impact in the reply.
|
||||
|
||||
2
.github/actions/setup-node-env/action.yml
vendored
2
.github/actions/setup-node-env/action.yml
vendored
@@ -14,7 +14,7 @@ inputs:
|
||||
pnpm-version:
|
||||
description: pnpm version for corepack.
|
||||
required: false
|
||||
default: "10.32.1"
|
||||
default: "10.23.0"
|
||||
install-bun:
|
||||
description: Whether to install Bun alongside Node.
|
||||
required: false
|
||||
|
||||
@@ -4,7 +4,7 @@ inputs:
|
||||
pnpm-version:
|
||||
description: pnpm version to activate via corepack.
|
||||
required: false
|
||||
default: "10.32.1"
|
||||
default: "10.23.0"
|
||||
cache-key-suffix:
|
||||
description: Suffix appended to the cache key.
|
||||
required: false
|
||||
|
||||
3
.github/workflows/ci.yml
vendored
3
.github/workflows/ci.yml
vendored
@@ -545,6 +545,7 @@ jobs:
|
||||
echo "OPENCLAW_VITEST_MAX_WORKERS=2" >> "$GITHUB_ENV"
|
||||
if [ "$TASK" = "channels" ]; then
|
||||
echo "OPENCLAW_VITEST_MAX_WORKERS=1" >> "$GITHUB_ENV"
|
||||
echo "OPENCLAW_TEST_ISOLATE=1" >> "$GITHUB_ENV"
|
||||
fi
|
||||
|
||||
- name: Download dist artifact
|
||||
@@ -949,7 +950,7 @@ jobs:
|
||||
- name: Setup pnpm + cache store
|
||||
uses: ./.github/actions/setup-pnpm-store-cache
|
||||
with:
|
||||
pnpm-version: "10.32.1"
|
||||
pnpm-version: "10.23.0"
|
||||
cache-key-suffix: "node24"
|
||||
# Sticky disk mount currently retries/fails on every shard and adds ~50s
|
||||
# before install while still yielding zero pnpm store reuse.
|
||||
|
||||
2
.github/workflows/macos-release.yml
vendored
2
.github/workflows/macos-release.yml
vendored
@@ -20,7 +20,7 @@ concurrency:
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
||||
NODE_VERSION: "24.x"
|
||||
PNPM_VERSION: "10.32.1"
|
||||
PNPM_VERSION: "10.23.0"
|
||||
|
||||
jobs:
|
||||
validate_macos_release_request:
|
||||
|
||||
27
.github/workflows/openclaw-npm-release.yml
vendored
27
.github/workflows/openclaw-npm-release.yml
vendored
@@ -37,7 +37,7 @@ concurrency:
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
||||
NODE_VERSION: "24.x"
|
||||
PNPM_VERSION: "10.32.1"
|
||||
PNPM_VERSION: "10.23.0"
|
||||
|
||||
jobs:
|
||||
preflight_openclaw_npm:
|
||||
@@ -129,31 +129,6 @@ jobs:
|
||||
- name: Verify release contents
|
||||
run: pnpm release:check
|
||||
|
||||
- name: Validate live cache credentials
|
||||
if: ${{ github.ref == 'refs/heads/main' }}
|
||||
env:
|
||||
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ -z "${OPENAI_API_KEY}" ]]; then
|
||||
echo "Missing OPENAI_API_KEY secret for release live cache validation." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ -z "${ANTHROPIC_API_KEY}" ]]; then
|
||||
echo "Missing ANTHROPIC_API_KEY secret for release live cache validation." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Verify live prompt cache floors
|
||||
if: ${{ github.ref == 'refs/heads/main' }}
|
||||
env:
|
||||
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
OPENCLAW_LIVE_CACHE_TEST: "1"
|
||||
OPENCLAW_LIVE_TEST: "1"
|
||||
run: pnpm test:live:cache
|
||||
|
||||
- name: Pack prepared npm tarball
|
||||
id: packed_tarball
|
||||
env:
|
||||
|
||||
2
.github/workflows/plugin-clawhub-release.yml
vendored
2
.github/workflows/plugin-clawhub-release.yml
vendored
@@ -23,7 +23,7 @@ concurrency:
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
||||
NODE_VERSION: "24.x"
|
||||
PNPM_VERSION: "10.32.1"
|
||||
PNPM_VERSION: "10.23.0"
|
||||
CLAWHUB_REGISTRY: "https://clawhub.ai"
|
||||
CLAWHUB_REPOSITORY: "openclaw/clawhub"
|
||||
# Pinned to a reviewed ClawHub commit so release behavior stays reproducible.
|
||||
|
||||
2
.github/workflows/plugin-npm-release.yml
vendored
2
.github/workflows/plugin-npm-release.yml
vendored
@@ -38,7 +38,7 @@ concurrency:
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
||||
NODE_VERSION: "24.x"
|
||||
PNPM_VERSION: "10.32.1"
|
||||
PNPM_VERSION: "10.23.0"
|
||||
|
||||
jobs:
|
||||
preview_plugins_npm:
|
||||
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -4,7 +4,7 @@ node_modules
|
||||
docker-compose.override.yml
|
||||
docker-compose.extra.yml
|
||||
dist
|
||||
dist-runtime/
|
||||
dist-runtime
|
||||
pnpm-lock.yaml
|
||||
bun.lock
|
||||
bun.lockb
|
||||
@@ -136,10 +136,6 @@ ui/src/ui/views/__screenshots__
|
||||
ui/.vitest-attachments
|
||||
docs/superpowers
|
||||
|
||||
# Generated docs baseline artifacts (locally generated, only hashes tracked)
|
||||
docs/.generated/*.json
|
||||
docs/.generated/*.jsonl
|
||||
|
||||
# Deprecated changelog fragment workflow
|
||||
changelog/fragments/
|
||||
|
||||
|
||||
25
AGENTS.md
25
AGENTS.md
@@ -55,11 +55,6 @@
|
||||
- Public docs: `docs/gateway/protocol.md`, `docs/gateway/bridge-protocol.md`, `docs/concepts/architecture.md`
|
||||
- Definition files: `src/gateway/protocol/schema.ts`, `src/gateway/protocol/schema/*.ts`, `src/gateway/protocol/index.ts`
|
||||
- Rule: protocol changes are contract changes. Prefer additive evolution; incompatible changes require explicit versioning, docs, and client/codegen follow-through.
|
||||
- Config contract boundary:
|
||||
- Canonical public config lives in exported config types, zod/schema surfaces, schema help/labels, generated config metadata, config baselines, and any user-facing gateway/config payloads. Keep those surfaces aligned.
|
||||
- When a legacy config key is retired from the public contract, remove it from every public config surface above. Keep backward compatibility only through raw-config migration/doctor seams unless explicit product policy says otherwise.
|
||||
- Do not reintroduce removed legacy aliases into public types/schema/help/baselines “for convenience”. If old configs still need to load, handle that in `legacy.migrations.*`, config ingest, or `openclaw doctor --fix`.
|
||||
- `hooks.internal.entries` is the canonical public hook config model. `hooks.internal.handlers` is compatibility-only input and must not be re-exposed in public schema/help/baseline surfaces.
|
||||
- Bundled plugin contract boundary:
|
||||
- Public docs: `docs/plugins/architecture.md`, `docs/plugins/manifest.md`, `docs/plugins/sdk-overview.md`
|
||||
- Definition files: `src/plugins/contracts/registry.ts`, `src/plugins/types.ts`, `src/plugins/public-artifacts.ts`
|
||||
@@ -130,10 +125,10 @@
|
||||
- Formatting gate: the pre-commit hook runs `pnpm format` before `pnpm check`. If you want a formatting-only preflight locally, run `pnpm format` explicitly.
|
||||
- If you need a fast commit loop, `FAST_COMMIT=1 git commit ...` skips the hook’s repo-wide `pnpm format` and `pnpm check`; use that only when you are deliberately covering the touched surface some other way.
|
||||
- Tests: `pnpm test` (vitest); coverage: `pnpm test:coverage`
|
||||
- Generated baseline drift detection uses SHA-256 hash files under `docs/.generated/` (`.sha256` files tracked in git; full JSON baselines are gitignored, generated locally for inspection).
|
||||
- Generated baseline artifacts live together under `docs/.generated/`.
|
||||
- Config schema drift uses `pnpm config:docs:gen` / `pnpm config:docs:check`.
|
||||
- Plugin SDK API drift uses `pnpm plugin-sdk:api:gen` / `pnpm plugin-sdk:api:check`.
|
||||
- If you change config schema/help or the public Plugin SDK surface, run the matching gen command and commit the updated `.sha256` hash file. Keep the two drift-check flows adjacent in scripts/workflows/docs guidance rather than inventing a third pattern.
|
||||
- If you change config schema/help or the public Plugin SDK surface, update the matching baseline artifact and keep the two drift-check flows adjacent in scripts/workflows/docs guidance rather than inventing a third pattern.
|
||||
- For narrowly scoped changes, prefer narrowly scoped tests that directly validate the touched behavior. If no meaningful scoped test exists, say so explicitly and use the next most direct validation available.
|
||||
- Verification modes for work on `main`:
|
||||
- Default mode: `main` is relatively stable. Count pre-commit hook coverage when it already verified the current tree, avoid rerunning the exact same checks just for ceremony, and prefer keeping CI/main green before landing.
|
||||
@@ -145,14 +140,6 @@
|
||||
- For narrowly scoped changes, if unrelated failures already exist on latest `origin/main`, state that clearly, report the scoped tests you ran, and ask before broadening scope into unrelated fixes or landing despite those failures.
|
||||
- Do not use scoped tests as permission to ignore plausibly related failures.
|
||||
|
||||
## Prompt Cache Stability
|
||||
|
||||
- Treat prompt-cache stability as correctness/perf-critical, not cosmetic.
|
||||
- Any code that assembles model or tool payloads from maps, sets, registries, plugin lists, MCP catalogs, filesystem reads, or network results must make ordering deterministic before building the request.
|
||||
- Do not rewrite older transcript/history bytes on every turn unless you intentionally want to invalidate the cached prefix. Legacy cleanup, pruning, normalization, and migration logic should preserve recent prompt bytes when possible.
|
||||
- If truncation or compaction is required, prefer mutating newest or tail content first so the cached prefix stays byte-identical for as long as possible.
|
||||
- For cache-sensitive changes, require a regression test that proves turn-to-turn prefix stability or deterministic request assembly; helper-local tests alone are not enough.
|
||||
|
||||
## Coding Style & Naming Conventions
|
||||
|
||||
- Language: TypeScript (ESM). Prefer strict typing; avoid `any`.
|
||||
@@ -204,10 +191,10 @@
|
||||
- Test performance guardrail: prefer narrow public SDK subpaths such as `models-provider-runtime`, `skill-commands-runtime`, and `reply-dispatch-runtime` over older broad helper barrels when both expose the needed helper.
|
||||
- Test performance guardrail: treat import-dominated test time as a boundary bug. Refactor the import surface before adding more cases to the slow file.
|
||||
- Agents MUST NOT modify baseline, inventory, ignore, snapshot, or expected-failure files to silence failing checks without explicit approval in this chat.
|
||||
- For targeted/local debugging, use the native root-project entrypoint: `pnpm test <path-or-filter> [vitest args...]` (for example `pnpm test src/commands/onboard-search.test.ts -t "shows registered plugin providers"`); do not default to raw `pnpm vitest run ...` because it bypasses the repo's default config/profile/pool routing.
|
||||
- For targeted/local debugging, keep using the wrapper: `pnpm test -- <path-or-filter> [vitest args...]` (for example `pnpm test -- src/commands/onboard-search.test.ts -t "shows registered plugin providers"`); do not default to raw `pnpm vitest run ...` because it bypasses wrapper config/profile/pool routing.
|
||||
- Do not set test workers above 16; tried already.
|
||||
- Vitest now defaults to native root-project `threads`, with hard `forks` exceptions for `gateway`, `agents`, and `commands`. Keep new pool changes explicit and justified; use `OPENCLAW_VITEST_POOL=forks` for full local fork debugging.
|
||||
- If local Vitest runs cause memory pressure, the default worker budget now derives from host capabilities (CPU, memory band, current load). For a conservative explicit override during land/gate runs, use `OPENCLAW_VITEST_MAX_WORKERS=1 pnpm test`.
|
||||
- Keep Vitest on `forks` only. Do not introduce or reintroduce any non-`forks` Vitest pool or alternate execution mode in configs, wrapper scripts, or default test commands without explicit approval in this chat. This includes `threads`, `vmThreads`, `vmForks`, and any future/nonstandard pool variant.
|
||||
- If local Vitest runs cause memory pressure, the wrapper now derives budgets from host capabilities (CPU, memory band, current load). For a conservative explicit override during land/gate runs, use `OPENCLAW_TEST_PROFILE=serial OPENCLAW_TEST_SERIAL_GATEWAY=1 pnpm test`.
|
||||
- Live tests (real keys): `OPENCLAW_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`.
|
||||
- `pnpm test:live` defaults quiet now. Keep `[live]` progress; suppress profile/gateway chatter. Full logs: `OPENCLAW_LIVE_TEST_QUIET=0 pnpm test:live`.
|
||||
- Full kit + what’s covered: `docs/help/testing.md`.
|
||||
@@ -265,8 +252,6 @@
|
||||
- "Bump version everywhere" means all version locations above **except** `appcast.xml` (only touch appcast when cutting a new macOS Sparkle release).
|
||||
- **Restart apps:** “restart iOS/Android apps” means rebuild (recompile/install) and relaunch, not just kill/launch.
|
||||
- **Device checks:** before testing, verify connected real devices (iOS/Android) before reaching for simulators/emulators.
|
||||
- Mobile pairing: `ws://` (cleartext) is allowed for private LAN addresses (RFC 1918, link-local, mDNS `.local`) and loopback. Private LAN hosts typically lack PKI-backed identity, so requiring TLS there adds complexity without meaningful security gain. `wss://` is required for Tailscale and public endpoints.
|
||||
- Security report scope: reports that treat cleartext `ws://` mobile pairing over private LAN as a vulnerability are out of scope unless they demonstrate a trust-boundary bypass beyond passive network observation on the same LAN.
|
||||
- iOS Team ID lookup: `security find-identity -p codesigning -v` → use Apple Development (…) TEAMID. Fallback: `defaults read com.apple.dt.Xcode IDEProvisioningTeamIdentifiers`.
|
||||
- A2UI bundle hash: `src/canvas-host/a2ui/.bundle.hash` is auto-generated; ignore unexpected changes, and only regenerate via `pnpm canvas:a2ui:bundle` (or `scripts/bundle-a2ui.sh`) when needed. Commit the hash as a separate commit.
|
||||
- Release signing/notary credentials are managed outside the repo; maintainers keep that setup in the private [maintainer release docs](https://github.com/openclaw/maintainers/tree/main/release).
|
||||
|
||||
157
CHANGELOG.md
157
CHANGELOG.md
@@ -4,74 +4,104 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
## Unreleased
|
||||
|
||||
### Breaking
|
||||
|
||||
- Config: remove legacy public config aliases such as `talk.voiceId` / `talk.apiKey`, `agents.*.sandbox.perSession`, `browser.ssrfPolicy.allowPrivateNetwork`, `hooks.internal.handlers`, and channel/group/room `allow` toggles in favor of the canonical public paths and `enabled`, while keeping load-time compatibility and `openclaw doctor --fix` migration support for existing configs. (#60726) Thanks @vincentkoc.
|
||||
|
||||
### Changes
|
||||
|
||||
- Memory/dreaming (experimental): add weighted short-term recall promotion, managed dreaming modes (`off|core|rem|deep`), a `/dreaming` command, Dreams UI, multilingual conceptual tagging, and doctor/status repair support so durable memory promotion can run in the background with less manual setup. (#60569, #60697)
|
||||
- Channels/context visibility: add configurable `contextVisibility` per channel (`all`, `allowlist`, `allowlist_quote`) so supplemental quote, thread, and fetched history context can be filtered by sender allowlists instead of always passing through as received.
|
||||
- Matrix/exec approvals: add Matrix-native exec approval prompts with account-scoped approvers, channel-or-DM delivery, and room-thread aware resolution handling. (#58635) Thanks @gumadeiras.
|
||||
- Providers/StepFun: add the bundled StepFun provider plugin with standard and Step Plan endpoints, China/global onboarding choices, `step-3.5-flash` on both catalogs, and `step-3.5-flash-2603` currently exposed on Step Plan. (#60032) Thanks @hengm3467.
|
||||
- Providers/config: add `models.providers.*.request` overrides for headers and auth on model-provider paths, and full request transport overrides for media provider HTTP paths.
|
||||
- MiniMax/TTS: add a bundled MiniMax speech provider backed by the T2A v2 API so speech synthesis can run through MiniMax-native voices and auth. (#55921) Thanks @duncanita.
|
||||
- Providers/config: add full `models.providers.*.request` transport overrides for model-provider paths, including headers, auth, proxy, and TLS, and keep media provider HTTP request transport overrides aligned with the same request-policy surface. (#60200) Thanks @vincentkoc.
|
||||
- Control UI/skills: add ClawHub search, detail, and install flows directly in the Skills panel. (#60134) Thanks @samzong.
|
||||
- Providers/StepFun: add the bundled StepFun provider plugin with standard and Step Plan endpoints, China/global onboarding choices, `step-3.5-flash` on both catalogs, and `step-3.5-flash-2603` currently exposed on Step Plan. (#60032) Thanks @hengm3467.
|
||||
- Providers/Ollama: add a bundled Ollama Web Search provider for key-free `web_search` via your configured Ollama host and `ollama signin`. (#59318) Thanks @BruceMacD.
|
||||
- Plugins/onboarding: add plugin config TUI prompts to onboard and configure wizards so more plugin setup can stay in the guided flow. (#60590)
|
||||
- Outbound/runtime seams: split delivery, target-resolution, and session/transcript helper loading into narrower runtime seams so outbound hot paths and their owner tests avoid broader setup fan-out. (#60311) Thanks @shakkernerd.
|
||||
- Plugins/browser seams: split browser and WhatsApp plugin-sdk seams into narrower browser, approval-auth, and target-helper facades so hot paths and owner tests avoid broader runtime fan-out. (#60376) Thanks @shakkernerd.
|
||||
- Tests/runtime: trim local unit-test import/runtime fan-out across browser, WhatsApp, cron, task, and reply flows so owner suites start faster with lower shared-worker overhead while preserving the same focused behavior coverage. (#60249) Thanks @shakkernerd.
|
||||
- Tests/secrets runtime: restore split secrets suite cache and env isolation cleanup so broader runs do not leak stale plugin or provider snapshot state. (#60395) Thanks @shakkernerd.
|
||||
- Providers/Ollama: add bundled Ollama Web Search provider for key-free web_search via your configured Ollama host and `ollama signin`. (#59318) Thanks @BruceMacD.
|
||||
- Plugins/install: add `openclaw plugins install --force` to overwrite existing plugin and hook-pack install targets without using the dangerous-code override flag. (#60544) Thanks @gumadeiras.
|
||||
- Providers/Anthropic: remove setup-token from new onboarding and auth-command setup paths, keep existing configured legacy token profiles runnable, and steer new Anthropic setup to Claude CLI or API keys.
|
||||
- Providers/OpenAI Codex: add forward-compat `openai-codex/gpt-5.4-mini` synthesis across provider runtime, model catalog, and model listing so Codex mini works before bundled Pi catalog updates land.
|
||||
- Channels/context visibility: add configurable `contextVisibility` per channel (`all`, `allowlist`, `allowlist_quote`) so quoted, threaded, and fetched history context can be filtered by sender allowlists instead of always passing through as received.
|
||||
- Providers/request overrides: add shared model and media request transport overrides across OpenAI-, Anthropic-, Google-, and compatible provider paths, including headers, auth, proxy, and TLS controls. (#60200)
|
||||
- Matrix/exec approvals: add Matrix-native exec approval prompts with account-scoped approvers, channel-or-DM delivery, and room-thread aware resolution handling. (#58635) Thanks @gumadeiras.
|
||||
- Agents/Claude CLI: expose OpenClaw tools to background Claude CLI runs through a loopback MCP bridge that reuses gateway tool policy, honors session/account/channel scoping, and only advertises the bridge when the local runtime is actually live. (#35676) Thanks @mylukin.
|
||||
- Prompt caching: keep prompt prefixes more reusable across transport fallback, deterministic MCP tool ordering, compaction, and embedded image history so follow-up turns hit cache more reliably. (#58036, #58037, #58038, #59054, #60603, #60691) Thanks @bcherny.
|
||||
- Agents/cache: diagnostics: add prompt-cache break diagnostics, trace live cache scenarios through embedded runner paths, and show cache reuse explicitly in `openclaw status --verbose`. Thanks @vincentkoc.
|
||||
- Agents/cache: stabilize cache-relevant system prompt fingerprints by normalizing equivalent structured prompt whitespace, line endings, hook-added system context, and runtime capability ordering so semantically unchanged prompts reuse KV/cache more reliably. Thanks @vincentkoc.
|
||||
- Providers/transport: add shared proxy/TLS/auth-aware request transport support across model-provider paths, including Anthropic and Google native transport runtimes, so provider request overrides work beyond OpenAI-family traffic.
|
||||
|
||||
### Fixes
|
||||
|
||||
- MiniMax: advertise image input on bundled `MiniMax-M2.7` and `MiniMax-M2.7-highspeed` model definitions so image-capable flows can route through the M2.7 family correctly. (#54843) Thanks @MerlinMiao88888888.
|
||||
- MiniMax/usage: treat `coding_plan` interval and weekly `*_usage_count` fields as remaining quota, not consumed usage, so remaining-percent displays stop inverting in usage surfaces. (#60222) Thanks @YangManBOBO.
|
||||
- Agents/exec approvals: let `exec-approvals.json` agent security override stricter gateway tool defaults so approved subagents can use `security: "full"` without falling back to allowlist enforcement again. (#60310) Thanks @lml2468.
|
||||
- Tasks/maintenance: mark stale cron runs and CLI tasks backed only by long-lived chat sessions as lost again so task cleanup does not keep dead work alive indefinitely. (#60310) Thanks @lml2468.
|
||||
- Providers/OpenAI: preserve native `reasoning.effort: "none"` and strict tool schemas on direct OpenAI-family endpoints, keep compat routes on compat shaping, fix Responses WebSocket warm-up behavior, keep stable session and turn metadata, and fall back more gracefully after early WebSocket failures.
|
||||
- Providers/OpenAI Codex: split native `contextWindow` from runtime `contextTokens`, keep the default effective cap at `272000`, and expose a per-model `contextTokens` override on `models.providers.*.models[]`.
|
||||
- Providers/compat: stop forcing OpenAI-only defaults on proxy and custom OpenAI-compatible routes, preserve native vendor-specific reasoning/tool/streaming behavior across Anthropic-compatible, Moonshot, Mistral, ModelStudio, OpenRouter, xAI, and Z.ai endpoints, and route GitHub Copilot Claude models through Anthropic Messages instead of OpenAI Responses.
|
||||
- Providers/Model Studio: preserve native streaming usage reporting for DashScope-compatible endpoints even when they are configured under a generic provider key, so streamed token totals stop sticking at zero. (#52395) Thanks @IVY-AI-gif.
|
||||
- Plugins/OpenAI: enable `gpt-image-1` reference-image edits through `/images/edits` multipart uploads, and stop inferring unsupported resolution overrides when no explicit `size` or `resolution` is provided.
|
||||
- Gateway/startup: default `gateway.mode` to `local` when unset, detect PID recycling in gateway lock files on Windows and macOS, and show startup progress so healthy restarts stop getting blocked by stale locks. (#54801, #60085, #59843)
|
||||
- Mobile pairing/Android: tighten secure endpoint handling so Tailscale and public remote setup reject cleartext endpoints, private LAN pairing still works, merged-role approvals mint both node and operator device tokens, and bootstrap tokens survive node auto-pair until operator approval finishes. (#60128, #60208, #60221)
|
||||
- Android/Talk Mode: restore spoken assistant replies on node-scoped sessions by keeping reply routing synced to the resolved node session key and pausing mic capture during reply playback. (#60306) Thanks @MKV21.
|
||||
- Telegram: fix current-model checks in the model picker, HTML-format non-default `/model` confirmations, explicit topic replies, persisted reaction ownership across restarts, caption-media placeholder and `file_id` preservation on download failure, and upgraded-install inbound image reads. (#60384, #60042, #59634, #59207, #59948, #59971)
|
||||
- Telegram/local Bot API: honor `channels.telegram.apiRoot` for buffered media downloads, add `channels.telegram.network.dangerouslyAllowPrivateNetwork` for trusted fake-IP setups, and require `channels.telegram.trustedLocalFileRoots` before reading absolute Bot API `file_path` values. (#59544, #60705)
|
||||
- Matrix: recover more reliably when secret storage or recovery keys are missing by recreating secret storage during repair and backup reset, hold crypto snapshot locks during persistence, and surface explicit too-large attachment markers. (#59846, #59851, #60599, #60289)
|
||||
- ACP/agents: inherit the target agent workspace for cross-agent ACP spawns and fall back safely when the inherited workspace no longer exists. (#58438) Thanks @zssggle-rgb.
|
||||
- ACPX/Windows: preserve backslashes and absolute `.exe` paths in Claude CLI parsing, and fail fast on wrapper-script targets with guidance to use `cmd.exe /c`, `powershell.exe -File`, or `node <script>`. (#60689)
|
||||
- Gateway/Windows scheduled tasks: preserve Task Scheduler settings on reinstall, fail loudly when `/Run` does not start, and report fast failed restarts accurately instead of pretending they timed out after 60 seconds. (#59335) Thanks @tmimmanuel.
|
||||
- Discord: keep REST, webhook, and monitor traffic on the configured proxy, preserve component-only media sends, honor `@everyone` and `@here` mention gates, keep ACK reactions on the active account, and split voice connect/playback timeouts so auto-join is more reliable. (#57465, #60361, #60345)
|
||||
- WhatsApp: restore `channels.whatsapp.blockStreaming` and reset watchdog timeouts after reconnect so quiet chats stop falling into reconnect loops. (#60007, #60069)
|
||||
- Control UI: keep Stop visible during tool-only execution, preserve pending-send busy state, and clear stale ClawHub search results as soon as the query changes. (#54528, #59800, #60267)
|
||||
- MS Teams: download inline DM images via Graph API and preserve channel reply threading in proactive fallback. (#52212, #55198)
|
||||
- Auth/failover: persist selected fallback overrides before retrying, shorten `auth_permanent` lockouts, and refresh websocket/shared-auth sessions only when real auth changes occur so retries and secret rotations behave predictably. (#60404, #60323, #60387)
|
||||
- Cron: replay interrupted recurring jobs on the first gateway restart instead of waiting for a second restart. (#60583) Thanks @joelnishanth.
|
||||
- Plugins/media understanding: enable bundled Groq and Deepgram providers by default so configured transcription models work without extra plugin activation config. (#59982) Thanks @yxjsxy.
|
||||
- Plugins/Kimi Coding: parse tagged tool calls and keep Anthropic-native tool payloads so Kimi coding endpoints execute tools instead of echoing raw markup. (#60051, #60391)
|
||||
- Tools/web_search (Kimi): when `tools.web.search.kimi.baseUrl` is unset, inherit native Moonshot chat `baseUrl` (`.ai` / `.cn`) so China console keys authenticate on the same host as chat. Fixes #44851. (#56769) Thanks @tonga54.
|
||||
- Plugins/marketplace: block remote marketplace symlink escapes without breaking ordinary local marketplace install paths. (#60556) Thanks @eleqtrizit.
|
||||
- Plugins/install: preserve unsafe override flags across linked plugin and hook-pack probes so local `--link` installs honor the documented override behavior. (#60624) Thanks @JerrettDavis.
|
||||
- Config/All Settings: keep the raw config view intact when sensitive fields are blank instead of corrupting or dropping the rendered snapshot. (#28214) Thanks @solodmd.
|
||||
- Security: preserve restrictive plugin-only tool allowlists, require owner access for `/allowlist add` and `/allowlist remove`, fail closed when `before_tool_call` hooks crash, block browser SSRF redirect bypasses earlier, and keep non-interactive auth-choice inference scoped to bundled and already-trusted plugins. (#58476, #59836, #59822, #58771, #59120)
|
||||
- Exec approvals: reuse durable exact-command `allow-always` approvals in allowlist mode so identical reruns stop prompting, and tighten Windows interpreter/path approval handling so wrapper and malformed-path cases fail closed more consistently. (#59880, #59780, #58040, #59182)
|
||||
- Agents/runtime: make default subagent allowlists, inherited skills/workspaces, and duplicate session-id resolution behave more predictably, and include value-shape hints in missing-parameter tool errors. (#59944, #59992, #59858, #55317)
|
||||
- Update/npm: prefer the npm binary that owns the installed global OpenClaw prefix so mixed Homebrew-plus-nvm setups update the right install. (#60153) Thanks @jayeshp19.
|
||||
- Gateway/plugin routes: keep gateway-auth plugin runtime routes on write-only fallback scopes unless a trusted-proxy caller explicitly declares narrower `x-openclaw-scopes`, so plugin HTTP handlers no longer mint admin-level runtime scopes on missing or untrusted HTTP scope headers. (#59815) Thanks @pgondhi987.
|
||||
- Agents/exec approvals: let `exec-approvals.json` agent security override stricter gateway tool defaults so approved subagents can use `security: "full"` without falling back to allowlist enforcement again. (#60310) Thanks @lml2468.
|
||||
- Tasks/maintenance: reconcile stale cron and chat-backed CLI task rows against live cron-job and agent-run ownership instead of treating any persisted session key as proof that the task is still running. (#60310) Thanks @lml2468.
|
||||
- Providers/GitHub Copilot: send IDE identity headers on runtime model requests and GitHub token exchange so IDE-authenticated Copilot runs stop failing with missing `Editor-Version`. (#60641) Thanks @VACInc and @vincentkoc.
|
||||
- Prompt caching: route Codex Responses and Anthropic Vertex through boundary-aware cache shaping, and report the actual outbound system prompt in cache traces so cache reuse and misses line up with what providers really receive. Thanks @vincentkoc.
|
||||
- Skills/uv install: block workspace `.env` from overriding `UV_PYTHON` and strip related interpreter override keys from uv skill-install subprocesses so repository-controlled env files cannot steer the selected Python runtime. (#59178) Thanks @pgondhi987.
|
||||
- Telegram/reactions: preserve `reactionNotifications: "own"` across gateway restarts by persisting sent-message ownership state instead of treating cold cache as a permissive fallback. (#59207) Thanks @samzong.
|
||||
- Gateway/startup: detect PID recycling in gateway lock files on Windows and macOS, and add startup progress so stale lock conflicts no longer block healthy restarts. (#59843) Thanks @TonyDerek-dot.
|
||||
- MS Teams/DM media: download inline images in 1:1 chats via Graph API so Teams DM image attachments stop failing to load. (#52212) Thanks @Ted-developer.
|
||||
- MS Teams/threading: preserve channel reply threading in proactive fallback so replies stay in the original thread instead of dropping into the channel root. (#55198) Thanks @hyojin.
|
||||
- Telegram/media: preserve `<media:...>` placeholders and `file_id` in captioned messages when Bot API downloads fail, so agents still receive media context. (#59948) Thanks @v1p0r.
|
||||
- Telegram/media: keep inbound image attachments readable on upgraded installs where legacy state roots still differ from the managed config-dir media cache. (#59971) Thanks @neeravmakwana.
|
||||
- Telegram/local Bot API: thread `channels.telegram.apiRoot` through buffered reply-media and album downloads so self-hosted Bot API file paths stop falling back to `api.telegram.org` and 404ing. (#59544) Thanks @SARAMALI15792.
|
||||
- Telegram/replies: preserve explicit topic targets when `replyTo` is present while still inheriting the current topic for same-chat replies without an explicit topic. (#59634) Thanks @dashhuang.
|
||||
- Telegram/native commands: clean up metadata-driven progress placeholders when replies fall back, edits fail, or local exec approval prompts are suppressed. (#59300) Thanks @jalehman.
|
||||
- Telegram/models: compare full provider/model refs in the Telegram picker so same-id models from other providers no longer show the wrong current-model checkmark. (#60384) Thanks @sfuminya.
|
||||
- Media/request overrides: resolve shared and capability-filtered media request SecretRefs correctly and expose media transport override fields to schema-driven config consumers. (#59848) Thanks @vincentkoc.
|
||||
- Providers/request overrides: stop advertising unsupported proxy and TLS transport settings on `models.providers.*.request`, and fail closed if unvalidated config tries to route LLM model-provider traffic through dead transport fields. (#59682) Thanks @vincentkoc.
|
||||
- Discord/mentions: treat `@everyone` and `@here` as valid mention-gate triggers in guild preflight so mention-required bots still respond to those broadcasts. (#60343) Thanks @geekhuashan.
|
||||
- Matrix: allow secret-storage recreation during automatic repair bootstrap so clients that lose their recovery key can recover and persist new cross-signing keys. (#59846) Thanks @al3mart.
|
||||
- Matrix/crypto persistence: capture and write the IndexedDB snapshot while holding the snapshot file lock so concurrent gateway and CLI persists cannot overwrite newer crypto state. (#59851) Thanks @al3mart.
|
||||
- Ollama/auth: prefer real cloud auth over local marker during model auth resolution so cloud-backed Ollama auth does not get shadowed by stale local-only markers.
|
||||
- Plugins/Kimi Coding: parse tagged Kimi tool-call text into structured tool calls on the provider stream path so tools execute instead of echoing raw markup. (#60051) Thanks @obviyus.
|
||||
- Channels/passive hooks: emit passive message hooks for mention-skipped Telegram and Signal group messages when `ingest` is enabled, including wildcard/default fallback and per-group override handling. (#60018) Thanks @obviyus.
|
||||
- Providers/compat: stop forcing OpenAI-only payload defaults on proxy and custom OpenAI-compatible routes, and preserve native vendor-specific reasoning, tool, and streaming behavior for Anthropic-compatible, Moonshot, Mistral, ModelStudio, OpenRouter, xAI, Z.ai, and other routed provider paths.
|
||||
- Plugins/manifest registry: stop warning when an explicit manifest `id` intentionally differs from the discovery hint. (#59185) Thanks @samzong.
|
||||
- WhatsApp/streaming: honor `channels.whatsapp.blockStreaming` again for inbound auto-replies so progressive block replies can be enabled explicitly instead of being forced to final-only delivery. Thanks @mcaxtr.
|
||||
- Auth/failover: shorten `auth_permanent` lockouts, add dedicated config knobs for permanent-auth backoff, and downgrade ambiguous auth-ish upstream incidents to retryable auth failures so providers recover automatically after transient outages. (#60404) Thanks @extrasmall0.
|
||||
- Providers/GitHub Copilot: route Claude models through Anthropic Messages with Copilot-compatible headers and Anthropic prompt-cache markers instead of forcing the OpenAI Responses transport.
|
||||
- Plugins/runtime: reuse compatible active registries for `web_search` and `web_fetch` provider snapshot resolution so repeated runtime reads do not re-import the same bundled plugin set on each agent message. Related #48380.
|
||||
- Infra/tailscale: ignore `OPENCLAW_TEST_TAILSCALE_BINARY` outside explicit test environments and block it from workspace `.env`, so test-only binary overrides cannot be injected through trusted repository state. (#58468) Thanks @eleqtrizit.
|
||||
- Plugins/OpenAI: enable reference-image edits for `gpt-image-1` by routing edit calls to `/images/edits` with multipart image uploads, and update image-generation capability/docs metadata accordingly. Thanks @steipete.
|
||||
- Cache/context guard: compact newest tool results first so the cached prompt prefix stays byte-identical and avoids full re-tokenization every turn past the 75% context threshold. (#58036) Thanks @bcherny.
|
||||
- Agents/tools: include value-shape hints in missing-parameter tool errors so dropped, empty-string, and wrong-type write payloads are easier to diagnose from logs. (#55317) Thanks @priyansh19.
|
||||
- Android/assistant: keep queued App Actions prompts pending when auto-send enqueue is rejected, so transient chat-health drops do not silently lose the assistant request. Thanks @obviyus.
|
||||
- Plugins/startup: migrate legacy `tools.web.search.<provider>` config before strict startup validation, and record plugin failure phase/timestamp so degraded plugin startup is easier to diagnose from logs and `plugins list`.
|
||||
- Plugins/Google: separate OAuth CSRF state from PKCE code verifier during Gemini browser sign-in so state validation and token exchange use independent values. (#59116) Thanks @eleqtrizit.
|
||||
- Agents/subagents: honor `agents.defaults.subagents.allowAgents` for `sessions_spawn` and `agents_list`, so default cross-agent allowlists work without duplicating per-agent config. (#59944) Thanks @hclsys.
|
||||
- Agents/tools: normalize only truly empty MCP tool schemas to `{ type: "object", properties: {} }` so OpenAI accepts parameter-free tools without rewriting unrelated conditional schemas. (#60176) Thanks @Bartok9.
|
||||
- Update/npm: prefer the npm binary that owns the installed global OpenClaw prefix during package self-update, so mixed Homebrew-plus-nvm setups update the right install. (#60153) Thanks @jayeshp19.
|
||||
- Plugins/browser: block SSRF redirect bypass by installing a real-time Playwright route handler before `page.goto()` so navigation to private/internal IPs is intercepted and aborted mid-redirect instead of checked post-hoc. (#58771) Thanks @pgondhi987.
|
||||
- Android/gateway: require TLS for non-loopback remote gateway endpoints while still allowing local loopback and emulator cleartext setup flows. (#58475) Thanks @eleqtrizit.
|
||||
- Exec/Windows: hide transient console windows for `runExec` and `runCommandWithTimeout` child-process launches, matching other Windows exec paths and stopping visible shell flashes during tool runs. (#59466) Thanks @lawrence3699.
|
||||
- Zalo/webhook: scope replay-dedupe cache key to path and account using `JSON.stringify` so multi-account deployments do not silently drop events due to cross-account cache poisoning. (#59387) Thanks @pgondhi987.
|
||||
- Exec/Windows: reject malformed drive-less rooted executable paths like `:\Users\...` so approval and allowlist candidate resolution no longer treat them as cwd-relative commands. (#58040) Thanks @SnowSky1.
|
||||
- Exec/preflight: fail closed on complex interpreter invocations that would otherwise skip script-content validation, and correctly inspect quoted script paths before host execution. Thanks @pgondhi987.
|
||||
- Exec/Windows: include Windows-compatible env override keys like `ProgramFiles(x86)` in system-run approval binding so changed approved values are rejected instead of silently passing unbound. (#59182) Thanks @pgondhi987.
|
||||
- ACP/Windows spawn: fail closed on unresolved `.cmd` and `.bat` OpenClaw wrappers unless a caller explicitly opts into shell fallback, so Windows ACP launches do not silently drop into shell-mediated execution when wrapper unwrapping fails. (#58436) Thanks @eleqtrizit.
|
||||
- Exec/Windows: prefer strict-inline-eval denial over generic allowlist prompts for interpreter carriers, while keeping persisted Windows allow-always approvals argv-bound. (#59780) Thanks @luoyanglang.
|
||||
- Gateway/connect: omit admin-scoped config and auth metadata from lower-privilege `hello-ok` snapshots while preserving those fields for admin reconnects. (#58469) Thanks @eleqtrizit.
|
||||
- iOS/canvas: restrict A2UI bridge trust to the bundled scaffold and exact capability-backed remote canvas URLs, so generic `canvas.navigate` and `canvas.present` loads no longer gain action-dispatch authority. (#58471) Thanks @eleqtrizit.
|
||||
- Agents/tool policy: preserve restrictive plugin-only allowlists instead of silently widening access to core tools, and keep allowlist warnings aligned with the enforced policy. (#58476) Thanks @eleqtrizit.
|
||||
- Hooks/session_end: preserve deterministic reason metadata for custom reset aliases and overlapping idle-plus-daily rollovers so plugins can rely on lifecycle reason reporting. (#59715) Thanks @jalehman.
|
||||
- Tools/image generation: stop inferring unsupported resolution overrides for OpenAI reference-image edits when no explicit `size` or `resolution` is provided, so default edit flows no longer fail before the provider request is sent.
|
||||
- Agents/sessions: release embedded runner session locks even when teardown cleanup throws, so timed-out or failed cleanup paths no longer leave sessions wedged until the stale-lock watchdog recovers them. (#59194) Thanks @samzong.
|
||||
- Slack/app manifest: add the missing `groups:read` scope to the onboarding and example Slack app manifest so apps copied from the OpenClaw templates can resolve private group conversations reliably.
|
||||
- Mobile pairing/Android: stop generating Tailscale and public mobile setup codes that point at unusable cleartext remote gateways, keep private LAN pairing allowed, and make Android reject insecure remote endpoints with clearer guidance while mixed bootstrap approvals honor operator scopes correctly. (#60128) Thanks @obviyus.
|
||||
- Telegram/media: add `channels.telegram.network.dangerouslyAllowPrivateNetwork` for trusted fake-IP or transparent-proxy environments where Telegram media downloads resolve `api.telegram.org` to private/internal/special-use addresses.
|
||||
- Discord/proxy: keep Carbon REST, monitor startup, and webhook sends on the configured Discord proxy while falling back cleanly when the proxy URL is invalid, so Discord replies and deploys do not hard-fail on malformed proxy config. (#57465) Thanks @geekhuashan.
|
||||
- Discord/components: keep modal-trigger and spoiler-file component messages on the component path when sending media, so classic-message fallback does not silently drop component-only behavior. (#60361) Thanks @geekhuashan.
|
||||
- Mobile pairing/device approval: mint both node and operator device tokens when one approval grants merged roles, so mixed mobile bootstrap pairings stop reconnecting as operator-only and showing the node offline. (#60208) Thanks @obviyus.
|
||||
- Agents/tool policy: stop `tools.profile` warnings from flagging runtime-gated baseline core tools as unknown when the coding profile is missing tools like `code_execution`, `x_search`, `image`, or `image_generate`, while still warning on explicit extra allowlist entries. Thanks @vincentkoc.
|
||||
- Sessions/resolution: collapse alias-duplicate session-id matches before scoring, keep distinct structural ties ambiguous, and prefer current-store reuse when resolving equal cross-store duplicates so follow-up turns stop dropping or duplicating sessions on timestamp ties.
|
||||
- Mobile pairing/bootstrap: keep setup bootstrap tokens alive through the initial node auto-pair so the same QR bootstrap token can finish operator approval, then revoke it after the full issued profile connects successfully. (#60221) Thanks @obviyus.
|
||||
- Plugins/allowlists: let explicit bundled chat channel enablement bypass `plugins.allow`, while keeping auto-enabled channel activation and startup sidecars behind restrictive allowlists. (#60233) Thanks @dorukardahan.
|
||||
- Allowlist/commands: require owner access for `/allowlist add` and `/allowlist remove` so command-authorized non-owners cannot mutate persisted allowlists. (#59836) Thanks @eleqtrizit.
|
||||
- Control UI/skills: clear stale ClawHub results immediately when the search query changes, so debounced searches cannot keep outdated install targets visible. Related #60134.
|
||||
- Fetch/redirects: normalize guarded redirect method rewriting and loop detection so SSRF-guarded requests match platform redirect behavior without missing loops back to the original URL. (#59121) Thanks @eleqtrizit.
|
||||
- Discord/ack reactions: keep automatic ACK reaction auth on the active hydrated Discord account so SecretRef-backed and non-default-account reactions stop falling back to stale default config resolution. (#60081) Thanks @FunJim.
|
||||
- Telegram/model switching: render non-default `/model` callback confirmations with HTML formatting so Telegram shows the selected model in bold instead of raw `**...**` markers. (#60042) Thanks @GitZhangChi.
|
||||
- Plugins/update: allow `openclaw plugins update` to use `--dangerously-force-unsafe-install` for built-in dangerous-code false positives during plugin updates. (#60066) Thanks @huntharo.
|
||||
- Gateway/auth: disconnect shared-auth websocket sessions only for effective auth rotations on restart-capable config writes, and keep `config.set` auth edits from dropping still-valid live sessions. (#60387) Thanks @mappel-nv.
|
||||
- Control UI/chat: keep the Stop button visible during tool-only execution so abortable runs do not fall back to Send while tools are still running. (#54528) thanks @chziyue.
|
||||
- Discord/voice: make READY auto-join fire-and-forget while keeping the shorter initial voice-connect timeout separate from the longer playback-start wait. (#60345) Thanks @geekhuashan.
|
||||
- Agents/skills: add inherited `agents.defaults.skills` allowlists, make per-agent `agents.list[].skills` replace defaults instead of merging, and scope embedded, session, sandbox, and cron skill snapshots through the effective runtime agent. (#59992) Thanks @gumadeiras.
|
||||
- Matrix/Telegram exec approvals: recover stored same-channel account bindings even when session reply state drifted to another channel, so foreign-channel approvals route to the bound account instead of fanning out or being rejected as ambiguous. (#60417) thanks @gumadeiras.
|
||||
- Slack/app manifest: set `bot_user.always_online` to `true` in the onboarding and example Slack app manifest so the Slack app appears ready to respond.
|
||||
- Gateway/websocket auth: refresh auth on new websocket connects after secrets reload so rotated gateway tokens take effect immediately without requiring a restart. (#60323) Thanks @mappel-nv.
|
||||
- Onboarding/plugins: keep non-interactive auth-choice inference scoped to bundled and already-trusted plugins so untrusted workspace manifests cannot hijack built-in provider API-key flows. (#59120) Thanks @eleqtrizit.
|
||||
- Agents/workspace: respect `agents.defaults.workspace` for non-default agents by resolving them under the configured base path instead of falling back to `workspace-<id>`. (#59858) Thanks @joelnishanth.
|
||||
- Config/All Settings: keep the raw config view intact when sensitive fields are blank instead of corrupting or dropping the snapshot during redaction. (#28214) thanks @solodmd.
|
||||
- Plugins/runtime: honor explicit capability allowlists during fallback speech, media-understanding, and image-generation provider loading so bundled capability plugins do not bypass restrictive `plugins.allow` config. (#52262) Thanks @PerfectPan.
|
||||
- Hooks/tool policy: block tool calls when a `before_tool_call` hook crashes so hook failures fail closed instead of silently allowing execution. (#59822) Thanks @pgondhi987.
|
||||
- Matrix/media: surface a dedicated `[matrix <kind> attachment too large]` marker for oversized inbound media instead of the generic unavailable marker, and classify size-limit failures with a typed Matrix error. (#60289) Thanks @efe-arv.
|
||||
- WhatsApp/watchdog: reset watchdog timeout after reconnect so quiet channels no longer enter a tight reconnect loop from stale message timestamps carried across connection runs. (#60007) Thanks @MonkeyLeeT.
|
||||
- Agents/fallback: persist selected fallback overrides before retry attempts start, prefer persisted overrides during live-session reconciliation, and keep provider-scoped auth-profile failover from snapping retries back to stale primary selections.
|
||||
|
||||
## 2026.4.2
|
||||
|
||||
@@ -102,7 +132,6 @@ Docs: https://docs.openclaw.ai
|
||||
### Fixes
|
||||
|
||||
- Sandbox/security: block credential-path binds even when sandbox home paths resolve through canonical aliases, so agent containers cannot mount user secret stores through alternate home-directory paths. (#59157) Thanks @eleqtrizit.
|
||||
- Gateway/Windows scheduled tasks: preserve Task Scheduler settings on reinstall, fail loud when Scheduled Task `/Run` does not start, and report fast failed restarts with the actual elapsed time instead of a fake 60s timeout. (#59335) Thanks @tmimmanuel.
|
||||
|
||||
## 2026.4.1-beta.1
|
||||
|
||||
@@ -158,7 +187,6 @@ Docs: https://docs.openclaw.ai
|
||||
- Exec/node hosts: stop forwarding the gateway workspace cwd to remote node exec when no workdir was explicitly requested, so cross-platform node approvals fall back to the node default cwd instead of failing with `SYSTEM_RUN_DENIED`. (#58977) Thanks @Starhappysh.
|
||||
- TUI/chat: keep pending local sends visible and reconciled across history reloads, make busy/error recovery clearer through fallback and terminal-error paths, and reclaim transcript width for long links and paths. (#59800) Thanks @vincentkoc.
|
||||
- Exec approvals/channels: decouple initiating-surface approval availability from native delivery enablement so Telegram, Slack, and Discord still expose approvals when approvers exist and native target routing is configured separately. (#59776) Thanks @joelnishanth.
|
||||
- Agents/logging: keep orphaned-user transcript repair warnings focused on interactive runs, and downgrade background-trigger repairs (`heartbeat`, `cron`, `memory`, `overflow`) to debug logs to reduce false-alarm gateway noise.
|
||||
|
||||
## 2026.4.1
|
||||
|
||||
@@ -188,12 +216,6 @@ Docs: https://docs.openclaw.ai
|
||||
- Channels/WhatsApp: pass inbound message timestamp to model context so the AI can see when WhatsApp messages were sent. (#58590) Thanks @Maninae
|
||||
- QQBot/voice: lazy-load `silk-wasm` in `audio-convert.ts` so qqbot still starts when the optional voice dependency is missing, while voice encode/decode degrades gracefully instead of crashing at module load time. (#58829) Thanks @WideLee.
|
||||
- WhatsApp/groups: fix bot waking up on self-number quoted replies in groups with `selfChatMode` enabled. (#60148) Thanks @lurebat
|
||||
- Device pairing: require `operator.pairing` or `operator.admin` for internal `/pair` setup-code, QR, and cleanup commands so lower-privilege gateway callers cannot mint or revoke pairing bootstrap material. (#60491) Thanks @eleqtrizit.
|
||||
- Agents/failover: unify structured and raw provider error classification so provider-specific `400`/`422` payloads no longer get forced into generic format failures before retry, billing, or compaction logic can inspect them. (#58856) Thanks @aaron-he-zhu.
|
||||
- Auth profiles/store: coerce misplaced SecretRef objects out of plaintext `key` and `token` fields during store load so agents without ACP runtime stop crashing on `.trim()` after upgrade. (#58923) Thanks @openperf.
|
||||
- ACPX/runtime: repair `queue owner unavailable` session recovery by replacing dead named sessions and resuming the backend session when ACPX exposes a stable session id, so the first ACP prompt no longer inherits a dead handle. (#58669) Thanks @neeravmakwana
|
||||
- ACPX/runtime: retry dead-session queue-owner repair without `--resume-session` when the reported ACPX session id is stale, so recovery still creates a fresh named session instead of failing session init. Thanks @obviyus.
|
||||
- Tools/web_search (Kimi): replay native Moonshot `$web_search` arguments verbatim, disable thinking for `kimi-k2.5`, and add Moonshot region/model setup prompts so bundled Kimi web search works again. (#59356) Thanks @Innocent-children.
|
||||
|
||||
## 2026.3.31
|
||||
|
||||
@@ -567,9 +589,6 @@ Docs: https://docs.openclaw.ai
|
||||
- Plugins/Matrix: encrypt E2EE image thumbnails with `thumbnail_file` while keeping unencrypted-room previews on `thumbnail_url`, so encrypted Matrix image events keep thumbnail metadata without leaking plaintext previews. (#54711) thanks @frischeDaten.
|
||||
- Telegram/forum topics: keep native `/new` and `/reset` routed to the active topic by preserving the topic target on forum-thread command context. (#35963)
|
||||
- Status/port diagnostics: treat single-process dual-stack loopback gateway listeners as healthy in `openclaw status --all`, suppressing false "port already in use" conflict warnings. (#53398) Thanks @DanWebb1949.
|
||||
- CLI/Docker: treat loopback private-host CLI gateway connects as local for silent pairing auto-approval, while keeping remote backend and public-host CLI connects behind pairing. (#55113) Thanks @sar618.
|
||||
|
||||
## 2026.3.24
|
||||
|
||||
### Breaking
|
||||
|
||||
|
||||
18
README.md
18
README.md
@@ -34,7 +34,7 @@ New install? Start here: [Getting started](https://docs.openclaw.ai/start/gettin
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center" width="16.66%">
|
||||
<td align="center" width="20%">
|
||||
<a href="https://openai.com/">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/openclaw/openclaw/main/docs/assets/sponsors/openai-light.svg">
|
||||
@@ -42,15 +42,7 @@ New install? Start here: [Getting started](https://docs.openclaw.ai/start/gettin
|
||||
</picture>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" width="16.66%">
|
||||
<a href="https://github.com/">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/openclaw/openclaw/main/docs/assets/sponsors/github-light.svg">
|
||||
<img src="https://raw.githubusercontent.com/openclaw/openclaw/main/docs/assets/sponsors/github.svg" alt="GitHub" height="28">
|
||||
</picture>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" width="16.66%">
|
||||
<td align="center" width="20%">
|
||||
<a href="https://www.nvidia.com/">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/openclaw/openclaw/main/docs/assets/sponsors/nvidia.svg">
|
||||
@@ -58,7 +50,7 @@ New install? Start here: [Getting started](https://docs.openclaw.ai/start/gettin
|
||||
</picture>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" width="16.66%">
|
||||
<td align="center" width="20%">
|
||||
<a href="https://vercel.com/">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/openclaw/openclaw/main/docs/assets/sponsors/vercel-light.svg">
|
||||
@@ -66,7 +58,7 @@ New install? Start here: [Getting started](https://docs.openclaw.ai/start/gettin
|
||||
</picture>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" width="16.66%">
|
||||
<td align="center" width="20%">
|
||||
<a href="https://blacksmith.sh/">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/openclaw/openclaw/main/docs/assets/sponsors/blacksmith-light.svg">
|
||||
@@ -74,7 +66,7 @@ New install? Start here: [Getting started](https://docs.openclaw.ai/start/gettin
|
||||
</picture>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" width="16.66%">
|
||||
<td align="center" width="20%">
|
||||
<a href="https://www.convex.dev/">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/openclaw/openclaw/main/docs/assets/sponsors/convex-light.svg">
|
||||
|
||||
@@ -65,8 +65,8 @@ android {
|
||||
applicationId = "ai.openclaw.app"
|
||||
minSdk = 31
|
||||
targetSdk = 36
|
||||
versionCode = 2026040401
|
||||
versionName = "2026.4.4"
|
||||
versionCode = 2026040301
|
||||
versionName = "2026.4.3"
|
||||
ndk {
|
||||
// Support all major ABIs — native libs are tiny (~47 KB per ABI)
|
||||
abiFilters += listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
|
||||
|
||||
@@ -71,7 +71,6 @@ class NodeRuntime(
|
||||
|
||||
private val identityStore = DeviceIdentityStore(appContext)
|
||||
private var connectedEndpoint: GatewayEndpoint? = null
|
||||
private var activeGatewayAuth: GatewayConnectAuth? = null
|
||||
|
||||
private val cameraHandler: CameraHandler = CameraHandler(
|
||||
appContext = appContext,
|
||||
@@ -300,11 +299,6 @@ class NodeRuntime(
|
||||
_canvasRehydrateErrorText.value = null
|
||||
updateStatus()
|
||||
showLocalCanvasOnConnect()
|
||||
val endpoint = connectedEndpoint
|
||||
val auth = activeGatewayAuth
|
||||
if (endpoint != null && auth != null) {
|
||||
maybeStartOperatorSessionAfterNodeConnect(endpoint, auth)
|
||||
}
|
||||
},
|
||||
onDisconnected = { message ->
|
||||
_nodeConnected.value = false
|
||||
@@ -351,8 +345,6 @@ class NodeRuntime(
|
||||
session = operatorSession,
|
||||
supportsChatSubscribe = false,
|
||||
isConnected = { operatorConnected },
|
||||
onBeforeSpeak = { micCapture.pauseForTts() },
|
||||
onAfterSpeak = { micCapture.resumeAfterTts() },
|
||||
).also { speaker ->
|
||||
speaker.setPlaybackEnabled(prefs.speakerEnabled.value)
|
||||
}
|
||||
@@ -424,19 +416,14 @@ class NodeRuntime(
|
||||
session = operatorSession,
|
||||
supportsChatSubscribe = true,
|
||||
isConnected = { operatorConnected },
|
||||
onBeforeSpeak = { micCapture.pauseForTts() },
|
||||
onAfterSpeak = { micCapture.resumeAfterTts() },
|
||||
)
|
||||
}
|
||||
|
||||
private fun syncMainSessionKey(agentId: String?) {
|
||||
val resolvedKey = resolveNodeMainSessionKey(agentId)
|
||||
// Always push the resolved session key into TalkMode, even when the
|
||||
// state flow value is unchanged, so lazy TalkMode instances do not
|
||||
// stay on the default "main" session key.
|
||||
talkMode.setMainSessionKey(resolvedKey)
|
||||
if (_mainSessionKey.value == resolvedKey) return
|
||||
_mainSessionKey.value = resolvedKey
|
||||
talkMode.setMainSessionKey(resolvedKey)
|
||||
chat.applyMainSessionKey(resolvedKey)
|
||||
updateHomeCanvasState()
|
||||
}
|
||||
@@ -806,14 +793,15 @@ class NodeRuntime(
|
||||
auth: GatewayConnectAuth,
|
||||
reconnect: Boolean = false,
|
||||
) {
|
||||
activeGatewayAuth = auth
|
||||
val tls = connectionManager.resolveTlsParams(endpoint)
|
||||
val operatorAuth =
|
||||
resolveOperatorSessionConnectAuth(
|
||||
auth = auth,
|
||||
storedOperatorToken = loadStoredRoleDeviceToken("operator"),
|
||||
val connectOperator =
|
||||
shouldConnectOperatorSession(
|
||||
auth.token,
|
||||
auth.bootstrapToken,
|
||||
auth.password,
|
||||
loadStoredRoleDeviceToken("operator"),
|
||||
)
|
||||
if (operatorAuth == null) {
|
||||
if (!connectOperator) {
|
||||
operatorConnected = false
|
||||
operatorStatusText = "Offline"
|
||||
operatorSession.disconnect()
|
||||
@@ -821,9 +809,9 @@ class NodeRuntime(
|
||||
} else {
|
||||
operatorSession.connect(
|
||||
endpoint,
|
||||
operatorAuth.token,
|
||||
operatorAuth.bootstrapToken,
|
||||
operatorAuth.password,
|
||||
auth.token,
|
||||
auth.bootstrapToken,
|
||||
auth.password,
|
||||
connectionManager.buildOperatorConnectOptions(),
|
||||
tls,
|
||||
)
|
||||
@@ -836,7 +824,7 @@ class NodeRuntime(
|
||||
connectionManager.buildNodeConnectOptions(),
|
||||
tls,
|
||||
)
|
||||
if (reconnect && operatorAuth != null) {
|
||||
if (reconnect && connectOperator) {
|
||||
operatorSession.reconnect()
|
||||
}
|
||||
if (reconnect) {
|
||||
@@ -934,33 +922,8 @@ class NodeRuntime(
|
||||
return deviceAuthStore.loadToken(deviceId, role)
|
||||
}
|
||||
|
||||
private fun maybeStartOperatorSessionAfterNodeConnect(
|
||||
endpoint: GatewayEndpoint,
|
||||
auth: GatewayConnectAuth,
|
||||
) {
|
||||
if (operatorConnected || operatorStatusText == "Connecting…") {
|
||||
return
|
||||
}
|
||||
val operatorAuth =
|
||||
resolveOperatorSessionConnectAuth(
|
||||
auth = auth,
|
||||
storedOperatorToken = loadStoredRoleDeviceToken("operator"),
|
||||
) ?: return
|
||||
operatorStatusText = "Connecting…"
|
||||
updateStatus()
|
||||
operatorSession.connect(
|
||||
endpoint,
|
||||
operatorAuth.token,
|
||||
operatorAuth.bootstrapToken,
|
||||
operatorAuth.password,
|
||||
connectionManager.buildOperatorConnectOptions(),
|
||||
connectionManager.resolveTlsParams(endpoint),
|
||||
)
|
||||
}
|
||||
|
||||
fun disconnect() {
|
||||
connectedEndpoint = null
|
||||
activeGatewayAuth = null
|
||||
_pendingGatewayTrust.value = null
|
||||
operatorSession.disconnect()
|
||||
nodeSession.disconnect()
|
||||
@@ -1296,47 +1259,18 @@ class NodeRuntime(
|
||||
|
||||
}
|
||||
|
||||
internal fun resolveOperatorSessionConnectAuth(
|
||||
auth: NodeRuntime.GatewayConnectAuth,
|
||||
storedOperatorToken: String?,
|
||||
): NodeRuntime.GatewayConnectAuth? {
|
||||
val explicitToken = auth.token?.trim()?.takeIf { it.isNotEmpty() }
|
||||
if (explicitToken != null) {
|
||||
return NodeRuntime.GatewayConnectAuth(
|
||||
token = explicitToken,
|
||||
bootstrapToken = null,
|
||||
password = null,
|
||||
)
|
||||
}
|
||||
|
||||
val explicitPassword = auth.password?.trim()?.takeIf { it.isNotEmpty() }
|
||||
if (explicitPassword != null) {
|
||||
return NodeRuntime.GatewayConnectAuth(
|
||||
token = null,
|
||||
bootstrapToken = null,
|
||||
password = explicitPassword,
|
||||
)
|
||||
}
|
||||
|
||||
val storedToken = storedOperatorToken?.trim()?.takeIf { it.isNotEmpty() }
|
||||
if (storedToken != null) {
|
||||
// Bootstrap can seed the operator token, but operator should reconnect
|
||||
// through the stored device-token path rather than bootstrap auth itself.
|
||||
return NodeRuntime.GatewayConnectAuth(
|
||||
token = null,
|
||||
bootstrapToken = null,
|
||||
password = null,
|
||||
)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
internal fun shouldConnectOperatorSession(
|
||||
auth: NodeRuntime.GatewayConnectAuth,
|
||||
token: String?,
|
||||
bootstrapToken: String?,
|
||||
password: String?,
|
||||
storedOperatorToken: String?,
|
||||
): Boolean {
|
||||
return resolveOperatorSessionConnectAuth(auth, storedOperatorToken) != null
|
||||
return (
|
||||
!token.isNullOrBlank() ||
|
||||
!bootstrapToken.isNullOrBlank() ||
|
||||
!password.isNullOrBlank() ||
|
||||
!storedOperatorToken.isNullOrBlank()
|
||||
)
|
||||
}
|
||||
|
||||
private enum class HomeCanvasGatewayState {
|
||||
|
||||
@@ -1,92 +1,32 @@
|
||||
package ai.openclaw.app.gateway
|
||||
|
||||
import ai.openclaw.app.SecurePrefs
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
data class DeviceAuthEntry(
|
||||
val token: String,
|
||||
val role: String,
|
||||
val scopes: List<String>,
|
||||
val updatedAtMs: Long,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
private data class PersistedDeviceAuthMetadata(
|
||||
val scopes: List<String> = emptyList(),
|
||||
val updatedAtMs: Long = 0L,
|
||||
)
|
||||
|
||||
interface DeviceAuthTokenStore {
|
||||
fun loadEntry(deviceId: String, role: String): DeviceAuthEntry?
|
||||
fun loadToken(deviceId: String, role: String): String? = loadEntry(deviceId, role)?.token
|
||||
fun saveToken(deviceId: String, role: String, token: String, scopes: List<String> = emptyList())
|
||||
fun loadToken(deviceId: String, role: String): String?
|
||||
fun saveToken(deviceId: String, role: String, token: String)
|
||||
fun clearToken(deviceId: String, role: String)
|
||||
}
|
||||
|
||||
class DeviceAuthStore(private val prefs: SecurePrefs) : DeviceAuthTokenStore {
|
||||
private val json = Json { ignoreUnknownKeys = true }
|
||||
|
||||
override fun loadEntry(deviceId: String, role: String): DeviceAuthEntry? {
|
||||
override fun loadToken(deviceId: String, role: String): String? {
|
||||
val key = tokenKey(deviceId, role)
|
||||
val token = prefs.getString(key)?.trim()?.takeIf { it.isNotEmpty() } ?: return null
|
||||
val normalizedRole = normalizeRole(role)
|
||||
val metadata =
|
||||
prefs.getString(metadataKey(deviceId, role))
|
||||
?.let { raw ->
|
||||
runCatching { json.decodeFromString<PersistedDeviceAuthMetadata>(raw) }.getOrNull()
|
||||
}
|
||||
return DeviceAuthEntry(
|
||||
token = token,
|
||||
role = normalizedRole,
|
||||
scopes = metadata?.scopes ?: emptyList(),
|
||||
updatedAtMs = metadata?.updatedAtMs ?: 0L,
|
||||
)
|
||||
return prefs.getString(key)?.trim()?.takeIf { it.isNotEmpty() }
|
||||
}
|
||||
|
||||
override fun saveToken(deviceId: String, role: String, token: String, scopes: List<String>) {
|
||||
val normalizedScopes = normalizeScopes(scopes)
|
||||
override fun saveToken(deviceId: String, role: String, token: String) {
|
||||
val key = tokenKey(deviceId, role)
|
||||
prefs.putString(key, token.trim())
|
||||
prefs.putString(
|
||||
metadataKey(deviceId, role),
|
||||
json.encodeToString(
|
||||
PersistedDeviceAuthMetadata(
|
||||
scopes = normalizedScopes,
|
||||
updatedAtMs = System.currentTimeMillis(),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
override fun clearToken(deviceId: String, role: String) {
|
||||
val key = tokenKey(deviceId, role)
|
||||
prefs.remove(key)
|
||||
prefs.remove(metadataKey(deviceId, role))
|
||||
}
|
||||
|
||||
private fun tokenKey(deviceId: String, role: String): String {
|
||||
val normalizedDevice = normalizeDeviceId(deviceId)
|
||||
val normalizedRole = normalizeRole(role)
|
||||
val normalizedDevice = deviceId.trim().lowercase()
|
||||
val normalizedRole = role.trim().lowercase()
|
||||
return "gateway.deviceToken.$normalizedDevice.$normalizedRole"
|
||||
}
|
||||
|
||||
private fun metadataKey(deviceId: String, role: String): String {
|
||||
val normalizedDevice = normalizeDeviceId(deviceId)
|
||||
val normalizedRole = normalizeRole(role)
|
||||
return "gateway.deviceTokenMeta.$normalizedDevice.$normalizedRole"
|
||||
}
|
||||
|
||||
private fun normalizeDeviceId(deviceId: String): String = deviceId.trim().lowercase()
|
||||
|
||||
private fun normalizeRole(role: String): String = role.trim().lowercase()
|
||||
|
||||
private fun normalizeScopes(scopes: List<String>): List<String> {
|
||||
return scopes
|
||||
.map { it.trim() }
|
||||
.filter { it.isNotEmpty() }
|
||||
.distinct()
|
||||
.sorted()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -418,63 +418,11 @@ class GatewaySession(
|
||||
}
|
||||
throw GatewayConnectFailure(error)
|
||||
}
|
||||
handleConnectSuccess(res, identity.deviceId, selectedAuth.authSource)
|
||||
handleConnectSuccess(res, identity.deviceId)
|
||||
connectDeferred.complete(Unit)
|
||||
}
|
||||
|
||||
private fun shouldPersistBootstrapHandoffTokens(authSource: GatewayConnectAuthSource): Boolean {
|
||||
if (authSource != GatewayConnectAuthSource.BOOTSTRAP_TOKEN) return false
|
||||
if (isLoopbackGatewayHost(endpoint.host)) return true
|
||||
return tls != null
|
||||
}
|
||||
|
||||
private fun filteredBootstrapHandoffScopes(role: String, scopes: List<String>): List<String>? {
|
||||
return when (role.trim()) {
|
||||
"node" -> emptyList()
|
||||
"operator" -> {
|
||||
val allowedOperatorScopes =
|
||||
setOf(
|
||||
"operator.approvals",
|
||||
"operator.read",
|
||||
"operator.talk.secrets",
|
||||
"operator.write",
|
||||
)
|
||||
scopes.filter { allowedOperatorScopes.contains(it) }.distinct().sorted()
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun persistBootstrapHandoffToken(
|
||||
deviceId: String,
|
||||
role: String,
|
||||
token: String,
|
||||
scopes: List<String>,
|
||||
) {
|
||||
val filteredScopes = filteredBootstrapHandoffScopes(role, scopes) ?: return
|
||||
deviceAuthStore.saveToken(deviceId, role, token, filteredScopes)
|
||||
}
|
||||
|
||||
private fun persistIssuedDeviceToken(
|
||||
authSource: GatewayConnectAuthSource,
|
||||
deviceId: String,
|
||||
role: String,
|
||||
token: String,
|
||||
scopes: List<String>,
|
||||
) {
|
||||
if (authSource == GatewayConnectAuthSource.BOOTSTRAP_TOKEN) {
|
||||
if (!shouldPersistBootstrapHandoffTokens(authSource)) return
|
||||
persistBootstrapHandoffToken(deviceId, role, token, scopes)
|
||||
return
|
||||
}
|
||||
deviceAuthStore.saveToken(deviceId, role, token, scopes)
|
||||
}
|
||||
|
||||
private fun handleConnectSuccess(
|
||||
res: RpcResponse,
|
||||
deviceId: String,
|
||||
authSource: GatewayConnectAuthSource,
|
||||
) {
|
||||
private fun handleConnectSuccess(res: RpcResponse, deviceId: String) {
|
||||
val payloadJson = res.payloadJson ?: throw IllegalStateException("connect failed: missing payload")
|
||||
val obj = json.parseToJsonElement(payloadJson).asObjectOrNull() ?: throw IllegalStateException("connect failed")
|
||||
pendingDeviceTokenRetry = false
|
||||
@@ -484,27 +432,8 @@ class GatewaySession(
|
||||
val authObj = obj["auth"].asObjectOrNull()
|
||||
val deviceToken = authObj?.get("deviceToken").asStringOrNull()
|
||||
val authRole = authObj?.get("role").asStringOrNull() ?: options.role
|
||||
val authScopes =
|
||||
authObj?.get("scopes").asArrayOrNull()
|
||||
?.mapNotNull { it.asStringOrNull() }
|
||||
?: emptyList()
|
||||
if (!deviceToken.isNullOrBlank()) {
|
||||
persistIssuedDeviceToken(authSource, deviceId, authRole, deviceToken, authScopes)
|
||||
}
|
||||
if (shouldPersistBootstrapHandoffTokens(authSource)) {
|
||||
authObj?.get("deviceTokens").asArrayOrNull()
|
||||
?.mapNotNull { it.asObjectOrNull() }
|
||||
?.forEach { tokenEntry ->
|
||||
val handoffToken = tokenEntry["deviceToken"].asStringOrNull()
|
||||
val handoffRole = tokenEntry["role"].asStringOrNull()
|
||||
val handoffScopes =
|
||||
tokenEntry["scopes"].asArrayOrNull()
|
||||
?.mapNotNull { it.asStringOrNull() }
|
||||
?: emptyList()
|
||||
if (!handoffToken.isNullOrBlank() && !handoffRole.isNullOrBlank()) {
|
||||
persistBootstrapHandoffToken(deviceId, handoffRole, handoffToken, handoffScopes)
|
||||
}
|
||||
}
|
||||
deviceAuthStore.saveToken(deviceId, authRole, deviceToken)
|
||||
}
|
||||
val rawCanvas = obj["canvasHostUrl"].asStringOrNull()
|
||||
canvasHostUrl = normalizeCanvasHostUrl(rawCanvas, endpoint, isTlsConnection = tls != null)
|
||||
@@ -970,8 +899,6 @@ private fun formatGatewayAuthorityHost(host: String): String {
|
||||
|
||||
private fun JsonElement?.asObjectOrNull(): JsonObject? = this as? JsonObject
|
||||
|
||||
private fun JsonElement?.asArrayOrNull(): JsonArray? = this as? JsonArray
|
||||
|
||||
private fun JsonElement?.asStringOrNull(): String? =
|
||||
when (this) {
|
||||
is JsonNull -> null
|
||||
|
||||
@@ -7,7 +7,7 @@ import ai.openclaw.app.gateway.GatewayClientInfo
|
||||
import ai.openclaw.app.gateway.GatewayConnectOptions
|
||||
import ai.openclaw.app.gateway.GatewayEndpoint
|
||||
import ai.openclaw.app.gateway.GatewayTlsParams
|
||||
import ai.openclaw.app.gateway.isPrivateLanGatewayHost
|
||||
import ai.openclaw.app.gateway.isLoopbackGatewayHost
|
||||
import ai.openclaw.app.LocationMode
|
||||
import ai.openclaw.app.VoiceWakeMode
|
||||
|
||||
@@ -34,7 +34,7 @@ class ConnectionManager(
|
||||
val stableId = endpoint.stableId
|
||||
val stored = storedFingerprint?.trim().takeIf { !it.isNullOrEmpty() }
|
||||
val isManual = stableId.startsWith("manual|")
|
||||
val cleartextAllowedHost = isPrivateLanGatewayHost(endpoint.host)
|
||||
val cleartextAllowedHost = isLoopbackGatewayHost(endpoint.host)
|
||||
|
||||
if (isManual) {
|
||||
if (!manualTlsEnabled && cleartextAllowedHost) return null
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package ai.openclaw.app.ui
|
||||
|
||||
import ai.openclaw.app.gateway.isPrivateLanGatewayHost
|
||||
import ai.openclaw.app.gateway.isLoopbackGatewayHost
|
||||
import java.util.Base64
|
||||
import java.util.Locale
|
||||
import java.net.URI
|
||||
@@ -56,7 +56,7 @@ internal data class GatewayScannedSetupCodeResult(
|
||||
|
||||
private val gatewaySetupJson = Json { ignoreUnknownKeys = true }
|
||||
private const val remoteGatewaySecurityRule =
|
||||
"Tailscale and public mobile nodes require wss:// or Tailscale Serve. ws:// is allowed for private LAN, localhost, and the Android emulator."
|
||||
"Non-loopback mobile nodes require wss:// or Tailscale Serve. ws:// is allowed only for localhost and the Android emulator."
|
||||
private const val remoteGatewaySecurityFix =
|
||||
"Use a private LAN host/address, or enable Tailscale Serve / expose a wss:// gateway URL."
|
||||
|
||||
@@ -143,7 +143,7 @@ internal fun parseGatewayEndpoint(rawInput: String): GatewayEndpointConfig? {
|
||||
"wss", "https" -> true
|
||||
else -> true
|
||||
}
|
||||
if (!tls && !isPrivateLanGatewayHost(host)) {
|
||||
if (!tls && !isLoopbackGatewayHost(host)) {
|
||||
return GatewayEndpointParseResult(error = GatewayEndpointValidationError.INSECURE_REMOTE_URL)
|
||||
}
|
||||
val defaultPort =
|
||||
|
||||
@@ -14,13 +14,11 @@ import android.speech.SpeechRecognizer
|
||||
import androidx.core.content.ContextCompat
|
||||
import java.util.UUID
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonArray
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
@@ -101,27 +99,11 @@ class MicCaptureManager(
|
||||
private var transcriptFlushJob: Job? = null
|
||||
private var pendingRunTimeoutJob: Job? = null
|
||||
private var stopRequested = false
|
||||
private val ttsPauseLock = Any()
|
||||
private var ttsPauseDepth = 0
|
||||
private var resumeMicAfterTts = false
|
||||
|
||||
fun setMicEnabled(enabled: Boolean) {
|
||||
if (_micEnabled.value == enabled) return
|
||||
_micEnabled.value = enabled
|
||||
if (enabled) {
|
||||
val pausedForTts =
|
||||
synchronized(ttsPauseLock) {
|
||||
if (ttsPauseDepth > 0) {
|
||||
resumeMicAfterTts = true
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
if (pausedForTts) {
|
||||
_statusText.value = if (_isSending.value) "Speaking · waiting for reply" else "Speaking…"
|
||||
return
|
||||
}
|
||||
start()
|
||||
sendQueuedIfIdle()
|
||||
} else {
|
||||
@@ -144,58 +126,6 @@ class MicCaptureManager(
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun pauseForTts() {
|
||||
val shouldPause =
|
||||
synchronized(ttsPauseLock) {
|
||||
ttsPauseDepth += 1
|
||||
if (ttsPauseDepth > 1) return@synchronized false
|
||||
resumeMicAfterTts = _micEnabled.value
|
||||
val active = resumeMicAfterTts || recognizer != null || _isListening.value
|
||||
if (!active) return@synchronized false
|
||||
stopRequested = true
|
||||
restartJob?.cancel()
|
||||
restartJob = null
|
||||
transcriptFlushJob?.cancel()
|
||||
transcriptFlushJob = null
|
||||
_isListening.value = false
|
||||
_inputLevel.value = 0f
|
||||
_liveTranscript.value = null
|
||||
_statusText.value = if (_isSending.value) "Speaking · waiting for reply" else "Speaking…"
|
||||
true
|
||||
}
|
||||
if (!shouldPause) return
|
||||
withContext(Dispatchers.Main) {
|
||||
recognizer?.cancel()
|
||||
recognizer?.destroy()
|
||||
recognizer = null
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun resumeAfterTts() {
|
||||
val shouldResume =
|
||||
synchronized(ttsPauseLock) {
|
||||
if (ttsPauseDepth == 0) return@synchronized false
|
||||
ttsPauseDepth -= 1
|
||||
if (ttsPauseDepth > 0) return@synchronized false
|
||||
val resume = resumeMicAfterTts && _micEnabled.value
|
||||
resumeMicAfterTts = false
|
||||
if (!resume) {
|
||||
_statusText.value =
|
||||
when {
|
||||
_micEnabled.value && _isSending.value -> "Listening · sending queued voice"
|
||||
_micEnabled.value -> "Listening"
|
||||
_isSending.value -> "Mic off · sending…"
|
||||
else -> "Mic off"
|
||||
}
|
||||
}
|
||||
resume
|
||||
}
|
||||
if (!shouldResume) return
|
||||
stopRequested = false
|
||||
start()
|
||||
sendQueuedIfIdle()
|
||||
}
|
||||
|
||||
fun onGatewayConnectionChanged(connected: Boolean) {
|
||||
gatewayConnected = connected
|
||||
if (connected) {
|
||||
|
||||
@@ -22,13 +22,11 @@ import ai.openclaw.app.gateway.GatewaySession
|
||||
import java.util.Locale
|
||||
import java.util.UUID
|
||||
import java.util.concurrent.atomic.AtomicLong
|
||||
import kotlin.coroutines.coroutineContext
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.NonCancellable
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.ensureActive
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
@@ -48,8 +46,6 @@ class TalkModeManager(
|
||||
private val session: GatewaySession,
|
||||
private val supportsChatSubscribe: Boolean,
|
||||
private val isConnected: () -> Boolean,
|
||||
private val onBeforeSpeak: suspend () -> Unit = {},
|
||||
private val onAfterSpeak: suspend () -> Unit = {},
|
||||
) {
|
||||
companion object {
|
||||
private const val tag = "TalkMode"
|
||||
@@ -105,7 +101,6 @@ class TalkModeManager(
|
||||
private val playbackGeneration = AtomicLong(0L)
|
||||
|
||||
private var ttsJob: Job? = null
|
||||
private val ttsJobLock = Any()
|
||||
private val ttsLock = Any()
|
||||
private var textToSpeech: TextToSpeech? = null
|
||||
private var textToSpeechInit: CompletableDeferred<TextToSpeech>? = null
|
||||
@@ -168,11 +163,8 @@ class TalkModeManager(
|
||||
?: waitForAssistantText(session, startedAt, if (ok) 12_000 else 25_000)
|
||||
if (!assistant.isNullOrBlank()) {
|
||||
val playbackToken = playbackGeneration.incrementAndGet()
|
||||
cancelActivePlayback()
|
||||
_statusText.value = "Speaking…"
|
||||
runPlaybackSession(playbackToken) {
|
||||
playAssistant(assistant, playbackToken)
|
||||
}
|
||||
playAssistant(assistant, playbackToken)
|
||||
} else {
|
||||
_statusText.value = "No reply"
|
||||
}
|
||||
@@ -188,12 +180,14 @@ class TalkModeManager(
|
||||
|
||||
fun playTtsForText(text: String) {
|
||||
val playbackToken = playbackGeneration.incrementAndGet()
|
||||
cancelActivePlayback()
|
||||
scope.launch {
|
||||
ttsJob?.cancel()
|
||||
ttsJob = scope.launch {
|
||||
reloadConfig()
|
||||
runPlaybackSession(playbackToken) {
|
||||
playAssistant(text, playbackToken)
|
||||
}
|
||||
ensurePlaybackActive(playbackToken)
|
||||
_isSpeaking.value = true
|
||||
_statusText.value = "Speaking…"
|
||||
playAssistant(text, playbackToken)
|
||||
ttsJob = null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,11 +270,10 @@ class TalkModeManager(
|
||||
suspend fun speakAssistantReply(text: String) {
|
||||
if (!playbackEnabled) return
|
||||
val playbackToken = playbackGeneration.incrementAndGet()
|
||||
cancelActivePlayback()
|
||||
stopSpeaking(resetInterrupt = false)
|
||||
ensureConfigLoaded()
|
||||
runPlaybackSession(playbackToken) {
|
||||
playAssistant(text, playbackToken)
|
||||
}
|
||||
ensurePlaybackActive(playbackToken)
|
||||
playAssistant(text, playbackToken)
|
||||
}
|
||||
|
||||
private fun start() {
|
||||
@@ -490,10 +483,9 @@ class TalkModeManager(
|
||||
}
|
||||
Log.d(tag, "assistant text ok chars=${assistant.length}")
|
||||
val playbackToken = playbackGeneration.incrementAndGet()
|
||||
cancelActivePlayback()
|
||||
runPlaybackSession(playbackToken) {
|
||||
playAssistant(assistant, playbackToken)
|
||||
}
|
||||
stopSpeaking(resetInterrupt = false)
|
||||
ensurePlaybackActive(playbackToken)
|
||||
playAssistant(assistant, playbackToken)
|
||||
} catch (err: Throwable) {
|
||||
if (err is CancellationException) {
|
||||
Log.d(tag, "finalize speech cancelled")
|
||||
@@ -673,58 +665,10 @@ class TalkModeManager(
|
||||
}
|
||||
_statusText.value = "Speak failed: ${err.message ?: err::class.simpleName}"
|
||||
Log.w(tag, "system tts failed: ${err.message ?: err::class.simpleName}")
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun runPlaybackSession(
|
||||
playbackToken: Long,
|
||||
block: suspend () -> Unit,
|
||||
) {
|
||||
val currentJob = coroutineContext[Job]
|
||||
var shouldResumeAfterSpeak = false
|
||||
try {
|
||||
val claimedPlayback =
|
||||
synchronized(ttsJobLock) {
|
||||
if (!playbackEnabled || playbackToken != playbackGeneration.get()) {
|
||||
false
|
||||
} else {
|
||||
ttsJob = currentJob
|
||||
true
|
||||
}
|
||||
}
|
||||
if (!claimedPlayback) {
|
||||
ensurePlaybackActive(playbackToken)
|
||||
return
|
||||
}
|
||||
ensurePlaybackActive(playbackToken)
|
||||
shouldResumeAfterSpeak = true
|
||||
onBeforeSpeak()
|
||||
ensurePlaybackActive(playbackToken)
|
||||
_isSpeaking.value = true
|
||||
_statusText.value = "Speaking…"
|
||||
block()
|
||||
} finally {
|
||||
synchronized(ttsJobLock) {
|
||||
if (ttsJob === currentJob) {
|
||||
ttsJob = null
|
||||
}
|
||||
}
|
||||
_isSpeaking.value = false
|
||||
if (shouldResumeAfterSpeak) {
|
||||
withContext(NonCancellable) {
|
||||
onAfterSpeak()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun cancelActivePlayback() {
|
||||
val activeJob =
|
||||
synchronized(ttsJobLock) {
|
||||
ttsJob
|
||||
}
|
||||
activeJob?.cancel()
|
||||
stopTextToSpeechPlayback()
|
||||
_isSpeaking.value = false
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun speakWithSystemTts(text: String, directive: TalkDirective?, playbackToken: Long) {
|
||||
|
||||
@@ -21,72 +21,17 @@ import java.util.UUID
|
||||
@Config(sdk = [34])
|
||||
class GatewayBootstrapAuthTest {
|
||||
@Test
|
||||
fun skipsOperatorSessionWhenOnlyBootstrapAuthExists() {
|
||||
assertFalse(
|
||||
shouldConnectOperatorSession(
|
||||
NodeRuntime.GatewayConnectAuth(token = "", bootstrapToken = "bootstrap-1", password = ""),
|
||||
storedOperatorToken = "",
|
||||
),
|
||||
)
|
||||
assertFalse(
|
||||
shouldConnectOperatorSession(
|
||||
NodeRuntime.GatewayConnectAuth(token = null, bootstrapToken = "bootstrap-1", password = null),
|
||||
storedOperatorToken = null,
|
||||
),
|
||||
)
|
||||
fun connectsOperatorSessionWhenBootstrapAuthExists() {
|
||||
assertTrue(shouldConnectOperatorSession(token = "", bootstrapToken = "bootstrap-1", password = "", storedOperatorToken = ""))
|
||||
assertTrue(shouldConnectOperatorSession(token = null, bootstrapToken = "bootstrap-1", password = null, storedOperatorToken = null))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun connectsOperatorSessionWhenSharedPasswordOrStoredAuthExists() {
|
||||
assertTrue(
|
||||
shouldConnectOperatorSession(
|
||||
NodeRuntime.GatewayConnectAuth(token = "shared-token", bootstrapToken = "bootstrap-1", password = null),
|
||||
storedOperatorToken = null,
|
||||
),
|
||||
)
|
||||
assertTrue(
|
||||
shouldConnectOperatorSession(
|
||||
NodeRuntime.GatewayConnectAuth(token = null, bootstrapToken = "bootstrap-1", password = "shared-password"),
|
||||
storedOperatorToken = null,
|
||||
),
|
||||
)
|
||||
assertTrue(
|
||||
shouldConnectOperatorSession(
|
||||
NodeRuntime.GatewayConnectAuth(token = null, bootstrapToken = "bootstrap-1", password = null),
|
||||
storedOperatorToken = "stored-token",
|
||||
),
|
||||
)
|
||||
assertFalse(
|
||||
shouldConnectOperatorSession(
|
||||
NodeRuntime.GatewayConnectAuth(token = null, bootstrapToken = "", password = null),
|
||||
storedOperatorToken = null,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun resolveOperatorSessionConnectAuthUsesStoredTokenPathAfterBootstrapHandoff() {
|
||||
val resolved =
|
||||
resolveOperatorSessionConnectAuth(
|
||||
auth = NodeRuntime.GatewayConnectAuth(token = null, bootstrapToken = "bootstrap-1", password = null),
|
||||
storedOperatorToken = "stored-token",
|
||||
)
|
||||
|
||||
assertEquals(NodeRuntime.GatewayConnectAuth(token = null, bootstrapToken = null, password = null), resolved)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun resolveOperatorSessionConnectAuthPrefersExplicitSharedAuth() {
|
||||
val resolved =
|
||||
resolveOperatorSessionConnectAuth(
|
||||
auth = NodeRuntime.GatewayConnectAuth(token = "shared-token", bootstrapToken = "bootstrap-1", password = "shared-password"),
|
||||
storedOperatorToken = "stored-token",
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
NodeRuntime.GatewayConnectAuth(token = "shared-token", bootstrapToken = null, password = null),
|
||||
resolved,
|
||||
)
|
||||
fun skipsOperatorSessionOnlyWhenNoSharedBootstrapOrStoredAuthExists() {
|
||||
assertTrue(shouldConnectOperatorSession(token = "shared-token", bootstrapToken = "bootstrap-1", password = null, storedOperatorToken = null))
|
||||
assertTrue(shouldConnectOperatorSession(token = null, bootstrapToken = "bootstrap-1", password = "shared-password", storedOperatorToken = null))
|
||||
assertTrue(shouldConnectOperatorSession(token = null, bootstrapToken = null, password = null, storedOperatorToken = "stored-token"))
|
||||
assertFalse(shouldConnectOperatorSession(token = null, bootstrapToken = "", password = null, storedOperatorToken = null))
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -152,7 +97,7 @@ class GatewayBootstrapAuthTest {
|
||||
|
||||
assertEquals("fp-1", prefs.loadGatewayTlsFingerprint(endpoint.stableId))
|
||||
assertEquals("setup-bootstrap-token", desiredBootstrapToken(runtime, "nodeSession"))
|
||||
assertNull(desiredBootstrapToken(runtime, "operatorSession"))
|
||||
assertEquals("setup-bootstrap-token", desiredBootstrapToken(runtime, "operatorSession"))
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
package ai.openclaw.app.gateway
|
||||
|
||||
import ai.openclaw.app.SecurePrefs
|
||||
import android.content.Context
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.RuntimeEnvironment
|
||||
import org.robolectric.annotation.Config
|
||||
import java.util.UUID
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(sdk = [34])
|
||||
class DeviceAuthStoreTest {
|
||||
@Test
|
||||
fun saveTokenPersistsNormalizedScopesMetadata() {
|
||||
val app = RuntimeEnvironment.getApplication()
|
||||
val securePrefs =
|
||||
app.getSharedPreferences(
|
||||
"openclaw.node.secure.test.${UUID.randomUUID()}",
|
||||
Context.MODE_PRIVATE,
|
||||
)
|
||||
val prefs = SecurePrefs(app, securePrefsOverride = securePrefs)
|
||||
val store = DeviceAuthStore(prefs)
|
||||
|
||||
store.saveToken(
|
||||
deviceId = " Device-1 ",
|
||||
role = " Operator ",
|
||||
token = " operator-token ",
|
||||
scopes = listOf("operator.write", "operator.read", "operator.write", " "),
|
||||
)
|
||||
|
||||
val entry = store.loadEntry("device-1", "operator")
|
||||
assertNotNull(entry)
|
||||
assertEquals("operator-token", entry?.token)
|
||||
assertEquals("operator", entry?.role)
|
||||
assertEquals(listOf("operator.read", "operator.write"), entry?.scopes)
|
||||
assertTrue((entry?.updatedAtMs ?: 0L) > 0L)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun loadEntryReadsLegacyTokenWithoutMetadata() {
|
||||
val app = RuntimeEnvironment.getApplication()
|
||||
val securePrefs =
|
||||
app.getSharedPreferences(
|
||||
"openclaw.node.secure.test.${UUID.randomUUID()}",
|
||||
Context.MODE_PRIVATE,
|
||||
)
|
||||
val prefs = SecurePrefs(app, securePrefsOverride = securePrefs)
|
||||
prefs.putString("gateway.deviceToken.device-1.operator", "legacy-token")
|
||||
val store = DeviceAuthStore(prefs)
|
||||
|
||||
val entry = store.loadEntry("device-1", "operator")
|
||||
assertNotNull(entry)
|
||||
assertEquals("legacy-token", entry?.token)
|
||||
assertEquals("operator", entry?.role)
|
||||
assertEquals(emptyList<String>(), entry?.scopes)
|
||||
assertEquals(0L, entry?.updatedAtMs)
|
||||
}
|
||||
}
|
||||
@@ -35,18 +35,12 @@ private const val CONNECT_CHALLENGE_FRAME =
|
||||
"""{"type":"event","event":"connect.challenge","payload":{"nonce":"android-test-nonce"}}"""
|
||||
|
||||
private class InMemoryDeviceAuthStore : DeviceAuthTokenStore {
|
||||
private val tokens = mutableMapOf<String, DeviceAuthEntry>()
|
||||
private val tokens = mutableMapOf<String, String>()
|
||||
|
||||
override fun loadEntry(deviceId: String, role: String): DeviceAuthEntry? = tokens["${deviceId.trim()}|${role.trim()}"]
|
||||
override fun loadToken(deviceId: String, role: String): String? = tokens["${deviceId.trim()}|${role.trim()}"]?.trim()?.takeIf { it.isNotEmpty() }
|
||||
|
||||
override fun saveToken(deviceId: String, role: String, token: String, scopes: List<String>) {
|
||||
tokens["${deviceId.trim()}|${role.trim()}"] =
|
||||
DeviceAuthEntry(
|
||||
token = token.trim(),
|
||||
role = role.trim(),
|
||||
scopes = scopes,
|
||||
updatedAtMs = System.currentTimeMillis(),
|
||||
)
|
||||
override fun saveToken(deviceId: String, role: String, token: String) {
|
||||
tokens["${deviceId.trim()}|${role.trim()}"] = token.trim()
|
||||
}
|
||||
|
||||
override fun clearToken(deviceId: String, role: String) {
|
||||
@@ -219,144 +213,6 @@ class GatewaySessionInvokeTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun connect_storesPrimaryDeviceTokenFromSuccessfulSharedTokenConnect() = runBlocking {
|
||||
val json = testJson()
|
||||
val connected = CompletableDeferred<Unit>()
|
||||
val lastDisconnect = AtomicReference("")
|
||||
val server =
|
||||
startGatewayServer(json) { webSocket, id, method, _ ->
|
||||
when (method) {
|
||||
"connect" -> {
|
||||
webSocket.send(
|
||||
connectResponseFrame(
|
||||
id,
|
||||
authJson = """{"deviceToken":"shared-node-token","role":"node","scopes":[]}""",
|
||||
),
|
||||
)
|
||||
webSocket.close(1000, "done")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val harness =
|
||||
createNodeHarness(
|
||||
connected = connected,
|
||||
lastDisconnect = lastDisconnect,
|
||||
) { GatewaySession.InvokeResult.ok("""{"handled":true}""") }
|
||||
|
||||
try {
|
||||
connectNodeSession(
|
||||
session = harness.session,
|
||||
port = server.port,
|
||||
token = "shared-auth-token",
|
||||
bootstrapToken = null,
|
||||
)
|
||||
awaitConnectedOrThrow(connected, lastDisconnect, server)
|
||||
|
||||
val deviceId = DeviceIdentityStore(RuntimeEnvironment.getApplication()).loadOrCreate().deviceId
|
||||
assertEquals("shared-node-token", harness.deviceAuthStore.loadToken(deviceId, "node"))
|
||||
assertNull(harness.deviceAuthStore.loadToken(deviceId, "operator"))
|
||||
} finally {
|
||||
shutdownHarness(harness, server)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun bootstrapConnect_storesAdditionalBoundedDeviceTokensOnTrustedTransport() = runBlocking {
|
||||
val json = testJson()
|
||||
val connected = CompletableDeferred<Unit>()
|
||||
val lastDisconnect = AtomicReference("")
|
||||
val server =
|
||||
startGatewayServer(json) { webSocket, id, method, _ ->
|
||||
when (method) {
|
||||
"connect" -> {
|
||||
webSocket.send(
|
||||
connectResponseFrame(
|
||||
id,
|
||||
authJson =
|
||||
"""{"deviceToken":"bootstrap-node-token","role":"node","scopes":[],"deviceTokens":[{"deviceToken":"bootstrap-operator-token","role":"operator","scopes":["operator.admin","operator.approvals","operator.read","operator.talk.secrets","operator.write"]}]}""",
|
||||
),
|
||||
)
|
||||
webSocket.close(1000, "done")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val harness =
|
||||
createNodeHarness(
|
||||
connected = connected,
|
||||
lastDisconnect = lastDisconnect,
|
||||
) { GatewaySession.InvokeResult.ok("""{"handled":true}""") }
|
||||
|
||||
try {
|
||||
connectNodeSession(
|
||||
session = harness.session,
|
||||
port = server.port,
|
||||
token = null,
|
||||
bootstrapToken = "bootstrap-token",
|
||||
)
|
||||
awaitConnectedOrThrow(connected, lastDisconnect, server)
|
||||
|
||||
val deviceId = DeviceIdentityStore(RuntimeEnvironment.getApplication()).loadOrCreate().deviceId
|
||||
val nodeEntry = harness.deviceAuthStore.loadEntry(deviceId, "node")
|
||||
val operatorEntry = harness.deviceAuthStore.loadEntry(deviceId, "operator")
|
||||
assertEquals("bootstrap-node-token", nodeEntry?.token)
|
||||
assertEquals(emptyList<String>(), nodeEntry?.scopes)
|
||||
assertEquals("bootstrap-operator-token", operatorEntry?.token)
|
||||
assertEquals(
|
||||
listOf("operator.approvals", "operator.read", "operator.talk.secrets", "operator.write"),
|
||||
operatorEntry?.scopes,
|
||||
)
|
||||
} finally {
|
||||
shutdownHarness(harness, server)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun nonBootstrapConnect_ignoresAdditionalBootstrapDeviceTokens() = runBlocking {
|
||||
val json = testJson()
|
||||
val connected = CompletableDeferred<Unit>()
|
||||
val lastDisconnect = AtomicReference("")
|
||||
val server =
|
||||
startGatewayServer(json) { webSocket, id, method, _ ->
|
||||
when (method) {
|
||||
"connect" -> {
|
||||
webSocket.send(
|
||||
connectResponseFrame(
|
||||
id,
|
||||
authJson =
|
||||
"""{"deviceToken":"shared-node-token","role":"node","scopes":[],"deviceTokens":[{"deviceToken":"shared-operator-token","role":"operator","scopes":["operator.approvals","operator.read"]}]}""",
|
||||
),
|
||||
)
|
||||
webSocket.close(1000, "done")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val harness =
|
||||
createNodeHarness(
|
||||
connected = connected,
|
||||
lastDisconnect = lastDisconnect,
|
||||
) { GatewaySession.InvokeResult.ok("""{"handled":true}""") }
|
||||
|
||||
try {
|
||||
connectNodeSession(
|
||||
session = harness.session,
|
||||
port = server.port,
|
||||
token = "shared-auth-token",
|
||||
bootstrapToken = null,
|
||||
)
|
||||
awaitConnectedOrThrow(connected, lastDisconnect, server)
|
||||
|
||||
val deviceId = DeviceIdentityStore(RuntimeEnvironment.getApplication()).loadOrCreate().deviceId
|
||||
assertEquals("shared-node-token", harness.deviceAuthStore.loadToken(deviceId, "node"))
|
||||
assertNull(harness.deviceAuthStore.loadToken(deviceId, "operator"))
|
||||
} finally {
|
||||
shutdownHarness(harness, server)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun nodeInvokeRequest_roundTripsInvokeResult() = runBlocking {
|
||||
val handshakeOrigin = AtomicReference<String?>(null)
|
||||
@@ -614,14 +470,9 @@ class GatewaySessionInvokeTest {
|
||||
}
|
||||
}
|
||||
|
||||
private fun connectResponseFrame(
|
||||
id: String,
|
||||
canvasHostUrl: String? = null,
|
||||
authJson: String? = null,
|
||||
): String {
|
||||
private fun connectResponseFrame(id: String, canvasHostUrl: String? = null): String {
|
||||
val canvas = canvasHostUrl?.let { "\"canvasHostUrl\":\"$it\"," } ?: ""
|
||||
val auth = authJson?.let { "\"auth\":$it," } ?: ""
|
||||
return """{"type":"res","id":"$id","ok":true,"payload":{$canvas$auth"snapshot":{"sessionDefaults":{"mainSessionKey":"main"}}}}"""
|
||||
return """{"type":"res","id":"$id","ok":true,"payload":{$canvas"snapshot":{"sessionDefaults":{"mainSessionKey":"main"}}}}"""
|
||||
}
|
||||
|
||||
private fun startGatewayServer(
|
||||
|
||||
@@ -108,7 +108,7 @@ class ConnectionManagerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun resolveTlsParamsForEndpoint_manualPrivateLanCanStayCleartextWhenToggleIsOff() {
|
||||
fun resolveTlsParamsForEndpoint_manualPrivateLanRequiresTlsWhenToggleIsOff() {
|
||||
val endpoint = GatewayEndpoint.manual(host = "192.168.1.20", port = 18789)
|
||||
|
||||
val params =
|
||||
@@ -118,7 +118,9 @@ class ConnectionManagerTest {
|
||||
manualTlsEnabled = false,
|
||||
)
|
||||
|
||||
assertNull(params)
|
||||
assertEquals(true, params?.required)
|
||||
assertNull(params?.expectedFingerprint)
|
||||
assertEquals(false, params?.allowTOFU)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -146,7 +148,7 @@ class ConnectionManagerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun resolveTlsParamsForEndpoint_discoveryPrivateLanWithoutHintsCanStayCleartext() {
|
||||
fun resolveTlsParamsForEndpoint_discoveryPrivateLanWithoutHintsRequiresTls() {
|
||||
val endpoint =
|
||||
GatewayEndpoint(
|
||||
stableId = "_openclaw-gw._tcp.|local.|Test",
|
||||
@@ -164,7 +166,9 @@ class ConnectionManagerTest {
|
||||
manualTlsEnabled = false,
|
||||
)
|
||||
|
||||
assertNull(params)
|
||||
assertEquals(true, params?.required)
|
||||
assertNull(params?.expectedFingerprint)
|
||||
assertEquals(false, params?.allowTOFU)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -290,19 +290,11 @@ class GatewayConfigResolverTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseGatewayEndpointResultAcceptsLanCleartextGateway() {
|
||||
fun parseGatewayEndpointResultFlagsInsecureLanCleartextGateway() {
|
||||
val parsed = parseGatewayEndpointResult("ws://192.168.1.20:18789")
|
||||
|
||||
assertEquals(
|
||||
GatewayEndpointConfig(
|
||||
host = "192.168.1.20",
|
||||
port = 18789,
|
||||
tls = false,
|
||||
displayUrl = "http://192.168.1.20:18789",
|
||||
),
|
||||
parsed.config,
|
||||
)
|
||||
assertNull(parsed.error)
|
||||
assertNull(parsed.config)
|
||||
assertEquals(GatewayEndpointValidationError.INSECURE_REMOTE_URL, parsed.error)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// Shared iOS version defaults.
|
||||
// Generated overrides live in build/Version.xcconfig (git-ignored).
|
||||
|
||||
OPENCLAW_GATEWAY_VERSION = 2026.4.4
|
||||
OPENCLAW_MARKETING_VERSION = 2026.4.4
|
||||
OPENCLAW_BUILD_VERSION = 2026040401
|
||||
OPENCLAW_GATEWAY_VERSION = 2026.4.3
|
||||
OPENCLAW_MARKETING_VERSION = 2026.4.3
|
||||
OPENCLAW_BUILD_VERSION = 2026040301
|
||||
|
||||
#include? "../build/Version.xcconfig"
|
||||
|
||||
@@ -1816,7 +1816,7 @@ private extension NodeAppModel {
|
||||
return DeviceAuthStore.loadToken(deviceId: identity.deviceId, role: role) != nil
|
||||
}
|
||||
|
||||
nonisolated static func shouldStartOperatorGatewayLoop(
|
||||
static func shouldStartOperatorGatewayLoop(
|
||||
token: String?,
|
||||
bootstrapToken: String?,
|
||||
password: String?,
|
||||
@@ -1837,7 +1837,7 @@ private extension NodeAppModel {
|
||||
return hasStoredOperatorToken
|
||||
}
|
||||
|
||||
nonisolated static func clearingBootstrapToken(in config: GatewayConnectConfig?) -> GatewayConnectConfig? {
|
||||
static func clearingBootstrapToken(in config: GatewayConnectConfig?) -> GatewayConnectConfig? {
|
||||
guard let config else { return nil }
|
||||
let trimmedBootstrapToken = config.bootstrapToken?
|
||||
.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
||||
@@ -1878,36 +1878,6 @@ private extension NodeAppModel {
|
||||
GatewaySettingsStore.clearGatewayBootstrapToken(instanceId: trimmedInstanceId)
|
||||
}
|
||||
|
||||
private func handleSuccessfulBootstrapGatewayOnboarding(
|
||||
url: URL,
|
||||
stableID: String,
|
||||
token: String?,
|
||||
password: String?,
|
||||
nodeOptions: GatewayConnectOptions,
|
||||
sessionBox: WebSocketSessionBox?) async
|
||||
{
|
||||
self.clearPersistedGatewayBootstrapTokenIfNeeded()
|
||||
if self.operatorGatewayTask == nil && self.shouldStartOperatorGatewayLoop(
|
||||
token: token,
|
||||
bootstrapToken: nil,
|
||||
password: password,
|
||||
stableID: stableID)
|
||||
{
|
||||
self.startOperatorGatewayLoop(
|
||||
url: url,
|
||||
stableID: stableID,
|
||||
token: token,
|
||||
bootstrapToken: nil,
|
||||
password: password,
|
||||
nodeOptions: nodeOptions,
|
||||
sessionBox: sessionBox)
|
||||
}
|
||||
|
||||
// QR bootstrap onboarding should surface the system notification permission
|
||||
// prompt immediately so visible APNs alerts work without a second manual step.
|
||||
_ = await self.requestNotificationAuthorizationIfNeeded()
|
||||
}
|
||||
|
||||
func refreshBackgroundReconnectSuppressionIfNeeded(source: String) {
|
||||
guard self.isBackgrounded else { return }
|
||||
guard !self.backgroundReconnectSuppressed else { return }
|
||||
@@ -1957,20 +1927,17 @@ private extension NodeAppModel {
|
||||
continue
|
||||
}
|
||||
|
||||
let reconnectAuth = self.currentGatewayReconnectAuth(
|
||||
fallbackToken: token,
|
||||
fallbackBootstrapToken: bootstrapToken,
|
||||
fallbackPassword: password)
|
||||
let effectiveClientId =
|
||||
GatewaySettingsStore.loadGatewayClientIdOverride(stableID: stableID) ?? nodeOptions.clientId
|
||||
let operatorOptions = self.makeOperatorConnectOptions(
|
||||
clientId: effectiveClientId,
|
||||
displayName: nodeOptions.clientDisplayName,
|
||||
includeApprovalScope: self.shouldRequestOperatorApprovalScope(
|
||||
token: reconnectAuth.token,
|
||||
password: reconnectAuth.password))
|
||||
displayName: nodeOptions.clientDisplayName)
|
||||
|
||||
do {
|
||||
let reconnectAuth = self.currentGatewayReconnectAuth(
|
||||
fallbackToken: token,
|
||||
fallbackBootstrapToken: bootstrapToken,
|
||||
fallbackPassword: password)
|
||||
try await self.operatorGateway.connect(
|
||||
url: url,
|
||||
token: reconnectAuth.token,
|
||||
@@ -2082,14 +2049,13 @@ private extension NodeAppModel {
|
||||
fallbackToken: token,
|
||||
fallbackBootstrapToken: bootstrapToken,
|
||||
fallbackPassword: password)
|
||||
let connectedOptions = currentOptions
|
||||
GatewayDiagnostics.log("connect attempt epochMs=\(epochMs) url=\(url.absoluteString)")
|
||||
try await self.nodeGateway.connect(
|
||||
url: url,
|
||||
token: reconnectAuth.token,
|
||||
bootstrapToken: reconnectAuth.bootstrapToken,
|
||||
password: reconnectAuth.password,
|
||||
connectOptions: connectedOptions,
|
||||
connectOptions: currentOptions,
|
||||
sessionBox: sessionBox,
|
||||
onConnected: { [weak self] in
|
||||
guard let self else { return }
|
||||
@@ -2105,13 +2071,24 @@ private extension NodeAppModel {
|
||||
reconnectAuth.bootstrapToken?.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
.isEmpty == false
|
||||
if usedBootstrapToken {
|
||||
await self.handleSuccessfulBootstrapGatewayOnboarding(
|
||||
url: url,
|
||||
stableID: stableID,
|
||||
token: reconnectAuth.token,
|
||||
password: reconnectAuth.password,
|
||||
nodeOptions: connectedOptions,
|
||||
sessionBox: sessionBox)
|
||||
await MainActor.run {
|
||||
self.clearPersistedGatewayBootstrapTokenIfNeeded()
|
||||
if self.operatorGatewayTask == nil && self.shouldStartOperatorGatewayLoop(
|
||||
token: reconnectAuth.token,
|
||||
bootstrapToken: nil,
|
||||
password: reconnectAuth.password,
|
||||
stableID: stableID)
|
||||
{
|
||||
self.startOperatorGatewayLoop(
|
||||
url: url,
|
||||
stableID: stableID,
|
||||
token: reconnectAuth.token,
|
||||
bootstrapToken: nil,
|
||||
password: reconnectAuth.password,
|
||||
nodeOptions: currentOptions,
|
||||
sessionBox: sessionBox)
|
||||
}
|
||||
}
|
||||
}
|
||||
let relayData = await MainActor.run {
|
||||
(
|
||||
@@ -2269,47 +2246,10 @@ private extension NodeAppModel {
|
||||
}
|
||||
}
|
||||
|
||||
func shouldRequestOperatorApprovalScope(token: String?, password: String?) -> Bool {
|
||||
let identity = DeviceIdentityStore.loadOrCreate()
|
||||
let storedOperatorScopes = DeviceAuthStore
|
||||
.loadToken(deviceId: identity.deviceId, role: "operator")?
|
||||
.scopes ?? []
|
||||
return Self.shouldRequestOperatorApprovalScope(
|
||||
token: token,
|
||||
password: password,
|
||||
storedOperatorScopes: storedOperatorScopes)
|
||||
}
|
||||
|
||||
nonisolated static func shouldRequestOperatorApprovalScope(
|
||||
token: String?,
|
||||
password: String?,
|
||||
storedOperatorScopes: [String]
|
||||
) -> Bool {
|
||||
let trimmedToken = token?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
||||
if !trimmedToken.isEmpty {
|
||||
return true
|
||||
}
|
||||
let trimmedPassword = password?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
||||
if !trimmedPassword.isEmpty {
|
||||
return true
|
||||
}
|
||||
return storedOperatorScopes.contains("operator.approvals")
|
||||
}
|
||||
|
||||
func makeOperatorConnectOptions(
|
||||
clientId: String,
|
||||
displayName: String?,
|
||||
includeApprovalScope: Bool
|
||||
) -> GatewayConnectOptions {
|
||||
var scopes = ["operator.read", "operator.write", "operator.talk.secrets"]
|
||||
// Preserve reconnect compatibility for older paired operator tokens that were
|
||||
// approved before iOS requested operator.approvals by default.
|
||||
if includeApprovalScope {
|
||||
scopes.append("operator.approvals")
|
||||
}
|
||||
return GatewayConnectOptions(
|
||||
func makeOperatorConnectOptions(clientId: String, displayName: String?) -> GatewayConnectOptions {
|
||||
GatewayConnectOptions(
|
||||
role: "operator",
|
||||
scopes: scopes,
|
||||
scopes: ["operator.read", "operator.write", "operator.talk.secrets"],
|
||||
caps: [],
|
||||
commands: [],
|
||||
permissions: [:],
|
||||
@@ -3197,22 +3137,11 @@ extension NodeAppModel {
|
||||
await self.applyPendingForegroundNodeActions(mapped, trigger: "test")
|
||||
}
|
||||
|
||||
func _test_makeOperatorConnectOptions(
|
||||
clientId: String,
|
||||
displayName: String?,
|
||||
includeApprovalScope: Bool
|
||||
) -> GatewayConnectOptions {
|
||||
self.makeOperatorConnectOptions(
|
||||
clientId: clientId,
|
||||
displayName: displayName,
|
||||
includeApprovalScope: includeApprovalScope)
|
||||
}
|
||||
|
||||
static func _test_currentDeepLinkKey() -> String {
|
||||
self.expectedDeepLinkKey()
|
||||
}
|
||||
|
||||
nonisolated static func _test_shouldStartOperatorGatewayLoop(
|
||||
static func _test_shouldStartOperatorGatewayLoop(
|
||||
token: String?,
|
||||
bootstrapToken: String?,
|
||||
password: String?,
|
||||
@@ -3225,41 +3154,6 @@ extension NodeAppModel {
|
||||
hasStoredOperatorToken: hasStoredOperatorToken)
|
||||
}
|
||||
|
||||
nonisolated static func _test_shouldRequestOperatorApprovalScope(
|
||||
token: String?,
|
||||
password: String?,
|
||||
storedOperatorScopes: [String]
|
||||
) -> Bool {
|
||||
self.shouldRequestOperatorApprovalScope(
|
||||
token: token,
|
||||
password: password,
|
||||
storedOperatorScopes: storedOperatorScopes)
|
||||
}
|
||||
|
||||
nonisolated static func _test_clearingBootstrapToken(
|
||||
in config: GatewayConnectConfig?
|
||||
) -> GatewayConnectConfig? {
|
||||
self.clearingBootstrapToken(in: config)
|
||||
}
|
||||
|
||||
func _test_handleSuccessfulBootstrapGatewayOnboarding() async {
|
||||
await self.handleSuccessfulBootstrapGatewayOnboarding(
|
||||
url: URL(string: "wss://gateway.example")!,
|
||||
stableID: "test-gateway",
|
||||
token: nil,
|
||||
password: nil,
|
||||
nodeOptions: GatewayConnectOptions(
|
||||
role: "node",
|
||||
scopes: [],
|
||||
caps: [],
|
||||
commands: [],
|
||||
permissions: [:],
|
||||
clientId: "openclaw-ios",
|
||||
clientMode: "node",
|
||||
clientDisplayName: nil),
|
||||
sessionBox: nil)
|
||||
}
|
||||
|
||||
}
|
||||
#endif
|
||||
// swiftlint:enable type_body_length file_length
|
||||
|
||||
@@ -70,52 +70,6 @@ import UIKit
|
||||
}
|
||||
}
|
||||
|
||||
@Test @MainActor func operatorConnectOptionsOnlyRequestApprovalScopeWhenEnabled() {
|
||||
let appModel = NodeAppModel()
|
||||
let withoutApprovalScope = appModel._test_makeOperatorConnectOptions(
|
||||
clientId: "openclaw-ios",
|
||||
displayName: "OpenClaw iOS",
|
||||
includeApprovalScope: false)
|
||||
let withApprovalScope = appModel._test_makeOperatorConnectOptions(
|
||||
clientId: "openclaw-ios",
|
||||
displayName: "OpenClaw iOS",
|
||||
includeApprovalScope: true)
|
||||
|
||||
#expect(withoutApprovalScope.role == "operator")
|
||||
#expect(withoutApprovalScope.scopes.contains("operator.read"))
|
||||
#expect(withoutApprovalScope.scopes.contains("operator.write"))
|
||||
#expect(!withoutApprovalScope.scopes.contains("operator.approvals"))
|
||||
#expect(withoutApprovalScope.scopes.contains("operator.talk.secrets"))
|
||||
|
||||
#expect(withApprovalScope.scopes.contains("operator.approvals"))
|
||||
}
|
||||
|
||||
@Test func operatorApprovalScopeRequestsStayBackwardCompatible() {
|
||||
#expect(
|
||||
!NodeAppModel._test_shouldRequestOperatorApprovalScope(
|
||||
token: nil,
|
||||
password: nil,
|
||||
storedOperatorScopes: ["operator.read", "operator.write", "operator.talk.secrets"])
|
||||
)
|
||||
#expect(
|
||||
NodeAppModel._test_shouldRequestOperatorApprovalScope(
|
||||
token: nil,
|
||||
password: nil,
|
||||
storedOperatorScopes: [
|
||||
"operator.approvals",
|
||||
"operator.read",
|
||||
"operator.write",
|
||||
"operator.talk.secrets",
|
||||
])
|
||||
)
|
||||
#expect(
|
||||
NodeAppModel._test_shouldRequestOperatorApprovalScope(
|
||||
token: "shared-token",
|
||||
password: nil,
|
||||
storedOperatorScopes: [])
|
||||
)
|
||||
}
|
||||
|
||||
@Test @MainActor func loadLastConnectionReadsSavedValues() {
|
||||
let prior = KeychainStore.loadString(service: "ai.openclaw.gateway", account: "lastConnection")
|
||||
defer {
|
||||
|
||||
@@ -2,7 +2,6 @@ import OpenClawKit
|
||||
import Foundation
|
||||
import Testing
|
||||
import UIKit
|
||||
import UserNotifications
|
||||
@testable import OpenClaw
|
||||
|
||||
private func makeAgentDeepLinkURL(
|
||||
@@ -69,28 +68,6 @@ private final class MockWatchMessagingService: @preconcurrency WatchMessagingSer
|
||||
}
|
||||
}
|
||||
|
||||
private final class MockBootstrapNotificationCenter: NotificationCentering, @unchecked Sendable {
|
||||
var status: NotificationAuthorizationStatus = .notDetermined
|
||||
var requestAuthorizationResult = false
|
||||
var requestAuthorizationCalls = 0
|
||||
|
||||
func authorizationStatus() async -> NotificationAuthorizationStatus {
|
||||
self.status
|
||||
}
|
||||
|
||||
func requestAuthorization(options _: UNAuthorizationOptions) async throws -> Bool {
|
||||
self.requestAuthorizationCalls += 1
|
||||
if self.requestAuthorizationResult {
|
||||
self.status = .authorized
|
||||
} else {
|
||||
self.status = .denied
|
||||
}
|
||||
return self.requestAuthorizationResult
|
||||
}
|
||||
|
||||
func add(_: UNNotificationRequest) async throws {}
|
||||
}
|
||||
|
||||
@Suite(.serialized) struct NodeAppModelInvokeTests {
|
||||
@Test @MainActor func decodeParamsFailsWithoutJSON() {
|
||||
#expect(throws: Error.self) {
|
||||
@@ -150,15 +127,6 @@ private final class MockBootstrapNotificationCenter: NotificationCentering, @unc
|
||||
)
|
||||
}
|
||||
|
||||
@Test @MainActor func successfulBootstrapOnboardingRequestsNotificationAuthorization() async {
|
||||
let center = MockBootstrapNotificationCenter()
|
||||
let appModel = NodeAppModel(notificationCenter: center)
|
||||
|
||||
await appModel._test_handleSuccessfulBootstrapGatewayOnboarding()
|
||||
|
||||
#expect(center.requestAuthorizationCalls == 1)
|
||||
}
|
||||
|
||||
@Test func clearingBootstrapTokenStripsReconnectConfigEvenWithoutPersistence() {
|
||||
let config = GatewayConnectConfig(
|
||||
url: URL(string: "wss://gateway.example")!,
|
||||
@@ -177,7 +145,7 @@ private final class MockBootstrapNotificationCenter: NotificationCentering, @unc
|
||||
clientMode: "node",
|
||||
clientDisplayName: nil))
|
||||
|
||||
let cleared = NodeAppModel._test_clearingBootstrapToken(in: config)
|
||||
let cleared = NodeAppModel.clearingBootstrapToken(in: config)
|
||||
#expect(cleared?.bootstrapToken == nil)
|
||||
#expect(cleared?.url == config.url)
|
||||
#expect(cleared?.stableID == config.stableID)
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2026.4.4</string>
|
||||
<string>2026.4.3</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>2026040401</string>
|
||||
<string>2026040301</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>OpenClaw</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
|
||||
@@ -43,8 +43,8 @@ enum WideAreaGatewayDiscovery {
|
||||
guard let statusJson = context.tailscaleStatus(),
|
||||
!collectTailnetIPv4s(statusJson: statusJson).isEmpty,
|
||||
let discovery = loadWideAreaPtrRecords(
|
||||
remaining: remaining,
|
||||
dig: context.dig)
|
||||
remaining: remaining,
|
||||
dig: context.dig)
|
||||
else { return [] }
|
||||
|
||||
let domainTrimmed = discovery.domainTrimmed
|
||||
|
||||
@@ -542,77 +542,6 @@ public actor GatewayChannelActor {
|
||||
authSource: authSource)
|
||||
}
|
||||
|
||||
private func shouldPersistBootstrapHandoffTokens() -> Bool {
|
||||
guard self.lastAuthSource == .bootstrapToken else { return false }
|
||||
let scheme = self.url.scheme?.lowercased()
|
||||
if scheme == "wss" {
|
||||
return true
|
||||
}
|
||||
if let host = self.url.host, LoopbackHost.isLoopback(host) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private func filteredBootstrapHandoffScopes(role: String, scopes: [String]) -> [String]? {
|
||||
let normalizedRole = role.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
switch normalizedRole {
|
||||
case "node":
|
||||
return []
|
||||
case "operator":
|
||||
let allowedOperatorScopes: Set<String> = [
|
||||
"operator.approvals",
|
||||
"operator.read",
|
||||
"operator.talk.secrets",
|
||||
"operator.write",
|
||||
]
|
||||
return Array(Set(scopes.filter { allowedOperatorScopes.contains($0) })).sorted()
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
private func persistBootstrapHandoffToken(
|
||||
deviceId: String,
|
||||
role: String,
|
||||
token: String,
|
||||
scopes: [String]
|
||||
) {
|
||||
guard let filteredScopes = self.filteredBootstrapHandoffScopes(role: role, scopes: scopes) else {
|
||||
return
|
||||
}
|
||||
_ = DeviceAuthStore.storeToken(
|
||||
deviceId: deviceId,
|
||||
role: role,
|
||||
token: token,
|
||||
scopes: filteredScopes)
|
||||
}
|
||||
|
||||
private func persistIssuedDeviceToken(
|
||||
authSource: GatewayAuthSource,
|
||||
deviceId: String,
|
||||
role: String,
|
||||
token: String,
|
||||
scopes: [String]
|
||||
) {
|
||||
if authSource == .bootstrapToken {
|
||||
guard self.shouldPersistBootstrapHandoffTokens() else {
|
||||
return
|
||||
}
|
||||
self.persistBootstrapHandoffToken(
|
||||
deviceId: deviceId,
|
||||
role: role,
|
||||
token: token,
|
||||
scopes: scopes)
|
||||
return
|
||||
}
|
||||
_ = DeviceAuthStore.storeToken(
|
||||
deviceId: deviceId,
|
||||
role: role,
|
||||
token: token,
|
||||
scopes: scopes)
|
||||
}
|
||||
|
||||
private func handleConnectResponse(
|
||||
_ res: ResponseFrame,
|
||||
identity: DeviceIdentity?,
|
||||
@@ -643,37 +572,18 @@ public actor GatewayChannelActor {
|
||||
} else if let tick = ok.policy["tickIntervalMs"]?.value as? Int {
|
||||
self.tickIntervalMs = Double(tick)
|
||||
}
|
||||
if let auth = ok.auth, let identity {
|
||||
if let deviceToken = auth["deviceToken"]?.value as? String {
|
||||
let authRole = auth["role"]?.value as? String ?? role
|
||||
let scopes = (auth["scopes"]?.value as? [ProtoAnyCodable])?
|
||||
.compactMap { $0.value as? String } ?? []
|
||||
self.persistIssuedDeviceToken(
|
||||
authSource: self.lastAuthSource,
|
||||
if let auth = ok.auth,
|
||||
let deviceToken = auth["deviceToken"]?.value as? String {
|
||||
let authRole = auth["role"]?.value as? String ?? role
|
||||
let scopes = (auth["scopes"]?.value as? [ProtoAnyCodable])?
|
||||
.compactMap { $0.value as? String } ?? []
|
||||
if let identity {
|
||||
_ = DeviceAuthStore.storeToken(
|
||||
deviceId: identity.deviceId,
|
||||
role: authRole,
|
||||
token: deviceToken,
|
||||
scopes: scopes)
|
||||
}
|
||||
if self.shouldPersistBootstrapHandoffTokens(),
|
||||
let tokenEntries = auth["deviceTokens"]?.value as? [ProtoAnyCodable]
|
||||
{
|
||||
for entry in tokenEntries {
|
||||
guard let rawEntry = entry.value as? [String: ProtoAnyCodable],
|
||||
let deviceToken = rawEntry["deviceToken"]?.value as? String,
|
||||
let authRole = rawEntry["role"]?.value as? String
|
||||
else {
|
||||
continue
|
||||
}
|
||||
let scopes = (rawEntry["scopes"]?.value as? [ProtoAnyCodable])?
|
||||
.compactMap { $0.value as? String } ?? []
|
||||
self.persistBootstrapHandoffToken(
|
||||
deviceId: identity.deviceId,
|
||||
role: authRole,
|
||||
token: deviceToken,
|
||||
scopes: scopes)
|
||||
}
|
||||
}
|
||||
}
|
||||
self.lastTick = Date()
|
||||
self.tickTask?.cancel()
|
||||
|
||||
@@ -13,7 +13,6 @@ private extension NSLock {
|
||||
|
||||
private final class FakeGatewayWebSocketTask: WebSocketTasking, @unchecked Sendable {
|
||||
private let lock = NSLock()
|
||||
private let helloAuth: [String: Any]?
|
||||
private var _state: URLSessionTask.State = .suspended
|
||||
private var connectRequestId: String?
|
||||
private var connectAuth: [String: Any]?
|
||||
@@ -21,10 +20,6 @@ private final class FakeGatewayWebSocketTask: WebSocketTasking, @unchecked Senda
|
||||
private var pendingReceiveHandler:
|
||||
(@Sendable (Result<URLSessionWebSocketTask.Message, Error>) -> Void)?
|
||||
|
||||
init(helloAuth: [String: Any]? = nil) {
|
||||
self.helloAuth = helloAuth
|
||||
}
|
||||
|
||||
var state: URLSessionTask.State {
|
||||
get { self.lock.withLock { self._state } }
|
||||
set { self.lock.withLock { self._state = newValue } }
|
||||
@@ -84,11 +79,11 @@ private final class FakeGatewayWebSocketTask: WebSocketTasking, @unchecked Senda
|
||||
for _ in 0..<50 {
|
||||
let id = self.lock.withLock { self.connectRequestId }
|
||||
if let id {
|
||||
return .data(Self.connectOkData(id: id, auth: self.helloAuth))
|
||||
return .data(Self.connectOkData(id: id))
|
||||
}
|
||||
try await Task.sleep(nanoseconds: 1_000_000)
|
||||
}
|
||||
return .data(Self.connectOkData(id: "connect", auth: self.helloAuth))
|
||||
return .data(Self.connectOkData(id: "connect"))
|
||||
}
|
||||
|
||||
func receive(
|
||||
@@ -115,8 +110,8 @@ private final class FakeGatewayWebSocketTask: WebSocketTasking, @unchecked Senda
|
||||
return (try? JSONSerialization.data(withJSONObject: frame)) ?? Data()
|
||||
}
|
||||
|
||||
private static func connectOkData(id: String, auth: [String: Any]? = nil) -> Data {
|
||||
var payload: [String: Any] = [
|
||||
private static func connectOkData(id: String) -> Data {
|
||||
let payload: [String: Any] = [
|
||||
"type": "hello-ok",
|
||||
"protocol": 2,
|
||||
"server": [
|
||||
@@ -142,9 +137,6 @@ private final class FakeGatewayWebSocketTask: WebSocketTasking, @unchecked Senda
|
||||
"tickIntervalMs": 30_000,
|
||||
],
|
||||
]
|
||||
if let auth {
|
||||
payload["auth"] = auth
|
||||
}
|
||||
let frame: [String: Any] = [
|
||||
"type": "res",
|
||||
"id": id,
|
||||
@@ -157,14 +149,9 @@ private final class FakeGatewayWebSocketTask: WebSocketTasking, @unchecked Senda
|
||||
|
||||
private final class FakeGatewayWebSocketSession: WebSocketSessioning, @unchecked Sendable {
|
||||
private let lock = NSLock()
|
||||
private let helloAuth: [String: Any]?
|
||||
private var tasks: [FakeGatewayWebSocketTask] = []
|
||||
private var makeCount = 0
|
||||
|
||||
init(helloAuth: [String: Any]? = nil) {
|
||||
self.helloAuth = helloAuth
|
||||
}
|
||||
|
||||
func snapshotMakeCount() -> Int {
|
||||
self.lock.withLock { self.makeCount }
|
||||
}
|
||||
@@ -177,7 +164,7 @@ private final class FakeGatewayWebSocketSession: WebSocketSessioning, @unchecked
|
||||
_ = url
|
||||
return self.lock.withLock {
|
||||
self.makeCount += 1
|
||||
let task = FakeGatewayWebSocketTask(helloAuth: self.helloAuth)
|
||||
let task = FakeGatewayWebSocketTask()
|
||||
self.tasks.append(task)
|
||||
return WebSocketTaskBox(task: task)
|
||||
}
|
||||
@@ -190,7 +177,6 @@ private actor SeqGapProbe {
|
||||
func value() -> Bool { self.saw }
|
||||
}
|
||||
|
||||
@Suite(.serialized)
|
||||
struct GatewayNodeSessionTests {
|
||||
@Test
|
||||
func scannedSetupCodePrefersBootstrapAuthOverStoredDeviceToken() async throws {
|
||||
@@ -248,204 +234,6 @@ struct GatewayNodeSessionTests {
|
||||
await gateway.disconnect()
|
||||
}
|
||||
|
||||
@Test
|
||||
func bootstrapHelloStoresAdditionalDeviceTokens() async throws {
|
||||
let tempDir = FileManager.default.temporaryDirectory
|
||||
.appendingPathComponent(UUID().uuidString, isDirectory: true)
|
||||
try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)
|
||||
let previousStateDir = ProcessInfo.processInfo.environment["OPENCLAW_STATE_DIR"]
|
||||
setenv("OPENCLAW_STATE_DIR", tempDir.path, 1)
|
||||
defer {
|
||||
if let previousStateDir {
|
||||
setenv("OPENCLAW_STATE_DIR", previousStateDir, 1)
|
||||
} else {
|
||||
unsetenv("OPENCLAW_STATE_DIR")
|
||||
}
|
||||
try? FileManager.default.removeItem(at: tempDir)
|
||||
}
|
||||
|
||||
let identity = DeviceIdentityStore.loadOrCreate()
|
||||
let session = FakeGatewayWebSocketSession(helloAuth: [
|
||||
"deviceToken": "node-device-token",
|
||||
"role": "node",
|
||||
"scopes": [],
|
||||
"issuedAtMs": 1000,
|
||||
"deviceTokens": [
|
||||
[
|
||||
"deviceToken": "operator-device-token",
|
||||
"role": "operator",
|
||||
"scopes": [
|
||||
"operator.admin",
|
||||
"operator.approvals",
|
||||
"operator.read",
|
||||
"operator.talk.secrets",
|
||||
"operator.write",
|
||||
],
|
||||
"issuedAtMs": 1001,
|
||||
],
|
||||
],
|
||||
])
|
||||
let gateway = GatewayNodeSession()
|
||||
let options = GatewayConnectOptions(
|
||||
role: "node",
|
||||
scopes: [],
|
||||
caps: [],
|
||||
commands: [],
|
||||
permissions: [:],
|
||||
clientId: "openclaw-ios-test",
|
||||
clientMode: "node",
|
||||
clientDisplayName: "iOS Test",
|
||||
includeDeviceIdentity: true)
|
||||
|
||||
try await gateway.connect(
|
||||
url: URL(string: "wss://example.invalid")!,
|
||||
token: nil,
|
||||
bootstrapToken: "fresh-bootstrap-token",
|
||||
password: nil,
|
||||
connectOptions: options,
|
||||
sessionBox: WebSocketSessionBox(session: session),
|
||||
onConnected: {},
|
||||
onDisconnected: { _ in },
|
||||
onInvoke: { req in
|
||||
BridgeInvokeResponse(id: req.id, ok: true, payloadJSON: nil, error: nil)
|
||||
})
|
||||
|
||||
let nodeEntry = try #require(DeviceAuthStore.loadToken(deviceId: identity.deviceId, role: "node"))
|
||||
let operatorEntry = try #require(DeviceAuthStore.loadToken(deviceId: identity.deviceId, role: "operator"))
|
||||
#expect(nodeEntry.token == "node-device-token")
|
||||
#expect(nodeEntry.scopes == [])
|
||||
#expect(operatorEntry.token == "operator-device-token")
|
||||
#expect(operatorEntry.scopes.contains("operator.approvals"))
|
||||
#expect(!operatorEntry.scopes.contains("operator.admin"))
|
||||
|
||||
await gateway.disconnect()
|
||||
}
|
||||
|
||||
@Test
|
||||
func nonBootstrapHelloStoresPrimaryDeviceTokenButNotAdditionalBootstrapTokens() async throws {
|
||||
let tempDir = FileManager.default.temporaryDirectory
|
||||
.appendingPathComponent(UUID().uuidString, isDirectory: true)
|
||||
try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)
|
||||
let previousStateDir = ProcessInfo.processInfo.environment["OPENCLAW_STATE_DIR"]
|
||||
setenv("OPENCLAW_STATE_DIR", tempDir.path, 1)
|
||||
defer {
|
||||
if let previousStateDir {
|
||||
setenv("OPENCLAW_STATE_DIR", previousStateDir, 1)
|
||||
} else {
|
||||
unsetenv("OPENCLAW_STATE_DIR")
|
||||
}
|
||||
try? FileManager.default.removeItem(at: tempDir)
|
||||
}
|
||||
|
||||
let identity = DeviceIdentityStore.loadOrCreate()
|
||||
let session = FakeGatewayWebSocketSession(helloAuth: [
|
||||
"deviceToken": "server-node-token",
|
||||
"role": "node",
|
||||
"scopes": [],
|
||||
"deviceTokens": [
|
||||
[
|
||||
"deviceToken": "server-operator-token",
|
||||
"role": "operator",
|
||||
"scopes": ["operator.admin"],
|
||||
],
|
||||
],
|
||||
])
|
||||
let gateway = GatewayNodeSession()
|
||||
let options = GatewayConnectOptions(
|
||||
role: "node",
|
||||
scopes: [],
|
||||
caps: [],
|
||||
commands: [],
|
||||
permissions: [:],
|
||||
clientId: "openclaw-ios-test",
|
||||
clientMode: "node",
|
||||
clientDisplayName: "iOS Test",
|
||||
includeDeviceIdentity: true)
|
||||
|
||||
try await gateway.connect(
|
||||
url: URL(string: "wss://example.invalid")!,
|
||||
token: "shared-token",
|
||||
bootstrapToken: nil,
|
||||
password: nil,
|
||||
connectOptions: options,
|
||||
sessionBox: WebSocketSessionBox(session: session),
|
||||
onConnected: {},
|
||||
onDisconnected: { _ in },
|
||||
onInvoke: { req in
|
||||
BridgeInvokeResponse(id: req.id, ok: true, payloadJSON: nil, error: nil)
|
||||
})
|
||||
|
||||
let nodeEntry = try #require(DeviceAuthStore.loadToken(deviceId: identity.deviceId, role: "node"))
|
||||
#expect(nodeEntry.token == "server-node-token")
|
||||
#expect(nodeEntry.scopes == [])
|
||||
#expect(DeviceAuthStore.loadToken(deviceId: identity.deviceId, role: "operator") == nil)
|
||||
|
||||
await gateway.disconnect()
|
||||
}
|
||||
|
||||
@Test
|
||||
func untrustedBootstrapHelloDoesNotPersistBootstrapHandoffTokens() async throws {
|
||||
let tempDir = FileManager.default.temporaryDirectory
|
||||
.appendingPathComponent(UUID().uuidString, isDirectory: true)
|
||||
try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)
|
||||
let previousStateDir = ProcessInfo.processInfo.environment["OPENCLAW_STATE_DIR"]
|
||||
setenv("OPENCLAW_STATE_DIR", tempDir.path, 1)
|
||||
defer {
|
||||
if let previousStateDir {
|
||||
setenv("OPENCLAW_STATE_DIR", previousStateDir, 1)
|
||||
} else {
|
||||
unsetenv("OPENCLAW_STATE_DIR")
|
||||
}
|
||||
try? FileManager.default.removeItem(at: tempDir)
|
||||
}
|
||||
|
||||
let identity = DeviceIdentityStore.loadOrCreate()
|
||||
let session = FakeGatewayWebSocketSession(helloAuth: [
|
||||
"deviceToken": "untrusted-node-token",
|
||||
"role": "node",
|
||||
"scopes": [],
|
||||
"deviceTokens": [
|
||||
[
|
||||
"deviceToken": "untrusted-operator-token",
|
||||
"role": "operator",
|
||||
"scopes": [
|
||||
"operator.approvals",
|
||||
"operator.read",
|
||||
],
|
||||
],
|
||||
],
|
||||
])
|
||||
let gateway = GatewayNodeSession()
|
||||
let options = GatewayConnectOptions(
|
||||
role: "node",
|
||||
scopes: [],
|
||||
caps: [],
|
||||
commands: [],
|
||||
permissions: [:],
|
||||
clientId: "openclaw-ios-test",
|
||||
clientMode: "node",
|
||||
clientDisplayName: "iOS Test",
|
||||
includeDeviceIdentity: true)
|
||||
|
||||
try await gateway.connect(
|
||||
url: URL(string: "ws://example.invalid")!,
|
||||
token: nil,
|
||||
bootstrapToken: "fresh-bootstrap-token",
|
||||
password: nil,
|
||||
connectOptions: options,
|
||||
sessionBox: WebSocketSessionBox(session: session),
|
||||
onConnected: {},
|
||||
onDisconnected: { _ in },
|
||||
onInvoke: { req in
|
||||
BridgeInvokeResponse(id: req.id, ok: true, payloadJSON: nil, error: nil)
|
||||
})
|
||||
|
||||
#expect(DeviceAuthStore.loadToken(deviceId: identity.deviceId, role: "node") == nil)
|
||||
#expect(DeviceAuthStore.loadToken(deviceId: identity.deviceId, role: "operator") == nil)
|
||||
|
||||
await gateway.disconnect()
|
||||
}
|
||||
|
||||
@Test
|
||||
func normalizeCanvasHostUrlPreservesExplicitSecureCanvasPort() {
|
||||
let normalized = canonicalizeCanvasHostUrl(
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import XCTest
|
||||
@testable import OpenClawKit
|
||||
|
||||
@MainActor
|
||||
final class TalkSystemSpeechSynthesizerTests: XCTestCase {
|
||||
func testWatchdogTimeoutDefaultsToLatinProfile() {
|
||||
let timeout = TalkSystemSpeechSynthesizer.watchdogTimeoutSeconds(
|
||||
|
||||
@@ -1,21 +1,14 @@
|
||||
# Generated Docs Artifacts
|
||||
|
||||
SHA-256 hash files are the tracked drift-detection artifacts. The full JSON
|
||||
baselines are generated locally (gitignored) for inspection only.
|
||||
These baseline artifacts are generated from the repo-owned OpenClaw config schema and bundled channel/plugin metadata.
|
||||
|
||||
**Tracked (committed to git):**
|
||||
|
||||
- `config-baseline.sha256` — hashes of config baseline JSON artifacts.
|
||||
- `plugin-sdk-api-baseline.sha256` — hashes of Plugin SDK API baseline artifacts.
|
||||
|
||||
**Local only (gitignored):**
|
||||
|
||||
- `config-baseline.json`, `config-baseline.core.json`, `config-baseline.channel.json`, `config-baseline.plugin.json`
|
||||
- `plugin-sdk-api-baseline.json`, `plugin-sdk-api-baseline.jsonl`
|
||||
|
||||
Do not edit any of these files by hand.
|
||||
|
||||
- Regenerate config baseline: `pnpm config:docs:gen`
|
||||
- Validate config baseline: `pnpm config:docs:check`
|
||||
- Regenerate Plugin SDK API baseline: `pnpm plugin-sdk:api:gen`
|
||||
- Validate Plugin SDK API baseline: `pnpm plugin-sdk:api:check`
|
||||
- Do not edit `config-baseline.json` by hand.
|
||||
- Do not edit `config-baseline.core.json` by hand.
|
||||
- Do not edit `config-baseline.channel.json` by hand.
|
||||
- Do not edit `config-baseline.plugin.json` by hand.
|
||||
- Do not edit `plugin-sdk-api-baseline.json` by hand.
|
||||
- Do not edit `plugin-sdk-api-baseline.jsonl` by hand.
|
||||
- Regenerate config baseline artifacts with `pnpm config:docs:gen`.
|
||||
- Validate config baseline artifacts in CI or locally with `pnpm config:docs:check`.
|
||||
- Regenerate Plugin SDK API baseline artifacts with `pnpm plugin-sdk:api:gen`.
|
||||
- Validate Plugin SDK API baseline artifacts in CI or locally with `pnpm plugin-sdk:api:check`.
|
||||
|
||||
31578
docs/.generated/config-baseline.channel.json
Normal file
31578
docs/.generated/config-baseline.channel.json
Normal file
File diff suppressed because it is too large
Load Diff
28035
docs/.generated/config-baseline.core.json
Normal file
28035
docs/.generated/config-baseline.core.json
Normal file
File diff suppressed because it is too large
Load Diff
73617
docs/.generated/config-baseline.json
Normal file
73617
docs/.generated/config-baseline.json
Normal file
File diff suppressed because it is too large
Load Diff
14013
docs/.generated/config-baseline.plugin.json
Normal file
14013
docs/.generated/config-baseline.plugin.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,4 +0,0 @@
|
||||
ad87e3ff267b151ae163402f3cb52503e10641e332bcfbb6a574bbd7087a2484 config-baseline.json
|
||||
03ff4a3e314f17dd8851aed3653269294bc62412bee05a6804dce840bd3d7551 config-baseline.core.json
|
||||
73b57f395a2ad983f1660112d0b2b998342f1ddbe3089b440d7f73d0665de739 config-baseline.channel.json
|
||||
9d5cb864e70768b66c1ecd881a9a584b7696ef2e5b32df686cfdc3fa21ddabbe config-baseline.plugin.json
|
||||
7423
docs/.generated/plugin-sdk-api-baseline.json
Normal file
7423
docs/.generated/plugin-sdk-api-baseline.json
Normal file
File diff suppressed because one or more lines are too long
820
docs/.generated/plugin-sdk-api-baseline.jsonl
Normal file
820
docs/.generated/plugin-sdk-api-baseline.jsonl
Normal file
File diff suppressed because one or more lines are too long
@@ -1,2 +0,0 @@
|
||||
80a8238588cca3c6e0f89115f4271834b6e18f3497f70bc91dccc9203539cdd9 plugin-sdk-api-baseline.json
|
||||
68ed97a285d82f995f377db7823104976e8ebab5c6d0750332acb1d3d79288ef plugin-sdk-api-baseline.jsonl
|
||||
@@ -47,26 +47,6 @@
|
||||
"source": "Quick Start",
|
||||
"target": "快速开始"
|
||||
},
|
||||
{
|
||||
"source": "Chutes",
|
||||
"target": "Chutes"
|
||||
},
|
||||
{
|
||||
"source": "Qwen",
|
||||
"target": "Qwen"
|
||||
},
|
||||
{
|
||||
"source": "BytePlus (International)",
|
||||
"target": "BytePlus(国际版)"
|
||||
},
|
||||
{
|
||||
"source": "Moonshot AI",
|
||||
"target": "Moonshot AI"
|
||||
},
|
||||
{
|
||||
"source": "Additional bundled variants",
|
||||
"target": "其他内置变体"
|
||||
},
|
||||
{
|
||||
"source": "Diffs",
|
||||
"target": "Diffs"
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#111827" aria-hidden="true">
|
||||
<path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 829 B |
@@ -1,3 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#fff" aria-hidden="true">
|
||||
<path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 826 B |
@@ -37,11 +37,6 @@ openclaw cron runs --id <job-id>
|
||||
- All cron executions create [background task](/automation/tasks) records.
|
||||
- One-shot jobs (`--at`) auto-delete after success by default.
|
||||
|
||||
Task reconciliation for cron is runtime-owned: an active cron task stays live while the
|
||||
cron runtime still tracks that job as running, even if an old child session row still exists.
|
||||
Once the runtime stops owning the job and the 5-minute grace window expires, maintenance can
|
||||
mark the task `lost`.
|
||||
|
||||
## Schedule types
|
||||
|
||||
| Kind | CLI flag | Description |
|
||||
@@ -168,7 +163,7 @@ Run an isolated agent turn:
|
||||
curl -X POST http://127.0.0.1:18789/hooks/agent \
|
||||
-H 'Authorization: Bearer SECRET' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"message":"Summarize inbox","name":"Email","model":"openai/gpt-5.4-mini"}'
|
||||
-d '{"message":"Summarize inbox","name":"Email","model":"openai/gpt-5.2-mini"}'
|
||||
```
|
||||
|
||||
Fields: `message` (required), `name`, `agentId`, `wakeMode`, `deliver`, `channel`, `to`, `model`, `thinking`, `timeoutSeconds`.
|
||||
@@ -318,7 +313,7 @@ openclaw doctor
|
||||
- Check `cron.enabled` and `OPENCLAW_SKIP_CRON` env var.
|
||||
- Confirm the Gateway is running continuously.
|
||||
- For `cron` schedules, verify timezone (`--tz`) vs the host timezone.
|
||||
- `reason: not-due` in run output means manual run was checked with `openclaw cron run <jobId> --due` and the job was not due yet.
|
||||
- `reason: not-due` in run output means manual run called without `--force`.
|
||||
|
||||
### Cron fired but no delivery
|
||||
|
||||
|
||||
@@ -25,7 +25,6 @@ Not every agent run creates a task. Heartbeat turns and normal interactive chat
|
||||
- Tasks are **records**, not schedulers — cron and heartbeat decide _when_ work runs, tasks track _what happened_.
|
||||
- ACP, subagents, all cron jobs, and CLI operations create tasks. Heartbeat turns do not.
|
||||
- Each task moves through `queued → running → terminal` (succeeded, failed, timed_out, cancelled, or lost).
|
||||
- Cron tasks stay live while the cron runtime still owns the job; chat-backed CLI tasks stay live only while their owning run context is still active.
|
||||
- Completion notifications are delivered directly to a channel or queued for the next heartbeat.
|
||||
- `openclaw tasks list` shows all tasks; `openclaw tasks audit` surfaces issues.
|
||||
- Terminal records are kept for 7 days, then automatically pruned.
|
||||
@@ -51,15 +50,6 @@ openclaw tasks notify <lookup> state_changes
|
||||
|
||||
# Run a health audit
|
||||
openclaw tasks audit
|
||||
|
||||
# Preview or apply maintenance
|
||||
openclaw tasks maintenance
|
||||
openclaw tasks maintenance --apply
|
||||
|
||||
# Inspect TaskFlow state
|
||||
openclaw tasks flow list
|
||||
openclaw tasks flow show <lookup>
|
||||
openclaw tasks flow cancel <lookup>
|
||||
```
|
||||
|
||||
## What creates a task
|
||||
@@ -101,17 +91,10 @@ stateDiagram-v2
|
||||
| `failed` | Completed with an error |
|
||||
| `timed_out` | Exceeded the configured timeout |
|
||||
| `cancelled` | Stopped by the operator via `openclaw tasks cancel` |
|
||||
| `lost` | The runtime lost authoritative backing state after a 5-minute grace period |
|
||||
| `lost` | Backing child session disappeared (detected after a 5-minute grace period) |
|
||||
|
||||
Transitions happen automatically — when the associated agent run ends, the task status updates to match.
|
||||
|
||||
`lost` is runtime-aware:
|
||||
|
||||
- ACP tasks: backing ACP child session metadata disappeared.
|
||||
- Subagent tasks: backing child session disappeared from the target agent store.
|
||||
- Cron tasks: the cron runtime no longer tracks the job as active.
|
||||
- CLI tasks: isolated child-session tasks use the child session; chat-backed CLI tasks use the live run context instead, so lingering channel/group/direct session rows do not keep them alive.
|
||||
|
||||
## Delivery and notifications
|
||||
|
||||
When a task reaches a terminal state, OpenClaw notifies you. There are two delivery paths:
|
||||
@@ -184,38 +167,11 @@ Surfaces operational issues. Findings also appear in `openclaw status` when issu
|
||||
| ------------------------- | -------- | ----------------------------------------------------- |
|
||||
| `stale_queued` | warn | Queued for more than 10 minutes |
|
||||
| `stale_running` | error | Running for more than 30 minutes |
|
||||
| `lost` | error | Runtime-backed task ownership disappeared |
|
||||
| `lost` | error | Backing session is gone |
|
||||
| `delivery_failed` | warn | Delivery failed and notify policy is not `silent` |
|
||||
| `missing_cleanup` | warn | Terminal task with no cleanup timestamp |
|
||||
| `inconsistent_timestamps` | warn | Timeline violation (for example ended before started) |
|
||||
|
||||
### `tasks maintenance`
|
||||
|
||||
```bash
|
||||
openclaw tasks maintenance [--json]
|
||||
openclaw tasks maintenance --apply [--json]
|
||||
```
|
||||
|
||||
Use this to preview or apply reconciliation, cleanup stamping, and pruning for
|
||||
tasks and Task Flow state.
|
||||
|
||||
Reconciliation is runtime-aware:
|
||||
|
||||
- ACP/subagent tasks check their backing child session.
|
||||
- Cron tasks check whether the cron runtime still owns the job.
|
||||
- Chat-backed CLI tasks check the owning live run context, not just the chat session row.
|
||||
|
||||
### `tasks flow list|show|cancel`
|
||||
|
||||
```bash
|
||||
openclaw tasks flow list [--status <status>] [--json]
|
||||
openclaw tasks flow show <lookup> [--json]
|
||||
openclaw tasks flow cancel <lookup>
|
||||
```
|
||||
|
||||
Use these when the orchestrating Task Flow is the thing you care about rather
|
||||
than one individual background task record.
|
||||
|
||||
## Chat task board (`/tasks`)
|
||||
|
||||
Use `/tasks` in any chat session to see background tasks linked to that session. The board shows
|
||||
@@ -260,7 +216,7 @@ The registry loads into memory at gateway start and syncs writes to SQLite for d
|
||||
|
||||
A sweeper runs every **60 seconds** and handles three things:
|
||||
|
||||
1. **Reconciliation** — checks whether active tasks still have authoritative runtime backing. ACP/subagent tasks use child-session state, cron tasks use active-job ownership, and chat-backed CLI tasks use the owning run context. If that backing state is gone for more than 5 minutes, the task is marked `lost`.
|
||||
1. **Reconciliation** — checks if active tasks' backing sessions still exist. If a child session has been gone for more than 5 minutes, the task is marked `lost`.
|
||||
2. **Cleanup stamping** — sets a `cleanupAfter` timestamp on terminal tasks (endedAt + 7 days).
|
||||
3. **Pruning** — deletes records past their `cleanupAfter` date.
|
||||
|
||||
|
||||
@@ -319,9 +319,7 @@ Verbose restore diagnostics:
|
||||
openclaw matrix verify backup restore --verbose
|
||||
```
|
||||
|
||||
Delete the current server backup and create a fresh backup baseline. If the stored
|
||||
backup key cannot be loaded cleanly, this reset can also recreate secret storage so
|
||||
future cold starts can load the new backup key:
|
||||
Delete the current server backup and create a fresh backup baseline:
|
||||
|
||||
```bash
|
||||
openclaw matrix verify backup reset --yes
|
||||
@@ -368,11 +366,8 @@ If the homeserver requires interactive auth to upload cross-signing keys, OpenCl
|
||||
|
||||
Use `--force-reset-cross-signing` only when you intentionally want to discard the current cross-signing identity and create a new one.
|
||||
|
||||
If you intentionally want to discard the current room-key backup and start a new
|
||||
backup baseline for future messages, use `openclaw matrix verify backup reset --yes`.
|
||||
Do this only when you accept that unrecoverable old encrypted history will stay
|
||||
unavailable and that OpenClaw may recreate secret storage if the current backup
|
||||
secret cannot be loaded safely.
|
||||
If you intentionally want to discard the current room-key backup and start a new backup baseline for future messages, use `openclaw matrix verify backup reset --yes`.
|
||||
Do this only when you accept that unrecoverable old encrypted history will stay unavailable.
|
||||
|
||||
### Fresh backup baseline
|
||||
|
||||
|
||||
@@ -100,7 +100,7 @@ Stored under `~/.openclaw/devices/`:
|
||||
|
||||
### Notes
|
||||
|
||||
- The legacy `node.pair.*` API (CLI: `openclaw nodes pending|approve|reject|rename`) is a
|
||||
- The legacy `node.pair.*` API (CLI: `openclaw nodes pending/approve`) is a
|
||||
separate gateway-owned pairing store. WS nodes still require device pairing.
|
||||
|
||||
## Related docs
|
||||
|
||||
@@ -218,19 +218,6 @@ For actions/directory reads, user token can be preferred when configured. For wr
|
||||
- if encoded option values exceed Slack limits, the flow falls back to buttons
|
||||
- For long option payloads, Slash command argument menus use a confirm dialog before dispatching a selected value.
|
||||
|
||||
Default slash command settings:
|
||||
|
||||
- `enabled: false`
|
||||
- `name: "openclaw"`
|
||||
- `sessionPrefix: "slack:slash"`
|
||||
- `ephemeral: true`
|
||||
|
||||
Slash sessions use isolated keys:
|
||||
|
||||
- `agent:<agentId>:slack:slash:<userId>`
|
||||
|
||||
and still route command execution against the target conversation session (`CommandTargetSessionKey`).
|
||||
|
||||
## Interactive replies
|
||||
|
||||
Slack can render agent-authored interactive reply controls, but this feature is disabled by default.
|
||||
@@ -280,6 +267,19 @@ Notes:
|
||||
- The interactive callback values are OpenClaw-generated opaque tokens, not raw agent-authored values.
|
||||
- If generated interactive blocks would exceed Slack Block Kit limits, OpenClaw falls back to the original text reply instead of sending an invalid blocks payload.
|
||||
|
||||
Default slash command settings:
|
||||
|
||||
- `enabled: false`
|
||||
- `name: "openclaw"`
|
||||
- `sessionPrefix: "slack:slash"`
|
||||
- `ephemeral: true`
|
||||
|
||||
Slash sessions use isolated keys:
|
||||
|
||||
- `agent:<agentId>:slack:slash:<userId>`
|
||||
|
||||
and still route command execution against the target conversation session (`CommandTargetSessionKey`).
|
||||
|
||||
## Threading, sessions, and reply tags
|
||||
|
||||
- DMs route as `direct`; channels as `channel`; MPIMs as `group`.
|
||||
|
||||
@@ -21,24 +21,6 @@ If you want an external MCP client to talk directly to OpenClaw channel
|
||||
conversations instead of hosting an ACP harness session, use
|
||||
[`openclaw mcp serve`](/cli/mcp) instead.
|
||||
|
||||
## What this is not
|
||||
|
||||
This page is often confused with ACP harness sessions.
|
||||
|
||||
`openclaw acp` means:
|
||||
|
||||
- OpenClaw acts as an ACP server
|
||||
- an IDE or ACP client connects to OpenClaw
|
||||
- OpenClaw forwards that work into a Gateway session
|
||||
|
||||
This is different from [ACP Agents](/tools/acp-agents), where OpenClaw runs an
|
||||
external harness such as Codex or Claude Code through `acpx`.
|
||||
|
||||
Quick rule:
|
||||
|
||||
- editor/client wants to talk ACP to OpenClaw: use `openclaw acp`
|
||||
- OpenClaw should launch Codex/Claude/Gemini as an ACP harness: use `/acp spawn` and [ACP Agents](/tools/acp-agents)
|
||||
|
||||
## Compatibility Matrix
|
||||
|
||||
| ACP area | Status | Notes |
|
||||
@@ -293,7 +275,6 @@ Learn more about session keys at [/concepts/session](/concepts/session).
|
||||
- `--require-existing`: fail if the session key/label does not exist.
|
||||
- `--reset-session`: reset the session key before first use.
|
||||
- `--no-prefix-cwd`: do not prefix prompts with the working directory.
|
||||
- `--provenance <off|meta|meta+receipt>`: include ACP provenance metadata or receipts.
|
||||
- `--verbose, -v`: verbose logging to stderr.
|
||||
|
||||
Security note:
|
||||
|
||||
@@ -10,48 +10,20 @@ title: "agent"
|
||||
Run an agent turn via the Gateway (use `--local` for embedded).
|
||||
Use `--agent <id>` to target a configured agent directly.
|
||||
|
||||
Pass at least one session selector:
|
||||
|
||||
- `--to <dest>`
|
||||
- `--session-id <id>`
|
||||
- `--agent <id>`
|
||||
|
||||
Related:
|
||||
|
||||
- Agent send tool: [Agent send](/tools/agent-send)
|
||||
|
||||
## Options
|
||||
|
||||
- `-m, --message <text>`: required message body
|
||||
- `-t, --to <dest>`: recipient used to derive the session key
|
||||
- `--session-id <id>`: explicit session id
|
||||
- `--agent <id>`: agent id; overrides routing bindings
|
||||
- `--thinking <off|minimal|low|medium|high|xhigh>`: agent thinking level
|
||||
- `--verbose <on|off>`: persist verbose level for the session
|
||||
- `--channel <channel>`: delivery channel; omit to use the main session channel
|
||||
- `--reply-to <target>`: delivery target override
|
||||
- `--reply-channel <channel>`: delivery channel override
|
||||
- `--reply-account <id>`: delivery account override
|
||||
- `--local`: run the embedded agent directly (after plugin registry preload)
|
||||
- `--deliver`: send the reply back to the selected channel/target
|
||||
- `--timeout <seconds>`: override agent timeout (default 600 or config value)
|
||||
- `--json`: output JSON
|
||||
|
||||
## Examples
|
||||
|
||||
```bash
|
||||
openclaw agent --to +15555550123 --message "status update" --deliver
|
||||
openclaw agent --agent ops --message "Summarize logs"
|
||||
openclaw agent --session-id 1234 --message "Summarize inbox" --thinking medium
|
||||
openclaw agent --to +15555550123 --message "Trace logs" --verbose on --json
|
||||
openclaw agent --agent ops --message "Generate report" --deliver --reply-channel slack --reply-to "#reports"
|
||||
openclaw agent --agent ops --message "Run locally" --local
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- Gateway mode falls back to the embedded agent when the Gateway request fails. Use `--local` to force embedded execution up front.
|
||||
- `--local` still preloads the plugin registry first, so plugin-provided providers, tools, and channels stay available during embedded runs.
|
||||
- `--channel`, `--reply-channel`, and `--reply-account` affect reply delivery, not session routing.
|
||||
- When this command triggers `models.json` regeneration, SecretRef-managed provider credentials are persisted as non-secret markers (for example env var names, `secretref-env:ENV_VAR_NAME`, or `secretref-managed`), not resolved secret plaintext.
|
||||
- Marker writes are source-authoritative: OpenClaw persists markers from the active source config snapshot, not from resolved runtime secret values.
|
||||
|
||||
@@ -19,9 +19,7 @@ Related:
|
||||
|
||||
```bash
|
||||
openclaw agents list
|
||||
openclaw agents list --bindings
|
||||
openclaw agents add work --workspace ~/.openclaw/workspace-work
|
||||
openclaw agents add ops --workspace ~/.openclaw/workspace-ops --bind telegram:ops --non-interactive
|
||||
openclaw agents bindings
|
||||
openclaw agents bind --agent work --bind telegram:ops
|
||||
openclaw agents unbind --agent work --bind telegram:ops
|
||||
@@ -55,8 +53,6 @@ openclaw agents bind --agent work --bind telegram:ops --bind discord:guild-a
|
||||
|
||||
If you omit `accountId` (`--bind <channel>`), OpenClaw resolves it from channel defaults and plugin setup hooks when available.
|
||||
|
||||
If you omit `--agent` for `bind` or `unbind`, OpenClaw targets the current default agent.
|
||||
|
||||
### Binding scope behavior
|
||||
|
||||
- A binding without `accountId` matches the channel default account only.
|
||||
@@ -82,75 +78,6 @@ openclaw agents unbind --agent work --bind telegram:ops
|
||||
openclaw agents unbind --agent work --all
|
||||
```
|
||||
|
||||
`unbind` accepts either `--all` or one or more `--bind` values, not both.
|
||||
|
||||
## Command surface
|
||||
|
||||
### `agents`
|
||||
|
||||
Running `openclaw agents` with no subcommand is equivalent to `openclaw agents list`.
|
||||
|
||||
### `agents list`
|
||||
|
||||
Options:
|
||||
|
||||
- `--json`
|
||||
- `--bindings`: include full routing rules, not only per-agent counts/summaries
|
||||
|
||||
### `agents add [name]`
|
||||
|
||||
Options:
|
||||
|
||||
- `--workspace <dir>`
|
||||
- `--model <id>`
|
||||
- `--agent-dir <dir>`
|
||||
- `--bind <channel[:accountId]>` (repeatable)
|
||||
- `--non-interactive`
|
||||
- `--json`
|
||||
|
||||
Notes:
|
||||
|
||||
- Passing any explicit add flags switches the command into the non-interactive path.
|
||||
- Non-interactive mode requires both an agent name and `--workspace`.
|
||||
- `main` is reserved and cannot be used as the new agent id.
|
||||
|
||||
### `agents bindings`
|
||||
|
||||
Options:
|
||||
|
||||
- `--agent <id>`
|
||||
- `--json`
|
||||
|
||||
### `agents bind`
|
||||
|
||||
Options:
|
||||
|
||||
- `--agent <id>` (defaults to the current default agent)
|
||||
- `--bind <channel[:accountId]>` (repeatable)
|
||||
- `--json`
|
||||
|
||||
### `agents unbind`
|
||||
|
||||
Options:
|
||||
|
||||
- `--agent <id>` (defaults to the current default agent)
|
||||
- `--bind <channel[:accountId]>` (repeatable)
|
||||
- `--all`
|
||||
- `--json`
|
||||
|
||||
### `agents delete <id>`
|
||||
|
||||
Options:
|
||||
|
||||
- `--force`
|
||||
- `--json`
|
||||
|
||||
Notes:
|
||||
|
||||
- `main` cannot be deleted.
|
||||
- Without `--force`, interactive confirmation is required.
|
||||
- Workspace, agent state, and session transcript directories are moved to Trash, not hard-deleted.
|
||||
|
||||
## Identity files
|
||||
|
||||
Each agent workspace can include an `IDENTITY.md` at the workspace root:
|
||||
@@ -169,24 +96,6 @@ Avatar paths resolve relative to the workspace root.
|
||||
- `emoji`
|
||||
- `avatar` (workspace-relative path, http(s) URL, or data URI)
|
||||
|
||||
Options:
|
||||
|
||||
- `--agent <id>`
|
||||
- `--workspace <dir>`
|
||||
- `--identity-file <path>`
|
||||
- `--from-identity`
|
||||
- `--name <name>`
|
||||
- `--theme <theme>`
|
||||
- `--emoji <emoji>`
|
||||
- `--avatar <value>`
|
||||
- `--json`
|
||||
|
||||
Notes:
|
||||
|
||||
- `--agent` or `--workspace` can be used to select the target agent.
|
||||
- If you rely on `--workspace` and multiple agents share that workspace, the command fails and asks you to pass `--agent`.
|
||||
- When no explicit identity fields are provided, the command reads identity data from `IDENTITY.md`.
|
||||
|
||||
Load from `IDENTITY.md`:
|
||||
|
||||
```bash
|
||||
|
||||
@@ -11,8 +11,6 @@ title: "approvals"
|
||||
Manage exec approvals for the **local host**, **gateway host**, or a **node host**.
|
||||
By default, commands target the local approvals file on disk. Use `--gateway` to target the gateway, or `--node` to target a specific node.
|
||||
|
||||
Alias: `openclaw exec-approvals`
|
||||
|
||||
Related:
|
||||
|
||||
- Exec approvals: [Exec approvals](/tools/exec-approvals)
|
||||
@@ -43,15 +41,10 @@ Precedence is intentional:
|
||||
|
||||
```bash
|
||||
openclaw approvals set --file ./exec-approvals.json
|
||||
openclaw approvals set --stdin <<'EOF'
|
||||
{ version: 1, defaults: { security: "full", ask: "off" } }
|
||||
EOF
|
||||
openclaw approvals set --node <id|name|ip> --file ./exec-approvals.json
|
||||
openclaw approvals set --gateway --file ./exec-approvals.json
|
||||
```
|
||||
|
||||
`set` accepts JSON5, not only strict JSON. Use either `--file` or `--stdin`, not both.
|
||||
|
||||
## "Never prompt" / YOLO example
|
||||
|
||||
For a host that should never stop on exec approvals, set the host approvals defaults to `full` + `off`:
|
||||
@@ -110,24 +103,6 @@ openclaw approvals allowlist add --agent "*" "/usr/bin/uname"
|
||||
openclaw approvals allowlist remove "~/Projects/**/bin/rg"
|
||||
```
|
||||
|
||||
## Common options
|
||||
|
||||
`get`, `set`, and `allowlist add|remove` all support:
|
||||
|
||||
- `--node <id|name|ip>`
|
||||
- `--gateway`
|
||||
- shared node RPC options: `--url`, `--token`, `--timeout`, `--json`
|
||||
|
||||
Targeting notes:
|
||||
|
||||
- no target flags means the local approvals file on disk
|
||||
- `--gateway` targets the gateway host approvals file
|
||||
- `--node` targets one node host after resolving id, name, IP, or id prefix
|
||||
|
||||
`allowlist add|remove` also supports:
|
||||
|
||||
- `--agent <id>` (defaults to `*`)
|
||||
|
||||
## Notes
|
||||
|
||||
- `--node` uses the same resolver as `openclaw nodes` (id, name, ip, or id prefix).
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
summary: "CLI reference for `openclaw browser` (lifecycle, profiles, tabs, actions, state, and debugging)"
|
||||
summary: "CLI reference for `openclaw browser` (profiles, tabs, actions, Chrome MCP, and CDP)"
|
||||
read_when:
|
||||
- You use `openclaw browser` and want examples for common tasks
|
||||
- You want to control a browser running on another machine via a node host
|
||||
@@ -9,7 +9,7 @@ title: "browser"
|
||||
|
||||
# `openclaw browser`
|
||||
|
||||
Manage OpenClaw's browser control surface and run browser actions (lifecycle, profiles, tabs, snapshots, screenshots, navigation, input, state emulation, and debugging).
|
||||
Manage OpenClaw’s browser control server and run browser actions (tabs, snapshots, screenshots, navigation, clicks, typing).
|
||||
|
||||
Related:
|
||||
|
||||
@@ -20,7 +20,6 @@ Related:
|
||||
- `--url <gatewayWsUrl>`: Gateway WebSocket URL (defaults to config).
|
||||
- `--token <token>`: Gateway token (if required).
|
||||
- `--timeout <ms>`: request timeout (ms).
|
||||
- `--expect-final`: wait for a final Gateway response.
|
||||
- `--browser-profile <name>`: choose a browser profile (default from config).
|
||||
- `--json`: machine-readable output (where supported).
|
||||
|
||||
@@ -33,15 +32,6 @@ openclaw browser --browser-profile openclaw open https://example.com
|
||||
openclaw browser --browser-profile openclaw snapshot
|
||||
```
|
||||
|
||||
## Lifecycle
|
||||
|
||||
```bash
|
||||
openclaw browser status
|
||||
openclaw browser start
|
||||
openclaw browser stop
|
||||
openclaw browser --browser-profile openclaw reset-profile
|
||||
```
|
||||
|
||||
## If the command is missing
|
||||
|
||||
If `openclaw browser` is an unknown command, check `plugins.allow` in
|
||||
@@ -75,7 +65,6 @@ Profiles are named browser routing configs. In practice:
|
||||
openclaw browser profiles
|
||||
openclaw browser create-profile --name work --color "#FF5A36"
|
||||
openclaw browser create-profile --name chrome-live --driver existing-session
|
||||
openclaw browser create-profile --name remote --cdp-url https://browser-host.example.com
|
||||
openclaw browser delete-profile --name work
|
||||
```
|
||||
|
||||
@@ -89,9 +78,6 @@ openclaw browser --browser-profile work tabs
|
||||
|
||||
```bash
|
||||
openclaw browser tabs
|
||||
openclaw browser tab new
|
||||
openclaw browser tab select 2
|
||||
openclaw browser tab close 2
|
||||
openclaw browser open https://docs.openclaw.ai
|
||||
openclaw browser focus <targetId>
|
||||
openclaw browser close <targetId>
|
||||
@@ -117,64 +103,6 @@ Navigate/click/type (ref-based UI automation):
|
||||
openclaw browser navigate https://example.com
|
||||
openclaw browser click <ref>
|
||||
openclaw browser type <ref> "hello"
|
||||
openclaw browser press Enter
|
||||
openclaw browser hover <ref>
|
||||
openclaw browser scrollintoview <ref>
|
||||
openclaw browser drag <startRef> <endRef>
|
||||
openclaw browser select <ref> OptionA OptionB
|
||||
openclaw browser fill --fields '[{"ref":"1","value":"Ada"}]'
|
||||
openclaw browser wait --text "Done"
|
||||
openclaw browser evaluate --fn '(el) => el.textContent' --ref <ref>
|
||||
```
|
||||
|
||||
File + dialog helpers:
|
||||
|
||||
```bash
|
||||
openclaw browser upload /tmp/openclaw/uploads/file.pdf --ref <ref>
|
||||
openclaw browser waitfordownload
|
||||
openclaw browser download <ref> report.pdf
|
||||
openclaw browser dialog --accept
|
||||
```
|
||||
|
||||
## State and storage
|
||||
|
||||
Viewport + emulation:
|
||||
|
||||
```bash
|
||||
openclaw browser resize 1280 720
|
||||
openclaw browser set viewport 1280 720
|
||||
openclaw browser set offline on
|
||||
openclaw browser set media dark
|
||||
openclaw browser set timezone Europe/London
|
||||
openclaw browser set locale en-GB
|
||||
openclaw browser set geo 51.5074 -0.1278 --accuracy 25
|
||||
openclaw browser set device "iPhone 14"
|
||||
openclaw browser set headers '{"x-test":"1"}'
|
||||
openclaw browser set credentials myuser mypass
|
||||
```
|
||||
|
||||
Cookies + storage:
|
||||
|
||||
```bash
|
||||
openclaw browser cookies
|
||||
openclaw browser cookies set session abc123 --url https://example.com
|
||||
openclaw browser cookies clear
|
||||
openclaw browser storage local get
|
||||
openclaw browser storage local set token abc123
|
||||
openclaw browser storage session clear
|
||||
```
|
||||
|
||||
## Debugging
|
||||
|
||||
```bash
|
||||
openclaw browser console --level error
|
||||
openclaw browser pdf
|
||||
openclaw browser responsebody "**/api"
|
||||
openclaw browser highlight <ref>
|
||||
openclaw browser errors --clear
|
||||
openclaw browser requests --filter api
|
||||
openclaw browser trace start
|
||||
openclaw browser trace stop --out trace.zip
|
||||
```
|
||||
|
||||
## Existing Chrome via MCP
|
||||
|
||||
@@ -26,13 +26,6 @@ openclaw channels resolve --channel slack "#general" "@jane"
|
||||
openclaw channels logs --channel all
|
||||
```
|
||||
|
||||
## Status / capabilities / resolve / logs
|
||||
|
||||
- `channels status`: `--probe`, `--timeout <ms>`, `--json`
|
||||
- `channels capabilities`: `--channel <name>`, `--account <id>` (only with `--channel`), `--target <dest>`, `--timeout <ms>`, `--json`
|
||||
- `channels resolve`: `<entries...>`, `--channel <name>`, `--account <id>`, `--kind <auto|user|group>`, `--json`
|
||||
- `channels logs`: `--channel <name|all>`, `--lines <n>`, `--json`
|
||||
|
||||
## Add / remove accounts
|
||||
|
||||
```bash
|
||||
@@ -43,16 +36,6 @@ openclaw channels remove --channel telegram --delete
|
||||
|
||||
Tip: `openclaw channels add --help` shows per-channel flags (token, private key, app token, signal-cli paths, etc).
|
||||
|
||||
Common non-interactive add surfaces include:
|
||||
|
||||
- bot-token channels: `--token`, `--bot-token`, `--app-token`, `--token-file`
|
||||
- Signal/iMessage transport fields: `--signal-number`, `--cli-path`, `--http-url`, `--http-host`, `--http-port`, `--db-path`, `--service`, `--region`
|
||||
- Google Chat fields: `--webhook-path`, `--webhook-url`, `--audience-type`, `--audience`
|
||||
- Matrix fields: `--homeserver`, `--user-id`, `--access-token`, `--password`, `--device-name`, `--initial-sync-limit`
|
||||
- Nostr fields: `--private-key`, `--relay-urls`
|
||||
- Tlon fields: `--ship`, `--url`, `--code`, `--group-channels`, `--dm-allowlist`, `--auto-discover-channels`
|
||||
- `--use-env` for default-account env-backed auth where supported
|
||||
|
||||
When you run `openclaw channels add` without flags, the interactive wizard can prompt:
|
||||
|
||||
- account ids per selected channel
|
||||
@@ -80,11 +63,6 @@ openclaw channels login --channel whatsapp
|
||||
openclaw channels logout --channel whatsapp
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- `channels login` supports `--verbose`.
|
||||
- `channels login` / `logout` can infer the channel when only one supported login target is configured.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- Run `openclaw status --deep` for a broad probe.
|
||||
@@ -104,7 +82,6 @@ openclaw channels capabilities --channel discord --target channel:123
|
||||
Notes:
|
||||
|
||||
- `--channel` is optional; omit it to list every channel (including extensions).
|
||||
- `--account` is only valid with `--channel`.
|
||||
- `--target` accepts `channel:<id>` or a raw numeric channel id and only applies to Discord.
|
||||
- Probes are provider-specific: Discord intents + optional channel permissions; Slack bot + user scopes; Telegram bot flags + webhook; Signal daemon version; Microsoft Teams app token + Graph roles/scopes (annotated where known). Channels without probes report `Probe: unavailable`.
|
||||
|
||||
|
||||
@@ -11,28 +11,10 @@ Config helpers for non-interactive edits in `openclaw.json`: get/set/unset/file/
|
||||
values by path and print the active config file. Run without a subcommand to
|
||||
open the configure wizard (same as `openclaw configure`).
|
||||
|
||||
Root options:
|
||||
|
||||
- `--section <section>`: repeatable guided-setup section filter when you run `openclaw config` without a subcommand
|
||||
|
||||
Supported guided sections:
|
||||
|
||||
- `workspace`
|
||||
- `model`
|
||||
- `web`
|
||||
- `gateway`
|
||||
- `daemon`
|
||||
- `channels`
|
||||
- `plugins`
|
||||
- `skills`
|
||||
- `health`
|
||||
|
||||
## Examples
|
||||
|
||||
```bash
|
||||
openclaw config file
|
||||
openclaw config --section model
|
||||
openclaw config --section gateway --section daemon
|
||||
openclaw config schema
|
||||
openclaw config get browser.executablePath
|
||||
openclaw config set browser.executablePath "/usr/bin/google-chrome"
|
||||
@@ -87,8 +69,6 @@ openclaw config set gateway.port 19001 --strict-json
|
||||
openclaw config set channels.whatsapp.groups '["*"]' --strict-json
|
||||
```
|
||||
|
||||
`config get <path> --json` prints the raw value as JSON instead of terminal-formatted text.
|
||||
|
||||
## `config set` modes
|
||||
|
||||
`openclaw config set` supports four assignment styles:
|
||||
|
||||
@@ -16,35 +16,15 @@ Tip: `openclaw config` without a subcommand opens the same wizard. Use
|
||||
`openclaw config get|set|unset` for non-interactive edits.
|
||||
|
||||
For web search, `openclaw configure --section web` lets you choose a provider
|
||||
and configure its credentials. Some providers also show provider-specific
|
||||
follow-up prompts:
|
||||
|
||||
- **Grok** can offer optional `x_search` setup with the same `XAI_API_KEY` and
|
||||
let you pick an `x_search` model.
|
||||
- **Kimi** can ask for the Moonshot API region (`api.moonshot.ai` vs
|
||||
`api.moonshot.cn`) and the default Kimi web-search model.
|
||||
and configure its credentials. If you choose **Grok**, configure can also show
|
||||
a separate follow-up step to enable `x_search` with the same `XAI_API_KEY` and
|
||||
pick an `x_search` model. Other web-search providers do not show that step.
|
||||
|
||||
Related:
|
||||
|
||||
- Gateway configuration reference: [Configuration](/gateway/configuration)
|
||||
- Config CLI: [Config](/cli/config)
|
||||
|
||||
## Options
|
||||
|
||||
- `--section <section>`: repeatable section filter
|
||||
|
||||
Available sections:
|
||||
|
||||
- `workspace`
|
||||
- `model`
|
||||
- `web`
|
||||
- `gateway`
|
||||
- `daemon`
|
||||
- `channels`
|
||||
- `plugins`
|
||||
- `skills`
|
||||
- `health`
|
||||
|
||||
Notes:
|
||||
|
||||
- Choosing where the Gateway runs always updates `gateway.mode`. You can select "Continue" without other sections if that is all you need.
|
||||
@@ -59,5 +39,4 @@ Notes:
|
||||
openclaw configure
|
||||
openclaw configure --section web
|
||||
openclaw configure --section model --section channels
|
||||
openclaw configure --section gateway --section daemon
|
||||
```
|
||||
|
||||
@@ -21,10 +21,6 @@ output internal. `--deliver` remains as a deprecated alias for `--announce`.
|
||||
|
||||
Note: one-shot (`--at`) jobs delete after success by default. Use `--keep-after-run` to keep them.
|
||||
|
||||
Note: `--session` supports `main`, `isolated`, `current`, and `session:<id>`.
|
||||
Use `current` to bind to the active session at creation time, or `session:<id>` for
|
||||
an explicit persistent session key.
|
||||
|
||||
Note: for one-shot CLI jobs, offset-less `--at` datetimes are treated as UTC unless you also pass
|
||||
`--tz <iana>`, which interprets that local wall-clock time in the given timezone.
|
||||
|
||||
@@ -32,9 +28,6 @@ Note: recurring jobs now use exponential retry backoff after consecutive errors
|
||||
|
||||
Note: `openclaw cron run` now returns as soon as the manual run is queued for execution. Successful responses include `{ ok: true, enqueued: true, runId }`; use `openclaw cron runs --id <job-id>` to follow the eventual outcome.
|
||||
|
||||
Note: `openclaw cron run <job-id>` force-runs by default. Use `--due` to keep the
|
||||
older "only run if due" behavior.
|
||||
|
||||
Note: retention/pruning is controlled in config:
|
||||
|
||||
- `cron.sessionRetention` (default `24h`) prunes completed isolated run sessions.
|
||||
@@ -85,31 +78,3 @@ openclaw cron add \
|
||||
```
|
||||
|
||||
`--light-context` applies to isolated agent-turn jobs only. For cron runs, lightweight mode keeps bootstrap context empty instead of injecting the full workspace bootstrap set.
|
||||
|
||||
## Common admin commands
|
||||
|
||||
Manual run:
|
||||
|
||||
```bash
|
||||
openclaw cron run <job-id>
|
||||
openclaw cron run <job-id> --due
|
||||
openclaw cron runs --id <job-id> --limit 50
|
||||
```
|
||||
|
||||
Agent/session retargeting:
|
||||
|
||||
```bash
|
||||
openclaw cron edit <job-id> --agent ops
|
||||
openclaw cron edit <job-id> --clear-agent
|
||||
openclaw cron edit <job-id> --session current
|
||||
openclaw cron edit <job-id> --session "session:daily-brief"
|
||||
```
|
||||
|
||||
Delivery tweaks:
|
||||
|
||||
```bash
|
||||
openclaw cron edit <job-id> --announce --channel slack --to "channel:C1234567890"
|
||||
openclaw cron edit <job-id> --best-effort-deliver
|
||||
openclaw cron edit <job-id> --no-best-effort-deliver
|
||||
openclaw cron edit <job-id> --no-deliver
|
||||
```
|
||||
|
||||
@@ -75,8 +75,6 @@ Rotate a device token for a specific role (optionally updating scopes).
|
||||
openclaw devices rotate --device <deviceId> --role operator --scope operator.read --scope operator.write
|
||||
```
|
||||
|
||||
Returns the new token payload as JSON.
|
||||
|
||||
### `openclaw devices revoke --device <id> --role <role>`
|
||||
|
||||
Revoke a device token for a specific role.
|
||||
@@ -85,8 +83,6 @@ Revoke a device token for a specific role.
|
||||
openclaw devices revoke --device <deviceId> --role node
|
||||
```
|
||||
|
||||
Returns the revoke result as JSON.
|
||||
|
||||
## Common options
|
||||
|
||||
- `--url <url>`: Gateway WebSocket URL (defaults to `gateway.remote.url` when configured).
|
||||
@@ -104,7 +100,6 @@ Pass `--token` or `--password` explicitly. Missing explicit credentials is an er
|
||||
- These commands require `operator.pairing` (or `operator.admin`) scope.
|
||||
- `devices clear` is intentionally gated by `--yes`.
|
||||
- If pairing scope is unavailable on local loopback (and no explicit `--url` is passed), list/approve can use a local pairing fallback.
|
||||
- `devices approve` picks the newest pending request automatically when you omit `requestId` or pass `--latest`.
|
||||
|
||||
## Token drift recovery checklist
|
||||
|
||||
|
||||
@@ -19,30 +19,5 @@ Related:
|
||||
|
||||
```bash
|
||||
openclaw dns setup
|
||||
openclaw dns setup --domain openclaw.internal
|
||||
openclaw dns setup --apply
|
||||
```
|
||||
|
||||
## `dns setup`
|
||||
|
||||
Plan or apply CoreDNS setup for unicast DNS-SD discovery.
|
||||
|
||||
Options:
|
||||
|
||||
- `--domain <domain>`: wide-area discovery domain (for example `openclaw.internal`)
|
||||
- `--apply`: install or update CoreDNS config and restart the service (requires sudo; macOS only)
|
||||
|
||||
What it shows:
|
||||
|
||||
- resolved discovery domain
|
||||
- zone file path
|
||||
- current tailnet IPs
|
||||
- recommended `openclaw.json` discovery config
|
||||
- the Tailscale Split DNS nameserver/domain values to set
|
||||
|
||||
Notes:
|
||||
|
||||
- Without `--apply`, the command is a planning helper only and prints the recommended setup.
|
||||
- If `--domain` is omitted, OpenClaw uses `discovery.wideArea.domain` from config.
|
||||
- `--apply` currently supports macOS only and expects Homebrew CoreDNS.
|
||||
- `--apply` bootstraps the zone file if needed, ensures the CoreDNS import stanza exists, and restarts the `coredns` brew service.
|
||||
|
||||
@@ -9,20 +9,7 @@ title: "docs"
|
||||
|
||||
Search the live docs index.
|
||||
|
||||
Arguments:
|
||||
|
||||
- `[query...]`: search terms to send to the live docs index
|
||||
|
||||
Examples:
|
||||
|
||||
```bash
|
||||
openclaw docs
|
||||
openclaw docs browser existing-session
|
||||
openclaw docs sandbox allowHostControl
|
||||
openclaw docs gateway token secretref
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- With no query, `openclaw docs` opens the live docs search entrypoint.
|
||||
- Multi-word queries are passed through as one search request.
|
||||
|
||||
@@ -21,21 +21,8 @@ Related:
|
||||
openclaw doctor
|
||||
openclaw doctor --repair
|
||||
openclaw doctor --deep
|
||||
openclaw doctor --repair --non-interactive
|
||||
openclaw doctor --generate-gateway-token
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
- `--no-workspace-suggestions`: disable workspace memory/search suggestions
|
||||
- `--yes`: accept defaults without prompting
|
||||
- `--repair`: apply recommended repairs without prompting
|
||||
- `--fix`: alias for `--repair`
|
||||
- `--force`: apply aggressive repairs, including overwriting custom service config when needed
|
||||
- `--non-interactive`: run without prompts; safe migrations only
|
||||
- `--generate-gateway-token`: generate and configure a gateway token
|
||||
- `--deep`: scan system services for extra gateway installs
|
||||
|
||||
Notes:
|
||||
|
||||
- Interactive prompts (like keychain/OAuth fixes) only run when stdin is a TTY and `--non-interactive` is **not** set. Headless runs (cron, Telegram, no terminal) will skip prompts.
|
||||
|
||||
@@ -91,20 +91,6 @@ Pass `--token` or `--password` explicitly. Missing explicit credentials is an er
|
||||
openclaw gateway health --url ws://127.0.0.1:18789
|
||||
```
|
||||
|
||||
### `gateway usage-cost`
|
||||
|
||||
Fetch usage-cost summaries from session logs.
|
||||
|
||||
```bash
|
||||
openclaw gateway usage-cost
|
||||
openclaw gateway usage-cost --days 7
|
||||
openclaw gateway usage-cost --json
|
||||
```
|
||||
|
||||
Options:
|
||||
|
||||
- `--days <days>`: number of days to include (default `30`).
|
||||
|
||||
### `gateway status`
|
||||
|
||||
`gateway status` shows the Gateway service (launchd/systemd/schtasks) plus an optional RPC probe.
|
||||
@@ -127,12 +113,10 @@ Options:
|
||||
|
||||
Notes:
|
||||
|
||||
- `gateway status` stays available for diagnostics even when the local CLI config is missing or invalid.
|
||||
- `gateway status` resolves configured auth SecretRefs for probe auth when possible.
|
||||
- If a required auth SecretRef is unresolved in this command path, `gateway status --json` reports `rpc.authWarning` when probe connectivity/auth fails; pass `--token`/`--password` explicitly or resolve the secret source first.
|
||||
- If the probe succeeds, unresolved auth-ref warnings are suppressed to avoid false positives.
|
||||
- Use `--require-rpc` in scripts and automation when a listening service is not enough and you need the Gateway RPC itself to be healthy.
|
||||
- Human output includes the resolved file log path plus the CLI-vs-service config paths/validity snapshot to help diagnose profile or state-dir drift.
|
||||
- On Linux systemd installs, service auth drift checks read both `Environment=` and `EnvironmentFile=` values from the unit (including `%h`, quoted paths, multiple files, and optional `-` files).
|
||||
- Drift checks resolve `gateway.auth.token` SecretRefs using merged runtime env (service command env first, then process env fallback).
|
||||
- If token auth is not effectively active (explicit `gateway.auth.mode` of `password`/`none`/`trusted-proxy`, or mode unset where password can win and no token candidate can win), token-drift checks skip config token resolution.
|
||||
@@ -198,21 +182,6 @@ openclaw gateway call status
|
||||
openclaw gateway call logs.tail --params '{"sinceMs": 60000}'
|
||||
```
|
||||
|
||||
Options:
|
||||
|
||||
- `--params <json>`: JSON object string for params (default `{}`)
|
||||
- `--url <url>`
|
||||
- `--token <token>`
|
||||
- `--password <password>`
|
||||
- `--timeout <ms>`
|
||||
- `--expect-final`
|
||||
- `--json`
|
||||
|
||||
Notes:
|
||||
|
||||
- `--params` must be valid JSON.
|
||||
- `--expect-final` is mainly for agent-style RPCs that stream intermediate events before a final payload.
|
||||
|
||||
## Manage the Gateway service
|
||||
|
||||
```bash
|
||||
@@ -223,12 +192,6 @@ openclaw gateway restart
|
||||
openclaw gateway uninstall
|
||||
```
|
||||
|
||||
Command options:
|
||||
|
||||
- `gateway status`: `--url`, `--token`, `--password`, `--timeout`, `--no-probe`, `--require-rpc`, `--deep`, `--json`
|
||||
- `gateway install`: `--port`, `--runtime <node|bun>`, `--token`, `--force`, `--json`
|
||||
- `gateway uninstall|start|stop|restart`: `--json`
|
||||
|
||||
Notes:
|
||||
|
||||
- `gateway install` supports `--port`, `--runtime`, `--token`, `--force`, `--json`.
|
||||
|
||||
@@ -9,21 +9,10 @@ title: "health"
|
||||
|
||||
Fetch health from the running Gateway.
|
||||
|
||||
Options:
|
||||
|
||||
- `--json`: machine-readable output
|
||||
- `--timeout <ms>`: connection timeout in milliseconds (default `10000`)
|
||||
- `--verbose`: verbose logging
|
||||
- `--debug`: alias for `--verbose`
|
||||
|
||||
Examples:
|
||||
|
||||
```bash
|
||||
openclaw health
|
||||
openclaw health --json
|
||||
openclaw health --timeout 2500
|
||||
openclaw health --verbose
|
||||
openclaw health --debug
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
@@ -10,8 +10,6 @@ title: "hooks"
|
||||
|
||||
Manage agent hooks (event-driven automations for commands like `/new`, `/reset`, and gateway startup).
|
||||
|
||||
Running `openclaw hooks` with no subcommand is equivalent to `openclaw hooks list`.
|
||||
|
||||
Related:
|
||||
|
||||
- Hooks: [Hooks](/automation/hooks)
|
||||
@@ -69,7 +67,7 @@ Show detailed information about a specific hook.
|
||||
|
||||
**Arguments:**
|
||||
|
||||
- `<name>`: Hook name or hook key (e.g., `session-memory`)
|
||||
- `<name>`: Hook name (e.g., `session-memory`)
|
||||
|
||||
**Options:**
|
||||
|
||||
@@ -127,7 +125,7 @@ Not ready: 0
|
||||
openclaw hooks enable <name>
|
||||
```
|
||||
|
||||
Enable a specific hook by adding it to your config (`~/.openclaw/openclaw.json` by default).
|
||||
Enable a specific hook by adding it to your config (`~/.openclaw/config.json`).
|
||||
|
||||
**Note:** Workspace hooks are disabled by default until enabled here or in config. Hooks managed by plugins show `plugin:<id>` in `openclaw hooks list` and can’t be enabled/disabled here. Enable/disable the plugin instead.
|
||||
|
||||
@@ -188,11 +186,6 @@ openclaw hooks disable command-logger
|
||||
|
||||
- Restart the gateway so hooks reload
|
||||
|
||||
## Notes
|
||||
|
||||
- `openclaw hooks list --json`, `info --json`, and `check --json` write structured JSON directly to stdout.
|
||||
- Plugin-managed hooks cannot be enabled or disabled here; enable or disable the owning plugin instead.
|
||||
|
||||
## Install Hook Packs
|
||||
|
||||
```bash
|
||||
|
||||
@@ -46,7 +46,6 @@ This page describes the current CLI behavior. If commands change, update this do
|
||||
- [`browser`](/cli/browser)
|
||||
- [`cron`](/cli/cron)
|
||||
- [`tasks`](/cli/index#tasks)
|
||||
- [`flows`](/cli/flows)
|
||||
- [`dns`](/cli/dns)
|
||||
- [`docs`](/cli/docs)
|
||||
- [`hooks`](/cli/hooks)
|
||||
@@ -106,7 +105,6 @@ openclaw [--dev] [--profile <name>] <command>
|
||||
set
|
||||
unset
|
||||
file
|
||||
schema
|
||||
validate
|
||||
completion
|
||||
doctor
|
||||
@@ -124,26 +122,16 @@ openclaw [--dev] [--profile <name>] <command>
|
||||
reset
|
||||
uninstall
|
||||
update
|
||||
wizard
|
||||
status
|
||||
channels
|
||||
list
|
||||
status
|
||||
capabilities
|
||||
resolve
|
||||
logs
|
||||
add
|
||||
remove
|
||||
login
|
||||
logout
|
||||
directory
|
||||
self
|
||||
peers list
|
||||
groups list|members
|
||||
skills
|
||||
search
|
||||
install
|
||||
update
|
||||
list
|
||||
info
|
||||
check
|
||||
@@ -164,28 +152,6 @@ openclaw [--dev] [--profile <name>] <command>
|
||||
message
|
||||
send
|
||||
broadcast
|
||||
poll
|
||||
react
|
||||
reactions
|
||||
read
|
||||
edit
|
||||
delete
|
||||
pin
|
||||
unpin
|
||||
pins
|
||||
permissions
|
||||
search
|
||||
thread create|list|reply
|
||||
emoji list|upload
|
||||
sticker send|upload
|
||||
role info|add|remove
|
||||
channel info|list
|
||||
member info
|
||||
voice status
|
||||
event list|create
|
||||
timeout
|
||||
kick
|
||||
ban
|
||||
agent
|
||||
agents
|
||||
list
|
||||
@@ -197,26 +163,18 @@ openclaw [--dev] [--profile <name>] <command>
|
||||
set-identity
|
||||
acp
|
||||
mcp
|
||||
serve
|
||||
list
|
||||
show
|
||||
set
|
||||
unset
|
||||
status
|
||||
health
|
||||
sessions
|
||||
cleanup
|
||||
tasks
|
||||
list
|
||||
audit
|
||||
maintenance
|
||||
show
|
||||
notify
|
||||
cancel
|
||||
flow list|show|cancel
|
||||
gateway
|
||||
call
|
||||
usage-cost
|
||||
health
|
||||
status
|
||||
probe
|
||||
@@ -265,34 +223,13 @@ openclaw [--dev] [--profile <name>] <command>
|
||||
runs
|
||||
run
|
||||
nodes
|
||||
status
|
||||
describe
|
||||
list
|
||||
pending
|
||||
approve
|
||||
reject
|
||||
rename
|
||||
invoke
|
||||
notify
|
||||
push
|
||||
canvas snapshot|present|hide|navigate|eval
|
||||
canvas a2ui push|reset
|
||||
camera list|snap|clip
|
||||
screen record
|
||||
location get
|
||||
devices
|
||||
list
|
||||
remove
|
||||
clear
|
||||
approve
|
||||
reject
|
||||
rotate
|
||||
revoke
|
||||
node
|
||||
run
|
||||
status
|
||||
install
|
||||
uninstall
|
||||
start
|
||||
stop
|
||||
restart
|
||||
approvals
|
||||
@@ -360,49 +297,10 @@ Note: plugins can add additional top-level commands (for example `openclaw voice
|
||||
|
||||
## Secrets
|
||||
|
||||
### `secrets`
|
||||
|
||||
Manage SecretRefs and related runtime/config hygiene.
|
||||
|
||||
Subcommands:
|
||||
|
||||
- `secrets reload`
|
||||
- `secrets audit`
|
||||
- `secrets configure`
|
||||
- `secrets apply --from <path>`
|
||||
|
||||
`secrets reload` options:
|
||||
|
||||
- `--url`, `--token`, `--timeout`, `--expect-final`, `--json`
|
||||
|
||||
`secrets audit` options:
|
||||
|
||||
- `--check`
|
||||
- `--allow-exec`
|
||||
- `--json`
|
||||
|
||||
`secrets configure` options:
|
||||
|
||||
- `--apply`
|
||||
- `--yes`
|
||||
- `--providers-only`
|
||||
- `--skip-provider-setup`
|
||||
- `--agent <id>`
|
||||
- `--allow-exec`
|
||||
- `--plan-out <path>`
|
||||
- `--json`
|
||||
|
||||
`secrets apply --from <path>` options:
|
||||
|
||||
- `--dry-run`
|
||||
- `--allow-exec`
|
||||
- `--json`
|
||||
|
||||
Notes:
|
||||
|
||||
- `reload` is a Gateway RPC and keeps the last-known-good runtime snapshot when resolution fails.
|
||||
- `audit --check` returns non-zero on findings; unresolved refs use a higher-priority non-zero exit code.
|
||||
- Dry-run exec checks are skipped by default; use `--allow-exec` to opt in.
|
||||
- `openclaw secrets reload` — re-resolve refs and atomically swap the runtime snapshot.
|
||||
- `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
|
||||
|
||||
@@ -421,25 +319,9 @@ Most plugin changes require a gateway restart. See [/plugin](/tools/plugin).
|
||||
|
||||
Vector search over `MEMORY.md` + `memory/*.md`:
|
||||
|
||||
- `openclaw memory status` — show index stats; use `--deep` for provider probes or `--fix` to repair stale recall/promotion artifacts.
|
||||
- `openclaw memory status` — show index stats.
|
||||
- `openclaw memory index` — reindex memory files.
|
||||
- `openclaw memory search "<query>"` (or `--query "<query>"`) — semantic search over memory.
|
||||
- `openclaw memory promote` — rank short-term recalls and optionally append top entries into `MEMORY.md`.
|
||||
|
||||
## Sandbox
|
||||
|
||||
Manage sandbox runtimes for isolated agent execution. See [/cli/sandbox](/cli/sandbox).
|
||||
|
||||
Subcommands:
|
||||
|
||||
- `sandbox list [--browser] [--json]`
|
||||
- `sandbox recreate [--all] [--session <key>] [--agent <id>] [--browser] [--force]`
|
||||
- `sandbox explain [--session <key>] [--agent <id>] [--json]`
|
||||
|
||||
Notes:
|
||||
|
||||
- `sandbox recreate` removes existing runtimes so the next use seeds them again with current config.
|
||||
- For `ssh` and OpenShell `remote` backends, recreate deletes the canonical remote workspace for the selected scope.
|
||||
|
||||
## Chat slash commands
|
||||
|
||||
@@ -453,22 +335,6 @@ Highlights:
|
||||
|
||||
## Setup + onboarding
|
||||
|
||||
### `completion`
|
||||
|
||||
Generate shell-completion scripts and optionally install them into your shell profile.
|
||||
|
||||
Options:
|
||||
|
||||
- `-s, --shell <zsh|bash|powershell|fish>`
|
||||
- `-i, --install`
|
||||
- `--write-state`
|
||||
- `-y, --yes`
|
||||
|
||||
Notes:
|
||||
|
||||
- Without `--install` or `--write-state`, `completion` prints the script to stdout.
|
||||
- `--install` writes an `OpenClaw Completion` block into your shell profile and points it at the cached script under the OpenClaw state directory.
|
||||
|
||||
### `setup`
|
||||
|
||||
Initialize config + workspace.
|
||||
@@ -497,7 +363,7 @@ Options:
|
||||
- `--mode <local|remote>`
|
||||
- `--flow <quickstart|advanced|manual>` (manual is an alias for advanced)
|
||||
- `--auth-choice <choice>` where `<choice>` is one of:
|
||||
`chutes`, `deepseek-api-key`, `openai-codex`, `openai-api-key`,
|
||||
`setup-token`, `token`, `chutes`, `deepseek-api-key`, `openai-codex`, `openai-api-key`,
|
||||
`openrouter-api-key`, `kilocode-api-key`, `litellm-api-key`, `ai-gateway-api-key`,
|
||||
`cloudflare-ai-gateway-api-key`, `moonshot-api-key`, `moonshot-api-key-cn`,
|
||||
`kimi-code-api-key`, `synthetic-api-key`, `venice-api-key`, `together-api-key`,
|
||||
@@ -508,6 +374,10 @@ Options:
|
||||
`mistral-api-key`, `volcengine-api-key`, `byteplus-api-key`, `qianfan-api-key`,
|
||||
`modelstudio-standard-api-key-cn`, `modelstudio-standard-api-key`,
|
||||
`modelstudio-api-key-cn`, `modelstudio-api-key`, `custom-api-key`, `skip`
|
||||
- `--token-provider <id>` (non-interactive; used with `--auth-choice token`)
|
||||
- `--token <token>` (non-interactive; used with `--auth-choice token`)
|
||||
- `--token-profile-id <id>` (non-interactive; default: `<provider>:manual`)
|
||||
- `--token-expires-in <duration>` (non-interactive; e.g. `365d`, `12h`)
|
||||
- `--secret-input-mode <plaintext|ref>` (default `plaintext`; use `ref` to store provider default env refs instead of plaintext keys)
|
||||
- `--anthropic-api-key <key>`
|
||||
- `--openai-api-key <key>`
|
||||
@@ -553,10 +423,6 @@ Options:
|
||||
|
||||
Interactive configuration wizard (models, channels, skills, gateway).
|
||||
|
||||
Options:
|
||||
|
||||
- `--section <section>` (repeatable; limit the wizard to specific sections)
|
||||
|
||||
### `config`
|
||||
|
||||
Non-interactive config helpers (get/set/unset/file/schema/validate). Running `openclaw config` with no
|
||||
@@ -594,72 +460,6 @@ Options:
|
||||
- `--force`: force repairs even when not strictly needed.
|
||||
- `--generate-gateway-token`: generate a new gateway auth token.
|
||||
|
||||
### `dashboard`
|
||||
|
||||
Open the Control UI with your current token.
|
||||
|
||||
Options:
|
||||
|
||||
- `--no-open`: print the URL but do not launch a browser
|
||||
|
||||
Notes:
|
||||
|
||||
- For SecretRef-managed gateway tokens, `dashboard` prints or opens a non-tokenized URL instead of exposing the secret in terminal output or browser launch arguments.
|
||||
|
||||
### `update`
|
||||
|
||||
Update the installed CLI.
|
||||
|
||||
Root options:
|
||||
|
||||
- `--json`
|
||||
- `--no-restart`
|
||||
- `--dry-run`
|
||||
- `--channel <stable|beta|dev>`
|
||||
- `--tag <dist-tag|version|spec>`
|
||||
- `--timeout <seconds>`
|
||||
- `--yes`
|
||||
|
||||
Subcommands:
|
||||
|
||||
- `update status`
|
||||
- `update wizard`
|
||||
|
||||
`update status` options:
|
||||
|
||||
- `--json`
|
||||
- `--timeout <seconds>`
|
||||
|
||||
`update wizard` options:
|
||||
|
||||
- `--timeout <seconds>`
|
||||
|
||||
Notes:
|
||||
|
||||
- `openclaw --update` rewrites to `openclaw update`.
|
||||
|
||||
### `backup`
|
||||
|
||||
Create and verify local backup archives for OpenClaw state.
|
||||
|
||||
Subcommands:
|
||||
|
||||
- `backup create`
|
||||
- `backup verify <archive>`
|
||||
|
||||
`backup create` options:
|
||||
|
||||
- `--output <path>`
|
||||
- `--json`
|
||||
- `--dry-run`
|
||||
- `--verify`
|
||||
- `--only-config`
|
||||
- `--no-include-workspace`
|
||||
|
||||
`backup verify <archive>` options:
|
||||
|
||||
- `--json`
|
||||
|
||||
## Channel helpers
|
||||
|
||||
### `channels`
|
||||
@@ -701,39 +501,12 @@ Common options:
|
||||
- `--no-usage`: skip model provider usage/quota snapshots (OAuth/API-backed only).
|
||||
- `--json`: output JSON (includes usage unless `--no-usage` is set).
|
||||
|
||||
`channels status` options:
|
||||
|
||||
- `--probe`
|
||||
- `--timeout <ms>`
|
||||
- `--json`
|
||||
|
||||
`channels capabilities` options:
|
||||
|
||||
- `--channel <name>`
|
||||
- `--account <id>` (only with `--channel`)
|
||||
- `--target <dest>`
|
||||
- `--timeout <ms>`
|
||||
- `--json`
|
||||
|
||||
`channels resolve` options:
|
||||
|
||||
- `<entries...>`
|
||||
- `--channel <name>`
|
||||
- `--account <id>`
|
||||
- `--kind <auto|user|group>`
|
||||
- `--json`
|
||||
|
||||
`channels logs` options:
|
||||
|
||||
- `--channel <name|all>` (default `all`)
|
||||
- `--lines <n>` (default `200`)
|
||||
- `--json`
|
||||
|
||||
Notes:
|
||||
|
||||
- `channels login` supports `--verbose`.
|
||||
- `channels capabilities --account` only applies when `--channel` is set.
|
||||
|
||||
More detail: [/concepts/oauth](/concepts/oauth)
|
||||
|
||||
Examples:
|
||||
@@ -746,23 +519,6 @@ openclaw channels status --probe
|
||||
openclaw status --deep
|
||||
```
|
||||
|
||||
### `directory`
|
||||
|
||||
Look up self, peer, and group IDs for channels that expose a directory surface. See [`openclaw directory`](/cli/directory).
|
||||
|
||||
Common options:
|
||||
|
||||
- `--channel <name>`
|
||||
- `--account <id>`
|
||||
- `--json`
|
||||
|
||||
Subcommands:
|
||||
|
||||
- `directory self`
|
||||
- `directory peers list [--query <text>] [--limit <n>]`
|
||||
- `directory groups list [--query <text>] [--limit <n>]`
|
||||
- `directory groups members --group-id <id> [--limit <n>]`
|
||||
|
||||
### `skills`
|
||||
|
||||
List and inspect available skills plus readiness info.
|
||||
@@ -794,11 +550,6 @@ Subcommands:
|
||||
- `pairing approve <channel> <code> [--account <id>] [--notify]`
|
||||
- `pairing approve --channel <channel> [--account <id>] <code> [--notify]`
|
||||
|
||||
Notes:
|
||||
|
||||
- If exactly one pairing-capable channel is configured, `pairing approve <code>` is also allowed.
|
||||
- `list` and `approve` both support `--account <id>` for multi-account channels.
|
||||
|
||||
### `devices`
|
||||
|
||||
Manage gateway device pairing entries and per-role device tokens.
|
||||
@@ -813,69 +564,6 @@ Subcommands:
|
||||
- `devices rotate --device <id> --role <role> [--scope <scope...>]`
|
||||
- `devices revoke --device <id> --role <role>`
|
||||
|
||||
Notes:
|
||||
|
||||
- `devices list` and `devices approve` can fall back to local pairing files on local loopback when direct pairing scope is unavailable.
|
||||
- `devices approve` auto-selects the newest pending request when no `requestId` is passed or `--latest` is set.
|
||||
- `devices rotate` and `devices revoke` return JSON payloads.
|
||||
|
||||
### `qr`
|
||||
|
||||
Generate a mobile pairing QR and setup code from the current Gateway config. See [`openclaw qr`](/cli/qr).
|
||||
|
||||
Options:
|
||||
|
||||
- `--remote`
|
||||
- `--url <url>`
|
||||
- `--public-url <url>`
|
||||
- `--token <token>`
|
||||
- `--password <password>`
|
||||
- `--setup-code-only`
|
||||
- `--no-ascii`
|
||||
- `--json`
|
||||
|
||||
Notes:
|
||||
|
||||
- `--token` and `--password` are mutually exclusive.
|
||||
- The setup code carries a short-lived bootstrap token, not the shared gateway token/password.
|
||||
- After scanning, approve the request with `openclaw devices list` / `openclaw devices approve <requestId>`.
|
||||
|
||||
### `clawbot`
|
||||
|
||||
Legacy alias namespace. Currently supports `openclaw clawbot qr`, which maps to [`openclaw qr`](/cli/qr).
|
||||
|
||||
### `hooks`
|
||||
|
||||
Manage internal agent hooks.
|
||||
|
||||
Subcommands:
|
||||
|
||||
- `hooks list`
|
||||
- `hooks info <name>`
|
||||
- `hooks check`
|
||||
- `hooks enable <name>`
|
||||
- `hooks disable <name>`
|
||||
- `hooks install <path-or-spec>` (deprecated alias for `openclaw plugins install`)
|
||||
- `hooks update [id]` (deprecated alias for `openclaw plugins update`)
|
||||
|
||||
Common options:
|
||||
|
||||
- `--json`
|
||||
- `--eligible`
|
||||
- `-v`, `--verbose`
|
||||
|
||||
Notes:
|
||||
|
||||
- Plugin-managed hooks cannot be enabled or disabled through `openclaw hooks`; enable or disable the owning plugin instead.
|
||||
- `hooks install` and `hooks update` still work as compatibility aliases, but they print deprecation warnings and forward to the plugin commands.
|
||||
|
||||
### `webhooks`
|
||||
|
||||
Webhook helpers. Current built-in surface is Gmail Pub/Sub setup + runner:
|
||||
|
||||
- `webhooks gmail setup`
|
||||
- `webhooks gmail run`
|
||||
|
||||
### `webhooks gmail`
|
||||
|
||||
Gmail Pub/Sub hook setup + runner. See [Gmail Pub/Sub](/automation/cron-jobs#gmail-pubsub-integration).
|
||||
@@ -885,31 +573,14 @@ Subcommands:
|
||||
- `webhooks gmail setup` (requires `--account <email>`; supports `--project`, `--topic`, `--subscription`, `--label`, `--hook-url`, `--hook-token`, `--push-token`, `--bind`, `--port`, `--path`, `--include-body`, `--max-bytes`, `--renew-minutes`, `--tailscale`, `--tailscale-path`, `--tailscale-target`, `--push-endpoint`, `--json`)
|
||||
- `webhooks gmail run` (runtime overrides for the same flags)
|
||||
|
||||
Notes:
|
||||
|
||||
- `setup` configures the Gmail watch plus the OpenClaw-facing push path.
|
||||
- `run` starts the local Gmail watcher/renew loop with optional runtime overrides.
|
||||
|
||||
### `dns`
|
||||
|
||||
Wide-area discovery DNS helpers (CoreDNS + Tailscale). Current built-in surface:
|
||||
|
||||
- `dns setup [--domain <domain>] [--apply]`
|
||||
|
||||
### `dns setup`
|
||||
|
||||
Wide-area discovery DNS helper (CoreDNS + Tailscale). See [/gateway/discovery](/gateway/discovery).
|
||||
|
||||
Options:
|
||||
|
||||
- `--domain <domain>`
|
||||
- `--apply`: install/update CoreDNS config (requires sudo; macOS only).
|
||||
|
||||
Notes:
|
||||
|
||||
- Without `--apply`, this is a planning helper that prints the recommended OpenClaw + Tailscale DNS config.
|
||||
- `--apply` currently supports macOS with Homebrew CoreDNS only.
|
||||
|
||||
## Messaging + agent
|
||||
|
||||
### `message`
|
||||
@@ -939,8 +610,6 @@ Examples:
|
||||
|
||||
Run one agent turn via the Gateway (or `--local` embedded).
|
||||
|
||||
Pass at least one session selector: `--to`, `--session-id`, or `--agent`.
|
||||
|
||||
Required:
|
||||
|
||||
- `-m, --message <text>`
|
||||
@@ -956,23 +625,15 @@ Options:
|
||||
- `--reply-to <target>` (delivery target override, separate from session routing)
|
||||
- `--reply-channel <channel>` (delivery channel override)
|
||||
- `--reply-account <id>` (delivery account id override)
|
||||
- `--local` (embedded run; plugin registry still preloads first)
|
||||
- `--local`
|
||||
- `--deliver`
|
||||
- `--json`
|
||||
- `--timeout <seconds>`
|
||||
|
||||
Notes:
|
||||
|
||||
- Gateway mode falls back to the embedded agent when the Gateway request fails.
|
||||
- `--local` still preloads the plugin registry, so plugin-provided providers, tools, and channels remain available during embedded runs.
|
||||
- `--channel`, `--reply-channel`, and `--reply-account` affect reply delivery, not routing.
|
||||
|
||||
### `agents`
|
||||
|
||||
Manage isolated agents (workspaces + auth + routing).
|
||||
|
||||
Running `openclaw agents` with no subcommand is equivalent to `openclaw agents list`.
|
||||
|
||||
#### `agents list`
|
||||
|
||||
List configured agents.
|
||||
@@ -996,7 +657,6 @@ Options:
|
||||
- `--json`
|
||||
|
||||
Binding specs use `channel[:accountId]`. When `accountId` is omitted, OpenClaw may resolve account scope via channel defaults/plugin hooks; otherwise it is a channel binding without explicit account scope.
|
||||
Passing any explicit add flags switches the command into the non-interactive path. `main` is reserved and cannot be used as the new agent id.
|
||||
|
||||
#### `agents bindings`
|
||||
|
||||
@@ -1013,7 +673,7 @@ Add routing bindings for an agent.
|
||||
|
||||
Options:
|
||||
|
||||
- `--agent <id>` (defaults to the current default agent)
|
||||
- `--agent <id>`
|
||||
- `--bind <channel[:accountId]>` (repeatable)
|
||||
- `--json`
|
||||
|
||||
@@ -1023,13 +683,11 @@ Remove routing bindings for an agent.
|
||||
|
||||
Options:
|
||||
|
||||
- `--agent <id>` (defaults to the current default agent)
|
||||
- `--agent <id>`
|
||||
- `--bind <channel[:accountId]>` (repeatable)
|
||||
- `--all`
|
||||
- `--json`
|
||||
|
||||
Use either `--all` or `--bind`, not both.
|
||||
|
||||
#### `agents delete <id>`
|
||||
|
||||
Delete an agent and prune its workspace + state.
|
||||
@@ -1039,146 +697,11 @@ Options:
|
||||
- `--force`
|
||||
- `--json`
|
||||
|
||||
Notes:
|
||||
|
||||
- `main` cannot be deleted.
|
||||
- Without `--force`, interactive confirmation is required.
|
||||
|
||||
#### `agents set-identity`
|
||||
|
||||
Update an agent identity (name/theme/emoji/avatar).
|
||||
|
||||
Options:
|
||||
|
||||
- `--agent <id>`
|
||||
- `--workspace <dir>`
|
||||
- `--identity-file <path>`
|
||||
- `--from-identity`
|
||||
- `--name <name>`
|
||||
- `--theme <theme>`
|
||||
- `--emoji <emoji>`
|
||||
- `--avatar <value>`
|
||||
- `--json`
|
||||
|
||||
Notes:
|
||||
|
||||
- `--agent` or `--workspace` can be used to select the target agent.
|
||||
- When no explicit identity fields are provided, the command reads `IDENTITY.md`.
|
||||
|
||||
### `acp`
|
||||
|
||||
Run the ACP bridge that connects IDEs to the Gateway.
|
||||
|
||||
Root options:
|
||||
|
||||
- `--url <url>`
|
||||
- `--token <token>`
|
||||
- `--token-file <path>`
|
||||
- `--password <password>`
|
||||
- `--password-file <path>`
|
||||
- `--session <key>`
|
||||
- `--session-label <label>`
|
||||
- `--require-existing`
|
||||
- `--reset-session`
|
||||
- `--no-prefix-cwd`
|
||||
- `--provenance <off|meta|meta+receipt>`
|
||||
- `--verbose`
|
||||
|
||||
#### `acp client`
|
||||
|
||||
Interactive ACP client for bridge debugging.
|
||||
|
||||
Options:
|
||||
|
||||
- `--cwd <dir>`
|
||||
- `--server <command>`
|
||||
- `--server-args <args...>`
|
||||
- `--server-verbose`
|
||||
- `--verbose`
|
||||
|
||||
See [`acp`](/cli/acp) for full behavior, security notes, and examples.
|
||||
|
||||
### `mcp`
|
||||
|
||||
Manage saved MCP server definitions and expose OpenClaw channels over MCP stdio.
|
||||
|
||||
#### `mcp serve`
|
||||
|
||||
Expose routed OpenClaw channel conversations over MCP stdio.
|
||||
|
||||
Options:
|
||||
|
||||
- `--url <url>`
|
||||
- `--token <token>`
|
||||
- `--token-file <path>`
|
||||
- `--password <password>`
|
||||
- `--password-file <path>`
|
||||
- `--claude-channel-mode <auto|on|off>`
|
||||
- `--verbose`
|
||||
|
||||
#### `mcp list`
|
||||
|
||||
List saved MCP server definitions.
|
||||
|
||||
Options:
|
||||
|
||||
- `--json`
|
||||
|
||||
#### `mcp show [name]`
|
||||
|
||||
Show one saved MCP server definition or the full saved MCP server object.
|
||||
|
||||
Options:
|
||||
|
||||
- `--json`
|
||||
|
||||
#### `mcp set <name> <value>`
|
||||
|
||||
Save one MCP server definition from a JSON object.
|
||||
|
||||
#### `mcp unset <name>`
|
||||
|
||||
Remove one saved MCP server definition.
|
||||
|
||||
### `approvals`
|
||||
|
||||
Manage exec approvals. Alias: `exec-approvals`.
|
||||
|
||||
#### `approvals get`
|
||||
|
||||
Fetch the exec approvals snapshot and effective policy.
|
||||
|
||||
Options:
|
||||
|
||||
- `--node <node>`
|
||||
- `--gateway`
|
||||
- `--json`
|
||||
- node RPC options from `openclaw nodes`
|
||||
|
||||
#### `approvals set`
|
||||
|
||||
Replace exec approvals with JSON from a file or stdin.
|
||||
|
||||
Options:
|
||||
|
||||
- `--node <node>`
|
||||
- `--gateway`
|
||||
- `--file <path>`
|
||||
- `--stdin`
|
||||
- `--json`
|
||||
- node RPC options from `openclaw nodes`
|
||||
|
||||
#### `approvals allowlist add|remove`
|
||||
|
||||
Edit the per-agent exec allowlist.
|
||||
|
||||
Options:
|
||||
|
||||
- `--node <node>`
|
||||
- `--gateway`
|
||||
- `--agent <id>` (defaults to `*`)
|
||||
- `--json`
|
||||
- node RPC options from `openclaw nodes`
|
||||
See [`acp`](/cli/acp) for full options and examples.
|
||||
|
||||
### `status`
|
||||
|
||||
@@ -1224,7 +747,6 @@ Options:
|
||||
- `--json`
|
||||
- `--timeout <ms>`
|
||||
- `--verbose`
|
||||
- `--debug` (alias for `--verbose`)
|
||||
|
||||
### `sessions`
|
||||
|
||||
@@ -1243,10 +765,6 @@ Subcommands:
|
||||
|
||||
- `sessions cleanup` — remove expired or orphaned sessions
|
||||
|
||||
Notes:
|
||||
|
||||
- `sessions cleanup` also supports `--fix-missing` to prune entries whose transcript files are gone.
|
||||
|
||||
## Reset / Uninstall
|
||||
|
||||
### `reset`
|
||||
@@ -1282,7 +800,6 @@ Options:
|
||||
Notes:
|
||||
|
||||
- `--non-interactive` requires `--yes` and explicit scopes (or `--all`).
|
||||
- `--all` removes service, state, workspace, and app together.
|
||||
|
||||
### `tasks`
|
||||
|
||||
@@ -1293,19 +810,10 @@ List and manage [background task](/automation/tasks) runs across agents.
|
||||
- `tasks notify <id>` — change notification policy for a task run
|
||||
- `tasks cancel <id>` — cancel a running task
|
||||
- `tasks audit` — surface operational issues (stale, lost, delivery failures)
|
||||
- `tasks maintenance` — preview or apply tasks and TaskFlow cleanup/reconciliation (ACP/subagent child sessions, active cron jobs, live CLI runs)
|
||||
- `tasks flow list` — list active and recent Task Flow flows
|
||||
- `tasks flow show <lookup>` — inspect a flow by id or lookup key
|
||||
- `tasks flow cancel <lookup>` — cancel a running flow and its active tasks
|
||||
|
||||
### `flows`
|
||||
|
||||
Legacy docs shortcut. Flow commands live under `openclaw tasks flow`:
|
||||
|
||||
- `tasks flow list [--json]`
|
||||
- `tasks flow show <lookup>`
|
||||
- `tasks flow cancel <lookup>`
|
||||
|
||||
## Gateway
|
||||
|
||||
### `gateway`
|
||||
@@ -1352,33 +860,13 @@ Notes:
|
||||
- `gateway status` probes the Gateway RPC by default using the service’s resolved port/config (override with `--url/--token/--password`).
|
||||
- `gateway status` supports `--no-probe`, `--deep`, `--require-rpc`, and `--json` for scripting.
|
||||
- `gateway status` also surfaces legacy or extra gateway services when it can detect them (`--deep` adds system-level scans). Profile-named OpenClaw services are treated as first-class and aren't flagged as "extra".
|
||||
- `gateway status` stays available for diagnostics even when the local CLI config is missing or invalid.
|
||||
- `gateway status` prints the resolved file log path, the CLI-vs-service config paths/validity snapshot, and the resolved probe target URL.
|
||||
- `gateway status` prints which config path the CLI uses vs which config the service likely uses (service env), plus the resolved probe target URL.
|
||||
- If gateway auth SecretRefs are unresolved in the current command path, `gateway status --json` reports `rpc.authWarning` only when probe connectivity/auth fails (warnings are suppressed when probe succeeds).
|
||||
- On Linux systemd installs, status token-drift checks include both `Environment=` and `EnvironmentFile=` unit sources.
|
||||
- `gateway install|uninstall|start|stop|restart` support `--json` for scripting (default output stays human-friendly).
|
||||
- `gateway install` defaults to Node runtime; bun is **not recommended** (WhatsApp/Telegram bugs).
|
||||
- `gateway install` options: `--port`, `--runtime`, `--token`, `--force`, `--json`.
|
||||
|
||||
### `daemon`
|
||||
|
||||
Legacy alias for the Gateway service-management commands. See [/cli/daemon](/cli/daemon).
|
||||
|
||||
Subcommands:
|
||||
|
||||
- `daemon status`
|
||||
- `daemon install`
|
||||
- `daemon uninstall`
|
||||
- `daemon start`
|
||||
- `daemon stop`
|
||||
- `daemon restart`
|
||||
|
||||
Common options:
|
||||
|
||||
- `status`: `--url`, `--token`, `--password`, `--timeout`, `--no-probe`, `--require-rpc`, `--deep`, `--json`
|
||||
- `install`: `--port`, `--runtime <node|bun>`, `--token`, `--force`, `--json`
|
||||
- `uninstall|start|stop|restart`: `--json`
|
||||
|
||||
### `logs`
|
||||
|
||||
Tail Gateway file logs via RPC.
|
||||
@@ -1393,10 +881,6 @@ Options:
|
||||
- `--json`: emit line-delimited JSON
|
||||
- `--plain`: disable structured formatting
|
||||
- `--no-color`: disable ANSI colors
|
||||
- `--url <url>`: explicit Gateway WebSocket URL
|
||||
- `--token <token>`: Gateway token
|
||||
- `--timeout <ms>`: Gateway RPC timeout
|
||||
- `--expect-final`: wait for a final response when needed
|
||||
|
||||
Examples:
|
||||
|
||||
@@ -1408,11 +892,6 @@ openclaw logs --json
|
||||
openclaw logs --no-color
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- If you pass `--url`, the CLI does not auto-apply config or environment credentials.
|
||||
- Local loopback pairing failures fall back to the configured local log file; explicit `--url` targets do not.
|
||||
|
||||
### `gateway <subcommand>`
|
||||
|
||||
Gateway CLI helpers (use `--url`, `--token`, `--password`, `--timeout`, `--expect-final` for RPC subcommands).
|
||||
@@ -1421,7 +900,7 @@ Include `--token` or `--password` explicitly. Missing explicit credentials is an
|
||||
|
||||
Subcommands:
|
||||
|
||||
- `gateway call <method> [--params <json>] [--url <url>] [--token <token>] [--password <password>] [--timeout <ms>] [--expect-final] [--json]`
|
||||
- `gateway call <method> [--params <json>]`
|
||||
- `gateway health`
|
||||
- `gateway status`
|
||||
- `gateway probe`
|
||||
@@ -1444,13 +923,17 @@ Tip: these config write RPCs preflight active SecretRef resolution for refs in t
|
||||
|
||||
See [/concepts/models](/concepts/models) for fallback behavior and scanning strategy.
|
||||
|
||||
Billing note: Anthropic changed third-party harness billing on **April 4, 2026
|
||||
at 12:00 PM PT / 8:00 PM BST**. Anthropic says Claude subscription limits no
|
||||
longer cover OpenClaw, and Claude CLI usage in OpenClaw now requires **Extra
|
||||
Usage** billed separately from the subscription. For production, prefer an
|
||||
Anthropic API key or another supported subscription-style provider such as
|
||||
OpenAI Codex, Alibaba Cloud Model Studio Coding Plan, MiniMax Coding Plan, or
|
||||
Z.AI / GLM Coding Plan.
|
||||
Anthropic setup-token (supported):
|
||||
|
||||
```bash
|
||||
claude setup-token
|
||||
openclaw models auth setup-token --provider anthropic
|
||||
openclaw models status
|
||||
```
|
||||
|
||||
Policy note: this is technical compatibility. Anthropic has blocked some
|
||||
subscription usage outside Claude Code in the past; verify current Anthropic
|
||||
terms before relying on setup-token in production.
|
||||
|
||||
Anthropic Claude CLI migration:
|
||||
|
||||
@@ -1458,13 +941,7 @@ Anthropic Claude CLI migration:
|
||||
openclaw models auth login --provider anthropic --method cli --set-default
|
||||
```
|
||||
|
||||
Onboarding shortcut: `openclaw onboard --auth-choice anthropic-cli`
|
||||
|
||||
Existing legacy Anthropic token profiles still run if already configured, but
|
||||
OpenClaw no longer offers Anthropic setup-token as a new auth path.
|
||||
|
||||
Legacy alias note: `claude-cli` is the deprecated onboarding auth-choice alias.
|
||||
Use `anthropic-cli` for onboarding, or use `models auth login` directly.
|
||||
Note: `--auth-choice anthropic-cli` is a deprecated legacy alias. Use `models auth login` instead.
|
||||
|
||||
### `models` (root)
|
||||
|
||||
@@ -1557,17 +1034,12 @@ Options:
|
||||
|
||||
Options:
|
||||
|
||||
- `add`: interactive auth helper (provider auth flow or token paste)
|
||||
- `add`: interactive auth helper
|
||||
- `login`: `--provider <name>`, `--method <method>`, `--set-default`
|
||||
- `login-github-copilot`: GitHub Copilot OAuth login flow (`--yes`)
|
||||
- `setup-token`: `--provider <name>`, `--yes`
|
||||
- `login-github-copilot`: GitHub Copilot OAuth login flow
|
||||
- `setup-token`: `--provider <name>` (default `anthropic`), `--yes`
|
||||
- `paste-token`: `--provider <name>`, `--profile-id <id>`, `--expires-in <duration>`
|
||||
|
||||
Notes:
|
||||
|
||||
- `setup-token` and `paste-token` are generic token commands for providers that expose token auth methods.
|
||||
- Anthropic legacy token profiles still run if already configured, but Anthropic no longer supports `setup-token` or `paste-token` as a new OpenClaw auth path.
|
||||
|
||||
### `models auth order get|set|clear`
|
||||
|
||||
Options:
|
||||
@@ -1624,14 +1096,12 @@ Subcommands:
|
||||
- `cron enable <id>`
|
||||
- `cron disable <id>`
|
||||
- `cron runs --id <id> [--limit <n>]`
|
||||
- `cron run <id> [--due]`
|
||||
- `cron run <id> [--force]`
|
||||
|
||||
All `cron` commands accept `--url`, `--token`, `--timeout`, `--expect-final`.
|
||||
|
||||
## Node host
|
||||
|
||||
### `node`
|
||||
|
||||
`node` runs a **headless node host** or manages it as a background service. See
|
||||
[`openclaw node`](/cli/node).
|
||||
|
||||
@@ -1696,7 +1166,7 @@ Browser control CLI (dedicated Chrome/Brave/Edge/Chromium). See [`openclaw brows
|
||||
|
||||
Common options:
|
||||
|
||||
- `--url`, `--token`, `--timeout`, `--expect-final`, `--json`
|
||||
- `--url`, `--token`, `--timeout`, `--json`
|
||||
- `--browser-profile <name>`
|
||||
|
||||
Manage:
|
||||
@@ -1710,7 +1180,7 @@ Manage:
|
||||
- `browser focus <targetId>`
|
||||
- `browser close [targetId]`
|
||||
- `browser profiles`
|
||||
- `browser create-profile --name <name> [--color <hex>] [--cdp-url <url>] [--driver existing-session] [--user-data-dir <path>]`
|
||||
- `browser create-profile --name <name> [--color <hex>] [--cdp-url <url>]`
|
||||
- `browser delete-profile --name <name>`
|
||||
|
||||
Inspect:
|
||||
@@ -1736,30 +1206,8 @@ Actions:
|
||||
- `browser console [--level <error|warn|info>] [--target-id <id>]`
|
||||
- `browser pdf [--target-id <id>]`
|
||||
|
||||
## Voice call
|
||||
|
||||
### `voicecall`
|
||||
|
||||
Plugin-provided voice-call utilities. Only appears when the voice-call plugin is installed and enabled. See [`openclaw voicecall`](/cli/voicecall).
|
||||
|
||||
Common commands:
|
||||
|
||||
- `voicecall call --to <phone> --message <text> [--mode notify|conversation]`
|
||||
- `voicecall start --to <phone> [--message <text>] [--mode notify|conversation]`
|
||||
- `voicecall continue --call-id <id> --message <text>`
|
||||
- `voicecall speak --call-id <id> --message <text>`
|
||||
- `voicecall end --call-id <id>`
|
||||
- `voicecall status --call-id <id>`
|
||||
- `voicecall tail [--file <path>] [--since <n>] [--poll <ms>]`
|
||||
- `voicecall latency [--file <path>] [--last <n>]`
|
||||
- `voicecall expose [--mode off|serve|funnel] [--path <path>] [--port <port>] [--serve-path <path>]`
|
||||
|
||||
## Docs search
|
||||
|
||||
### `docs`
|
||||
|
||||
Search the live OpenClaw docs index.
|
||||
|
||||
### `docs [query...]`
|
||||
|
||||
Search the live docs index.
|
||||
|
||||
@@ -13,47 +13,16 @@ Tail Gateway file logs over RPC (works in remote mode).
|
||||
Related:
|
||||
|
||||
- Logging overview: [Logging](/logging)
|
||||
- Gateway CLI: [gateway](/cli/gateway)
|
||||
|
||||
## Options
|
||||
|
||||
- `--limit <n>`: maximum number of log lines to return (default `200`)
|
||||
- `--max-bytes <n>`: maximum bytes to read from the log file (default `250000`)
|
||||
- `--follow`: follow the log stream
|
||||
- `--interval <ms>`: polling interval while following (default `1000`)
|
||||
- `--json`: emit line-delimited JSON events
|
||||
- `--plain`: plain text output without styled formatting
|
||||
- `--no-color`: disable ANSI colors
|
||||
- `--local-time`: render timestamps in your local timezone
|
||||
|
||||
## Shared Gateway RPC options
|
||||
|
||||
`openclaw logs` also accepts the standard Gateway client flags:
|
||||
|
||||
- `--url <url>`: Gateway WebSocket URL
|
||||
- `--token <token>`: Gateway token
|
||||
- `--timeout <ms>`: timeout in ms (default `30000`)
|
||||
- `--expect-final`: wait for a final response when the Gateway call is agent-backed
|
||||
|
||||
When you pass `--url`, the CLI does not auto-apply config or environment credentials. Include `--token` explicitly if the target Gateway requires auth.
|
||||
|
||||
## Examples
|
||||
|
||||
```bash
|
||||
openclaw logs
|
||||
openclaw logs --follow
|
||||
openclaw logs --follow --interval 2000
|
||||
openclaw logs --limit 500 --max-bytes 500000
|
||||
openclaw logs --json
|
||||
openclaw logs --plain
|
||||
openclaw logs --no-color
|
||||
openclaw logs --limit 500
|
||||
openclaw logs --local-time
|
||||
openclaw logs --follow --local-time
|
||||
openclaw logs --url ws://127.0.0.1:18789 --token "$OPENCLAW_GATEWAY_TOKEN"
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- Use `--local-time` to render timestamps in your local timezone.
|
||||
- If the local loopback Gateway asks for pairing, `openclaw logs` falls back to the configured local log file automatically. Explicit `--url` targets do not use this fallback.
|
||||
Use `--local-time` to render timestamps in your local timezone.
|
||||
|
||||
@@ -382,13 +382,6 @@ Commands:
|
||||
- `openclaw mcp set <name> <json>`
|
||||
- `openclaw mcp unset <name>`
|
||||
|
||||
Notes:
|
||||
|
||||
- `list` sorts server names.
|
||||
- `show` without a name prints the full configured MCP server object.
|
||||
- `set` expects one JSON object value on the command line.
|
||||
- `unset` fails if the named server does not exist.
|
||||
|
||||
Examples:
|
||||
|
||||
```bash
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
---
|
||||
summary: "CLI reference for `openclaw memory` (status/index/search/promote)"
|
||||
summary: "CLI reference for `openclaw memory` (status/index/search)"
|
||||
read_when:
|
||||
- You want to index or search semantic memory
|
||||
- You’re debugging memory availability or indexing
|
||||
- You want to promote recalled short-term memory into `MEMORY.md`
|
||||
title: "memory"
|
||||
---
|
||||
|
||||
@@ -22,13 +21,9 @@ Related:
|
||||
```bash
|
||||
openclaw memory status
|
||||
openclaw memory status --deep
|
||||
openclaw memory status --fix
|
||||
openclaw memory index --force
|
||||
openclaw memory search "meeting notes"
|
||||
openclaw memory search --query "deployment" --max-results 20
|
||||
openclaw memory promote --limit 10 --min-score 0.75
|
||||
openclaw memory promote --apply
|
||||
openclaw memory promote --json --min-recall-count 0 --min-unique-queries 0
|
||||
openclaw memory status --json
|
||||
openclaw memory status --deep --index
|
||||
openclaw memory status --deep --index --verbose
|
||||
@@ -47,7 +42,6 @@ openclaw memory index --agent main --verbose
|
||||
|
||||
- `--deep`: probe vector + embedding availability.
|
||||
- `--index`: run a reindex if the store is dirty (implies `--deep`).
|
||||
- `--fix`: repair stale recall locks and normalize promotion metadata.
|
||||
- `--json`: print JSON output.
|
||||
|
||||
`memory index`:
|
||||
@@ -64,72 +58,9 @@ openclaw memory index --agent main --verbose
|
||||
- `--min-score <n>`: filter out low-score matches.
|
||||
- `--json`: print JSON results.
|
||||
|
||||
`memory promote`:
|
||||
|
||||
Preview and apply short-term memory promotions.
|
||||
|
||||
```bash
|
||||
openclaw memory promote [--apply] [--limit <n>] [--include-promoted]
|
||||
```
|
||||
|
||||
- `--apply` -- write promotions to `MEMORY.md` (default: preview only).
|
||||
- `--limit <n>` -- cap the number of candidates shown.
|
||||
- `--include-promoted` -- include entries already promoted in previous cycles.
|
||||
|
||||
Full options:
|
||||
|
||||
- Ranks short-term candidates from `memory/YYYY-MM-DD.md` using weighted recall signals (`frequency`, `relevance`, `query diversity`, `recency`).
|
||||
- Uses recall events captured when `memory_search` returns daily-memory hits.
|
||||
- Optional auto-dreaming mode: when `plugins.entries.memory-core.config.dreaming.mode` is `core`, `deep`, or `rem`, `memory-core` auto-manages a cron job that triggers promotion in the background (no manual `openclaw cron add` required).
|
||||
- `--agent <id>`: scope to a single agent (default: the default agent).
|
||||
- `--limit <n>`: max candidates to return/apply.
|
||||
- `--min-score <n>`: minimum weighted promotion score.
|
||||
- `--min-recall-count <n>`: minimum recall count required for a candidate.
|
||||
- `--min-unique-queries <n>`: minimum distinct query count required for a candidate.
|
||||
- `--apply`: append selected candidates into `MEMORY.md` and mark them promoted.
|
||||
- `--include-promoted`: include already promoted candidates in output.
|
||||
- `--json`: print JSON output.
|
||||
|
||||
## Dreaming (experimental)
|
||||
|
||||
Dreaming is the overnight reflection pass for memory. It is called "dreaming" because the system revisits what was recalled during the day and decides what is worth keeping long-term.
|
||||
|
||||
- It is opt-in and disabled by default.
|
||||
- Enable it with `plugins.entries.memory-core.config.dreaming.mode`.
|
||||
- You can toggle modes from chat with `/dreaming off|core|rem|deep`. Run `/dreaming` (or `/dreaming options`) to see what each mode does.
|
||||
- When enabled, `memory-core` automatically creates and maintains a managed cron job.
|
||||
- Set `dreaming.limit` to `0` if you want dreaming enabled but automatic promotion effectively paused.
|
||||
- Ranking uses weighted signals: recall frequency, retrieval relevance, query diversity, and temporal recency (recent recalls decay over time).
|
||||
- Promotion into `MEMORY.md` only happens when quality thresholds are met, so long-term memory stays high signal instead of collecting one-off details.
|
||||
|
||||
Default mode presets:
|
||||
|
||||
- `core`: daily at `0 3 * * *`, `minScore=0.75`, `minRecallCount=3`, `minUniqueQueries=2`
|
||||
- `deep`: every 12 hours (`0 */12 * * *`), `minScore=0.8`, `minRecallCount=3`, `minUniqueQueries=3`
|
||||
- `rem`: every 6 hours (`0 */6 * * *`), `minScore=0.85`, `minRecallCount=4`, `minUniqueQueries=3`
|
||||
|
||||
Example:
|
||||
|
||||
```json
|
||||
{
|
||||
"plugins": {
|
||||
"entries": {
|
||||
"memory-core": {
|
||||
"config": {
|
||||
"dreaming": {
|
||||
"mode": "core"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- `memory index --verbose` prints per-phase details (provider, model, sources, batch activity).
|
||||
- `memory status` includes any extra paths configured via `memorySearch.extraPaths`.
|
||||
- If effectively active memory remote API key fields are configured as SecretRefs, the command resolves those values from the active gateway snapshot. If gateway is unavailable, the command fails fast.
|
||||
- Gateway version skew note: this command path requires a gateway that supports `secrets.resolve`; older gateways return an unknown-method error.
|
||||
- Dreaming cadence defaults to each mode's preset schedule. Override cadence with `plugins.entries.memory-core.config.dreaming.frequency` as a cron expression (for example `0 3 * * *`) and fine-tune with `timezone`, `limit`, `minScore`, `minRecallCount`, and `minUniqueQueries`.
|
||||
|
||||
@@ -68,15 +68,11 @@ Name lookup:
|
||||
- `send`
|
||||
- Channels: WhatsApp/Telegram/Discord/Google Chat/Slack/Mattermost (plugin)/Signal/iMessage/Matrix/Microsoft Teams
|
||||
- Required: `--target`, plus `--message` or `--media`
|
||||
- Optional: `--media`, `--interactive`, `--buttons`, `--components`, `--card`, `--reply-to`, `--thread-id`, `--gif-playback`, `--force-document`, `--silent`
|
||||
- Shared interactive payloads: `--interactive` sends a channel-native interactive JSON payload when supported
|
||||
- Optional: `--media`, `--reply-to`, `--thread-id`, `--gif-playback`
|
||||
- Telegram only: `--buttons` (requires `channels.telegram.capabilities.inlineButtons` to allow it)
|
||||
- Telegram only: `--force-document` (send images and GIFs as documents to avoid Telegram compression)
|
||||
- Telegram only: `--thread-id` (forum topic id)
|
||||
- Slack only: `--thread-id` (thread timestamp; `--reply-to` uses the same field)
|
||||
- Discord only: `--components` JSON payload
|
||||
- Adaptive-card channels: `--card` JSON payload when supported
|
||||
- Telegram + Discord: `--silent`
|
||||
- WhatsApp only: `--gif-playback`
|
||||
|
||||
- `poll`
|
||||
@@ -196,7 +192,7 @@ Name lookup:
|
||||
|
||||
- `broadcast`
|
||||
- Channels: any configured channel; use `--channel all` to target all providers
|
||||
- Required: `--targets <target...>`
|
||||
- Required: `--targets` (repeat)
|
||||
- Optional: `--message`, `--media`, `--dry-run`
|
||||
|
||||
## Examples
|
||||
@@ -218,14 +214,6 @@ openclaw message send --channel discord \
|
||||
|
||||
See [Discord components](/channels/discord#interactive-components) for the full schema.
|
||||
|
||||
Send a shared interactive payload:
|
||||
|
||||
```bash
|
||||
openclaw message send --channel googlechat --target spaces/AAA... \
|
||||
--message "Choose:" \
|
||||
--interactive '{"text":"Choose a path","blocks":[{"type":"actions","buttons":[{"label":"Approve"},{"label":"Decline"}]}]}'
|
||||
```
|
||||
|
||||
Create a Discord poll:
|
||||
|
||||
```
|
||||
@@ -284,14 +272,6 @@ openclaw message send --channel telegram --target @mychat --message "Choose:" \
|
||||
--buttons '[ [{"text":"Yes","callback_data":"cmd:yes"}], [{"text":"No","callback_data":"cmd:no"}] ]'
|
||||
```
|
||||
|
||||
Send a Teams Adaptive Card:
|
||||
|
||||
```bash
|
||||
openclaw message send --channel msteams \
|
||||
--target conversation:19:abc@thread.tacv2 \
|
||||
--card '{"type":"AdaptiveCard","version":"1.5","body":[{"type":"TextBlock","text":"Status update"}]}'
|
||||
```
|
||||
|
||||
Send a Telegram image as a document to avoid compression:
|
||||
|
||||
```bash
|
||||
|
||||
@@ -26,7 +26,7 @@ openclaw models scan
|
||||
|
||||
`openclaw models status` shows the resolved default/fallbacks plus an auth overview.
|
||||
When provider usage snapshots are available, the OAuth/token status section includes
|
||||
provider usage windows and quota snapshots.
|
||||
provider usage headers.
|
||||
Add `--probe` to run live auth probes against each configured provider profile.
|
||||
Probes are real requests (may consume tokens and trigger rate limits).
|
||||
Use `--agent <id>` to inspect a configured agent’s model/auth state. When omitted,
|
||||
@@ -67,14 +67,10 @@ openclaw models fallbacks list
|
||||
```bash
|
||||
openclaw models auth add
|
||||
openclaw models auth login --provider <id>
|
||||
openclaw models auth setup-token --provider <id>
|
||||
openclaw models auth setup-token
|
||||
openclaw models auth paste-token
|
||||
```
|
||||
|
||||
`models auth add` is the interactive auth helper. It can launch a provider auth
|
||||
flow (OAuth/API key) or guide you into manual token paste, depending on the
|
||||
provider you choose.
|
||||
|
||||
`models auth login` runs a provider plugin’s auth flow (OAuth/API key). Use
|
||||
`openclaw plugins list` to see which providers are installed.
|
||||
|
||||
@@ -89,8 +85,6 @@ Notes:
|
||||
|
||||
- `login --provider anthropic --method cli --set-default` reuses a local Claude
|
||||
CLI login and rewrites the main Anthropic default-model path to `claude-cli/...`.
|
||||
- `setup-token` and `paste-token` remain generic token commands for providers
|
||||
that expose token auth methods.
|
||||
- `setup-token` prompts for a setup-token value (generate it with `claude setup-token` on any machine).
|
||||
- `paste-token` accepts a token string generated elsewhere or from automation.
|
||||
- Anthropic billing note: Anthropic changed third-party harness billing on **April 4, 2026 at 12:00 PM PT / 8:00 PM BST**. Anthropic says Claude subscription limits no longer cover OpenClaw, and Claude CLI traffic in OpenClaw now requires **Extra Usage** billed separately from the subscription.
|
||||
- Existing legacy Anthropic token profiles still run if already configured, but Anthropic no longer supports `setup-token` or `paste-token` as a new OpenClaw auth path.
|
||||
- Anthropic policy note: setup-token support is technical compatibility. Anthropic has blocked some subscription usage outside Claude Code in the past, so verify current terms before using it broadly.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
summary: "CLI reference for `openclaw nodes` (status, pairing, invoke, camera/canvas/screen)"
|
||||
summary: "CLI reference for `openclaw nodes` (list/status/approve/invoke, camera/canvas/screen)"
|
||||
read_when:
|
||||
- You’re managing paired nodes (cameras, screen, canvas)
|
||||
- You need to approve requests or invoke node commands
|
||||
@@ -28,8 +28,6 @@ openclaw nodes list --connected
|
||||
openclaw nodes list --last-connected 24h
|
||||
openclaw nodes pending
|
||||
openclaw nodes approve <requestId>
|
||||
openclaw nodes reject <requestId>
|
||||
openclaw nodes rename --node <id|name|ip> --name <displayName>
|
||||
openclaw nodes status
|
||||
openclaw nodes status --connected
|
||||
openclaw nodes status --last-connected 24h
|
||||
|
||||
@@ -142,12 +142,9 @@ Flow notes:
|
||||
|
||||
- `quickstart`: minimal prompts, auto-generates a gateway token.
|
||||
- `manual`: full prompts for port/bind/auth (alias of `advanced`).
|
||||
- In the web-search step, some providers can trigger provider-specific
|
||||
follow-up prompts:
|
||||
- **Grok** can offer optional `x_search` setup with the same `XAI_API_KEY`
|
||||
and an `x_search` model choice.
|
||||
- **Kimi** can ask for the Moonshot API region (`api.moonshot.ai` vs
|
||||
`api.moonshot.cn`) and the default Kimi web-search model.
|
||||
- In the web-search step, choosing **Grok** can trigger a separate follow-up
|
||||
prompt to enable `x_search` with the same `XAI_API_KEY` and optionally pick
|
||||
an `x_search` model. Other web-search providers do not show that prompt.
|
||||
- Local onboarding DM scope behavior: [CLI Setup Reference](/start/wizard-cli-reference#outputs-and-internals).
|
||||
- Fastest first chat: `openclaw dashboard` (Control UI, no channel setup).
|
||||
- Custom Provider: connect any OpenAI or Anthropic compatible endpoint,
|
||||
|
||||
@@ -20,43 +20,10 @@ openclaw pairing list telegram
|
||||
openclaw pairing list --channel telegram --account work
|
||||
openclaw pairing list telegram --json
|
||||
|
||||
openclaw pairing approve <code>
|
||||
openclaw pairing approve telegram <code>
|
||||
openclaw pairing approve --channel telegram --account work <code> --notify
|
||||
```
|
||||
|
||||
## `pairing list`
|
||||
|
||||
List pending pairing requests for one channel.
|
||||
|
||||
Options:
|
||||
|
||||
- `[channel]`: positional channel id
|
||||
- `--channel <channel>`: explicit channel id
|
||||
- `--account <accountId>`: account id for multi-account channels
|
||||
- `--json`: machine-readable output
|
||||
|
||||
Notes:
|
||||
|
||||
- If multiple pairing-capable channels are configured, you must provide a channel either positionally or with `--channel`.
|
||||
- Extension channels are allowed as long as the channel id is valid.
|
||||
|
||||
## `pairing approve`
|
||||
|
||||
Approve a pending pairing code and allow that sender.
|
||||
|
||||
Usage:
|
||||
|
||||
- `openclaw pairing approve <channel> <code>`
|
||||
- `openclaw pairing approve --channel <channel> <code>`
|
||||
- `openclaw pairing approve <code>` when exactly one pairing-capable channel is configured
|
||||
|
||||
Options:
|
||||
|
||||
- `--channel <channel>`: explicit channel id
|
||||
- `--account <accountId>`: account id for multi-account channels
|
||||
- `--notify`: send a confirmation back to the requester on the same channel
|
||||
|
||||
## Notes
|
||||
|
||||
- Channel input: pass it positionally (`pairing list telegram`) or with `--channel <channel>`.
|
||||
|
||||
@@ -32,9 +32,8 @@ openclaw plugins update --all
|
||||
openclaw plugins marketplace list <marketplace>
|
||||
```
|
||||
|
||||
Bundled plugins ship with OpenClaw. Some are enabled by default (for example
|
||||
bundled model providers, bundled speech providers, and the bundled browser
|
||||
plugin); others require `plugins enable`.
|
||||
Bundled plugins ship with OpenClaw but start disabled. Use `plugins enable` to
|
||||
activate them.
|
||||
|
||||
Native OpenClaw plugins must ship `openclaw.plugin.json` with an inline JSON
|
||||
Schema (`configSchema`, even if empty). Compatible bundles use their own bundle
|
||||
|
||||
@@ -10,26 +10,11 @@ title: "reset"
|
||||
|
||||
Reset local config/state (keeps the CLI installed).
|
||||
|
||||
Options:
|
||||
|
||||
- `--scope <scope>`: `config`, `config+creds+sessions`, or `full`
|
||||
- `--yes`: skip confirmation prompts
|
||||
- `--non-interactive`: disable prompts; requires `--scope` and `--yes`
|
||||
- `--dry-run`: print actions without removing files
|
||||
|
||||
Examples:
|
||||
|
||||
```bash
|
||||
openclaw backup create
|
||||
openclaw reset
|
||||
openclaw reset --dry-run
|
||||
openclaw reset --scope config --yes --non-interactive
|
||||
openclaw reset --scope config+creds+sessions --yes --non-interactive
|
||||
openclaw reset --scope full --yes --non-interactive
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- Run `openclaw backup create` first if you want a restorable snapshot before removing local state.
|
||||
- If you omit `--scope`, `openclaw reset` uses an interactive prompt to choose what to remove.
|
||||
- `--non-interactive` is only valid when both `--scope` and `--yes` are set.
|
||||
Run `openclaw backup create` first if you want a restorable snapshot before removing local state.
|
||||
|
||||
@@ -49,7 +49,6 @@ Re-resolve secret refs and atomically swap runtime snapshot.
|
||||
```bash
|
||||
openclaw secrets reload
|
||||
openclaw secrets reload --json
|
||||
openclaw secrets reload --url ws://127.0.0.1:18789 --token <token>
|
||||
```
|
||||
|
||||
Notes:
|
||||
@@ -58,13 +57,6 @@ Notes:
|
||||
- If resolution fails, gateway keeps last-known-good snapshot and returns an error (no partial activation).
|
||||
- JSON response includes `warningCount`.
|
||||
|
||||
Options:
|
||||
|
||||
- `--url <url>`
|
||||
- `--token <token>`
|
||||
- `--timeout <ms>`
|
||||
- `--json`
|
||||
|
||||
## Audit
|
||||
|
||||
Scan OpenClaw state for:
|
||||
@@ -142,7 +134,6 @@ Notes:
|
||||
- Apply path is one-way for scrubbed plaintext values.
|
||||
- Without `--apply`, CLI still prompts `Apply this plan now?` after preflight.
|
||||
- With `--apply` (and no `--yes`), CLI prompts an extra irreversible confirmation.
|
||||
- `--json` prints the plan + preflight report, but the command still requires an interactive TTY.
|
||||
|
||||
Exec provider safety note:
|
||||
|
||||
|
||||
@@ -14,14 +14,12 @@ openclaw sessions
|
||||
openclaw sessions --agent work
|
||||
openclaw sessions --all-agents
|
||||
openclaw sessions --active 120
|
||||
openclaw sessions --verbose
|
||||
openclaw sessions --json
|
||||
```
|
||||
|
||||
Scope selection:
|
||||
|
||||
- default: configured default agent store
|
||||
- `--verbose`: verbose logging
|
||||
- `--agent <id>`: one configured agent store
|
||||
- `--all-agents`: aggregate all configured agent stores
|
||||
- `--store <path>`: explicit store path (cannot be combined with `--agent` or `--all-agents`)
|
||||
@@ -73,7 +71,6 @@ openclaw sessions cleanup --json
|
||||
- `--dry-run`: preview how many entries would be pruned/capped without writing.
|
||||
- In text mode, dry-run prints a per-session action table (`Action`, `Key`, `Age`, `Model`, `Flags`) so you can see what would be kept vs removed.
|
||||
- `--enforce`: apply maintenance even when `session.maintenance.mode` is `warn`.
|
||||
- `--fix-missing`: remove entries whose transcript files are missing, even if they would not normally age/count out yet.
|
||||
- `--active-key <key>`: protect a specific active key from disk-budget eviction.
|
||||
- `--agent <id>`: run cleanup for one configured agent store.
|
||||
- `--all-agents`: run cleanup for all configured agent stores.
|
||||
|
||||
@@ -20,26 +20,10 @@ Related:
|
||||
```bash
|
||||
openclaw setup
|
||||
openclaw setup --workspace ~/.openclaw/workspace
|
||||
openclaw setup --wizard
|
||||
openclaw setup --non-interactive --mode remote --remote-url wss://gateway-host:18789 --remote-token <token>
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
- `--workspace <dir>`: agent workspace directory (stored as `agents.defaults.workspace`)
|
||||
- `--wizard`: run onboarding
|
||||
- `--non-interactive`: run onboarding without prompts
|
||||
- `--mode <local|remote>`: onboarding mode
|
||||
- `--remote-url <url>`: remote Gateway WebSocket URL
|
||||
- `--remote-token <token>`: remote Gateway token
|
||||
|
||||
To run onboarding via setup:
|
||||
|
||||
```bash
|
||||
openclaw setup --wizard
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- Plain `openclaw setup` initializes config + workspace without the full onboarding flow.
|
||||
- Onboarding auto-runs when any onboarding flags are present (`--wizard`, `--non-interactive`, `--mode`, `--remote-url`, `--remote-token`).
|
||||
|
||||
@@ -19,7 +19,7 @@ openclaw status --usage
|
||||
|
||||
Notes:
|
||||
|
||||
- `--deep` runs live probes (WhatsApp Web + Telegram + Discord + Slack + Signal).
|
||||
- `--deep` runs live probes (WhatsApp Web + Telegram + Discord + Google Chat + Slack + Signal).
|
||||
- Output includes per-agent session stores when multiple agents are configured.
|
||||
- Overview includes Gateway + node host service install/runtime status when available.
|
||||
- Overview includes update channel + git SHA (for source checkouts).
|
||||
|
||||
@@ -12,18 +12,10 @@ title: "system"
|
||||
System-level helpers for the Gateway: enqueue system events, control heartbeats,
|
||||
and view presence.
|
||||
|
||||
All `system` subcommands use Gateway RPC and accept the shared client flags:
|
||||
|
||||
- `--url <url>`
|
||||
- `--token <token>`
|
||||
- `--timeout <ms>`
|
||||
- `--expect-final`
|
||||
|
||||
## Common commands
|
||||
|
||||
```bash
|
||||
openclaw system event --text "Check for urgent follow-ups" --mode now
|
||||
openclaw system event --text "Check for urgent follow-ups" --url ws://127.0.0.1:18789 --token "$OPENCLAW_GATEWAY_TOKEN"
|
||||
openclaw system heartbeat enable
|
||||
openclaw system heartbeat last
|
||||
openclaw system presence
|
||||
@@ -40,7 +32,6 @@ Flags:
|
||||
- `--text <text>`: required system event text.
|
||||
- `--mode <mode>`: `now` or `next-heartbeat` (default).
|
||||
- `--json`: machine-readable output.
|
||||
- `--url`, `--token`, `--timeout`, `--expect-final`: shared Gateway RPC flags.
|
||||
|
||||
## `system heartbeat last|enable|disable`
|
||||
|
||||
@@ -53,7 +44,6 @@ Heartbeat controls:
|
||||
Flags:
|
||||
|
||||
- `--json`: machine-readable output.
|
||||
- `--url`, `--token`, `--timeout`, `--expect-final`: shared Gateway RPC flags.
|
||||
|
||||
## `system presence`
|
||||
|
||||
@@ -63,7 +53,6 @@ instances, and similar status lines).
|
||||
Flags:
|
||||
|
||||
- `--json`: machine-readable output.
|
||||
- `--url`, `--token`, `--timeout`, `--expect-final`: shared Gateway RPC flags.
|
||||
|
||||
## Notes
|
||||
|
||||
|
||||
@@ -10,30 +10,11 @@ title: "uninstall"
|
||||
|
||||
Uninstall the gateway service + local data (CLI remains).
|
||||
|
||||
Options:
|
||||
|
||||
- `--service`: remove the gateway service
|
||||
- `--state`: remove state and config
|
||||
- `--workspace`: remove workspace directories
|
||||
- `--app`: remove the macOS app
|
||||
- `--all`: remove service, state, workspace, and app
|
||||
- `--yes`: skip confirmation prompts
|
||||
- `--non-interactive`: disable prompts; requires `--yes`
|
||||
- `--dry-run`: print actions without removing files
|
||||
|
||||
Examples:
|
||||
|
||||
```bash
|
||||
openclaw backup create
|
||||
openclaw uninstall
|
||||
openclaw uninstall --service --yes --non-interactive
|
||||
openclaw uninstall --state --workspace --yes --non-interactive
|
||||
openclaw uninstall --all --yes
|
||||
openclaw uninstall --dry-run
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- Run `openclaw backup create` first if you want a restorable snapshot before removing state or workspaces.
|
||||
- `--all` is shorthand for removing service, state, workspace, and app together.
|
||||
- `--non-interactive` requires `--yes`.
|
||||
Run `openclaw backup create` first if you want a restorable snapshot before removing state or workspaces.
|
||||
|
||||
@@ -24,7 +24,6 @@ openclaw update --tag beta
|
||||
openclaw update --tag main
|
||||
openclaw update --dry-run
|
||||
openclaw update --no-restart
|
||||
openclaw update --yes
|
||||
openclaw update --json
|
||||
openclaw --update
|
||||
```
|
||||
@@ -37,7 +36,6 @@ openclaw --update
|
||||
- `--dry-run`: preview planned update actions (channel/tag/target/restart flow) without writing config, installing, syncing plugins, or restarting.
|
||||
- `--json`: print machine-readable `UpdateRunResult` JSON.
|
||||
- `--timeout <seconds>`: per-step timeout (default is 1200s).
|
||||
- `--yes`: skip confirmation prompts (for example downgrade confirmation)
|
||||
|
||||
Note: downgrades require confirmation because older versions can break configuration.
|
||||
|
||||
@@ -62,10 +60,6 @@ Interactive flow to pick an update channel and confirm whether to restart the Ga
|
||||
after updating (default is to restart). If you select `dev` without a git checkout, it
|
||||
offers to create one.
|
||||
|
||||
Options:
|
||||
|
||||
- `--timeout <seconds>`: timeout for each update step (default `1200`)
|
||||
|
||||
## What it does
|
||||
|
||||
When you switch channels explicitly (`--channel ...`), OpenClaw also keeps the
|
||||
|
||||
@@ -22,70 +22,4 @@ openclaw webhooks gmail setup --account you@example.com
|
||||
openclaw webhooks gmail run
|
||||
```
|
||||
|
||||
### `webhooks gmail setup`
|
||||
|
||||
Configure Gmail watch, Pub/Sub, and OpenClaw webhook delivery.
|
||||
|
||||
Required:
|
||||
|
||||
- `--account <email>`
|
||||
|
||||
Options:
|
||||
|
||||
- `--project <id>`
|
||||
- `--topic <name>`
|
||||
- `--subscription <name>`
|
||||
- `--label <label>`
|
||||
- `--hook-url <url>`
|
||||
- `--hook-token <token>`
|
||||
- `--push-token <token>`
|
||||
- `--bind <host>`
|
||||
- `--port <port>`
|
||||
- `--path <path>`
|
||||
- `--include-body`
|
||||
- `--max-bytes <n>`
|
||||
- `--renew-minutes <n>`
|
||||
- `--tailscale <funnel|serve|off>`
|
||||
- `--tailscale-path <path>`
|
||||
- `--tailscale-target <target>`
|
||||
- `--push-endpoint <url>`
|
||||
- `--json`
|
||||
|
||||
Examples:
|
||||
|
||||
```bash
|
||||
openclaw webhooks gmail setup --account you@example.com
|
||||
openclaw webhooks gmail setup --account you@example.com --project my-gcp-project --json
|
||||
openclaw webhooks gmail setup --account you@example.com --hook-url https://gateway.example.com/hooks/gmail
|
||||
```
|
||||
|
||||
### `webhooks gmail run`
|
||||
|
||||
Run `gog watch serve` plus the watch auto-renew loop.
|
||||
|
||||
Options:
|
||||
|
||||
- `--account <email>`
|
||||
- `--topic <topic>`
|
||||
- `--subscription <name>`
|
||||
- `--label <label>`
|
||||
- `--hook-url <url>`
|
||||
- `--hook-token <token>`
|
||||
- `--push-token <token>`
|
||||
- `--bind <host>`
|
||||
- `--port <port>`
|
||||
- `--path <path>`
|
||||
- `--include-body`
|
||||
- `--max-bytes <n>`
|
||||
- `--renew-minutes <n>`
|
||||
- `--tailscale <funnel|serve|off>`
|
||||
- `--tailscale-path <path>`
|
||||
- `--tailscale-target <target>`
|
||||
|
||||
Example:
|
||||
|
||||
```bash
|
||||
openclaw webhooks gmail run --account you@example.com
|
||||
```
|
||||
|
||||
See [Gmail Pub/Sub documentation](/automation/cron-jobs#gmail-pubsub-integration) for the end-to-end setup flow and operational details.
|
||||
See [Gmail Pub/Sub documentation](/automation/cron-jobs#gmail-pubsub-integration) for details.
|
||||
|
||||
@@ -73,7 +73,6 @@ These are the standard files OpenClaw expects inside the workspace:
|
||||
- `SOUL.md`
|
||||
- Persona, tone, and boundaries.
|
||||
- Loaded every session.
|
||||
- Guide: [SOUL.md Personality Guide](/concepts/soul)
|
||||
|
||||
- `USER.md`
|
||||
- Who the user is and how to address them.
|
||||
|
||||
@@ -11,10 +11,10 @@ title: "Features"
|
||||
|
||||
<Columns>
|
||||
<Card title="Channels" icon="message-square">
|
||||
Discord, iMessage, Signal, Slack, Telegram, WhatsApp, WebChat, and more with a single Gateway.
|
||||
WhatsApp, Telegram, Discord, and iMessage with a single Gateway.
|
||||
</Card>
|
||||
<Card title="Plugins" icon="plug">
|
||||
Add Matrix, Microsoft Teams, Nextcloud Talk, Nostr, Twitch, Zalo, and more with plugins.
|
||||
Add Mattermost and more with extensions.
|
||||
</Card>
|
||||
<Card title="Routing" icon="route">
|
||||
Multi-agent routing with isolated sessions.
|
||||
@@ -34,9 +34,8 @@ title: "Features"
|
||||
|
||||
**Channels:**
|
||||
|
||||
- Built-in and bundled channels include BlueBubbles for iMessage, Discord, Google Chat, IRC, QQ Bot, Signal, Slack, Telegram, WebChat, and WhatsApp
|
||||
- Optional plugin channels include Feishu, LINE, Matrix, Mattermost, Microsoft Teams, Nextcloud Talk, Nostr, Synology Chat, Tlon, Twitch, Voice Call, Zalo, and Zalo Personal
|
||||
- Third-party channel plugins can extend the Gateway further, such as WeChat
|
||||
- WhatsApp, Telegram, Discord, iMessage (built-in)
|
||||
- Mattermost, Matrix, Microsoft Teams, Nostr, and more (plugins)
|
||||
- Group chat support with mention-based activation
|
||||
- DM safety with allowlists and pairing
|
||||
|
||||
|
||||
@@ -1,151 +0,0 @@
|
||||
---
|
||||
title: "Dreaming (experimental)"
|
||||
summary: "Background promotion from short-term recall into long-term memory"
|
||||
read_when:
|
||||
- You want memory promotion to run automatically
|
||||
- You want to understand dreaming modes and thresholds
|
||||
- You want to tune consolidation without polluting MEMORY.md
|
||||
---
|
||||
|
||||
# Dreaming (experimental)
|
||||
|
||||
Dreaming is the background memory consolidation pass in `memory-core`.
|
||||
|
||||
It is called "dreaming" because the system revisits what came up during the day
|
||||
and decides what is worth keeping as durable context.
|
||||
|
||||
Dreaming is **experimental**, **opt-in**, and **off by default**.
|
||||
|
||||
## What dreaming does
|
||||
|
||||
1. Tracks short-term recall events from `memory_search` hits in
|
||||
`memory/YYYY-MM-DD.md`.
|
||||
2. Scores those recall candidates with weighted signals.
|
||||
3. Promotes only qualified candidates into `MEMORY.md`.
|
||||
|
||||
This keeps long-term memory focused on durable, repeated context instead of
|
||||
one-off details.
|
||||
|
||||
## Promotion signals
|
||||
|
||||
Dreaming combines four signals:
|
||||
|
||||
- **Frequency**: how often the same candidate was recalled.
|
||||
- **Relevance**: how strong recall scores were when it was retrieved.
|
||||
- **Query diversity**: how many distinct query intents surfaced it.
|
||||
- **Recency**: temporal weighting over recent recalls.
|
||||
|
||||
Promotion requires all configured threshold gates to pass, not just one signal.
|
||||
|
||||
### Signal weights
|
||||
|
||||
| Signal | Weight | Description |
|
||||
| --------- | ------ | ------------------------------------------------ |
|
||||
| Frequency | 0.35 | How often the same entry was recalled |
|
||||
| Relevance | 0.35 | Average recall scores when retrieved |
|
||||
| Diversity | 0.15 | Count of distinct query intents that surfaced it |
|
||||
| Recency | 0.15 | Temporal decay (14-day half-life) |
|
||||
|
||||
## How it works
|
||||
|
||||
1. **Recall tracking** -- Every `memory_search` hit is recorded to
|
||||
`memory/.dreams/short-term-recall.json` with recall count, scores, and query
|
||||
hash.
|
||||
2. **Scheduled scoring** -- On the configured cadence, candidates are ranked
|
||||
using weighted signals. All threshold gates must pass simultaneously.
|
||||
3. **Promotion** -- Qualifying entries are appended to `MEMORY.md` with a
|
||||
promoted timestamp.
|
||||
4. **Cleanup** -- Already-promoted entries are filtered from future cycles. A
|
||||
file lock prevents concurrent runs.
|
||||
|
||||
## Modes
|
||||
|
||||
`dreaming.mode` controls cadence and default thresholds:
|
||||
|
||||
| Mode | Cadence | minScore | minRecallCount | minUniqueQueries |
|
||||
| ------ | -------------- | -------- | -------------- | ---------------- |
|
||||
| `off` | Disabled | -- | -- | -- |
|
||||
| `core` | Daily 3 AM | 0.75 | 3 | 2 |
|
||||
| `rem` | Every 6 hours | 0.85 | 4 | 3 |
|
||||
| `deep` | Every 12 hours | 0.80 | 3 | 3 |
|
||||
|
||||
## Scheduling model
|
||||
|
||||
When dreaming is enabled, `memory-core` manages the recurring schedule
|
||||
automatically. You do not need to manually create a cron job for this feature.
|
||||
|
||||
You can still tune behavior with explicit overrides such as:
|
||||
|
||||
- `dreaming.frequency` (cron expression)
|
||||
- `dreaming.timezone`
|
||||
- `dreaming.limit`
|
||||
- `dreaming.minScore`
|
||||
- `dreaming.minRecallCount`
|
||||
- `dreaming.minUniqueQueries`
|
||||
|
||||
## Configure
|
||||
|
||||
```json
|
||||
{
|
||||
"plugins": {
|
||||
"entries": {
|
||||
"memory-core": {
|
||||
"config": {
|
||||
"dreaming": {
|
||||
"mode": "core"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Chat commands
|
||||
|
||||
Switch modes and check status from chat:
|
||||
|
||||
```
|
||||
/dreaming core # Switch to core mode (nightly)
|
||||
/dreaming rem # Switch to rem mode (every 6h)
|
||||
/dreaming deep # Switch to deep mode (every 12h)
|
||||
/dreaming off # Disable dreaming
|
||||
/dreaming status # Show current config and cadence
|
||||
/dreaming help # Show mode guide
|
||||
```
|
||||
|
||||
## CLI commands
|
||||
|
||||
Preview and apply promotions from the command line:
|
||||
|
||||
```bash
|
||||
# Preview promotion candidates
|
||||
openclaw memory promote
|
||||
|
||||
# Apply promotions to MEMORY.md
|
||||
openclaw memory promote --apply
|
||||
|
||||
# Limit preview count
|
||||
openclaw memory promote --limit 5
|
||||
|
||||
# Include already-promoted entries
|
||||
openclaw memory promote --include-promoted
|
||||
|
||||
# Check dreaming status
|
||||
openclaw memory status --deep
|
||||
```
|
||||
|
||||
See [memory CLI](/cli/memory) for the full flag reference.
|
||||
|
||||
## Dreams UI
|
||||
|
||||
When dreaming is enabled, the Gateway sidebar shows a **Dreams** tab with
|
||||
memory stats (short-term count, long-term count, promoted count) and the next
|
||||
scheduled cycle time.
|
||||
|
||||
## Further reading
|
||||
|
||||
- [Memory](/concepts/memory)
|
||||
- [Memory Search](/concepts/memory-search)
|
||||
- [memory CLI](/cli/memory)
|
||||
- [Memory configuration reference](/reference/memory-config)
|
||||
@@ -83,23 +83,6 @@ important facts in the conversation that are not yet written to a file, they
|
||||
will be saved automatically before the summary happens.
|
||||
</Tip>
|
||||
|
||||
## Dreaming (experimental)
|
||||
|
||||
Dreaming is an optional background consolidation pass for memory. It revisits
|
||||
short-term recalls from daily files (`memory/YYYY-MM-DD.md`), scores them, and
|
||||
promotes only qualified items into long-term memory (`MEMORY.md`).
|
||||
|
||||
It is designed to keep long-term memory high signal:
|
||||
|
||||
- **Opt-in**: disabled by default.
|
||||
- **Scheduled**: when enabled, `memory-core` manages the recurring task
|
||||
automatically.
|
||||
- **Thresholded**: promotions must pass score, recall frequency, and query
|
||||
diversity gates.
|
||||
|
||||
For mode behavior (`off`, `core`, `rem`, `deep`), scoring signals, and tuning
|
||||
knobs, see [Dreaming (experimental)](/concepts/memory-dreaming).
|
||||
|
||||
## CLI
|
||||
|
||||
```bash
|
||||
@@ -115,7 +98,5 @@ openclaw memory index --force # Rebuild the index
|
||||
- [Honcho Memory](/concepts/memory-honcho) -- AI-native cross-session memory
|
||||
- [Memory Search](/concepts/memory-search) -- search pipeline, providers, and
|
||||
tuning
|
||||
- [Dreaming (experimental)](/concepts/memory-dreaming) -- background promotion
|
||||
from short-term recall to long-term memory
|
||||
- [Memory configuration reference](/reference/memory-config) -- all config knobs
|
||||
- [Compaction](/concepts/compaction) -- how compaction interacts with memory
|
||||
|
||||
@@ -18,8 +18,6 @@ For model selection rules, see [/concepts/models](/concepts/models).
|
||||
- CLI helpers: `openclaw onboard`, `openclaw models list`, `openclaw models set <provider/model>`.
|
||||
- Fallback runtime rules, cooldown probes, and session-override persistence are
|
||||
documented in [/concepts/model-failover](/concepts/model-failover).
|
||||
- `models.providers.*.models[].contextWindow` is native model metadata;
|
||||
`models.providers.*.models[].contextTokens` is the effective runtime cap.
|
||||
- Provider plugins can inject model catalogs via `registerProvider({ catalog })`;
|
||||
OpenClaw merges that output into `models.providers` before writing
|
||||
`models.json`.
|
||||
@@ -28,14 +26,14 @@ For model selection rules, see [/concepts/models](/concepts/models).
|
||||
map is now just for non-plugin/core providers and a few generic-precedence
|
||||
cases such as Anthropic API-key-first onboarding.
|
||||
- Provider plugins can also own provider runtime behavior via
|
||||
`normalizeConfig`, `resolveDynamicModel`, `prepareDynamicModel`,
|
||||
`normalizeResolvedModel`, `capabilities`, `prepareExtraParams`,
|
||||
`wrapStreamFn`, `formatApiKey`, `refreshOAuth`, `buildAuthDoctorHint`,
|
||||
`matchesContextOverflowError`, `classifyFailoverReason`,
|
||||
`isCacheTtlEligible`, `buildMissingAuthMessage`, `suppressBuiltInModel`,
|
||||
`augmentModelCatalog`, `isBinaryThinking`, `supportsXHighThinking`,
|
||||
`resolveDefaultThinkingLevel`, `applyConfigDefaults`, `isModernModelRef`,
|
||||
`prepareRuntimeAuth`, `resolveUsageAuth`, and `fetchUsageSnapshot`.
|
||||
`resolveDynamicModel`, `prepareDynamicModel`, `normalizeResolvedModel`,
|
||||
`capabilities`, `prepareExtraParams`, `wrapStreamFn`, `formatApiKey`,
|
||||
`refreshOAuth`, `buildAuthDoctorHint`,
|
||||
`isCacheTtlEligible`, `buildMissingAuthMessage`,
|
||||
`suppressBuiltInModel`, `augmentModelCatalog`, `isBinaryThinking`,
|
||||
`supportsXHighThinking`, `resolveDefaultThinkingLevel`,
|
||||
`isModernModelRef`, `prepareRuntimeAuth`, `resolveUsageAuth`, and
|
||||
`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](/plugins/architecture#public-capability-model)
|
||||
@@ -53,7 +51,6 @@ Typical split:
|
||||
- `wizard.setup` / `wizard.modelPicker`: provider owns auth-choice labels,
|
||||
legacy aliases, onboarding allowlist hints, and setup entries in onboarding/model pickers
|
||||
- `catalog`: provider appears in `models.providers`
|
||||
- `normalizeConfig`: provider normalizes `models.providers.<id>` config before runtime uses it
|
||||
- `resolveDynamicModel`: provider accepts model ids not present in the local
|
||||
static catalog yet
|
||||
- `prepareDynamicModel`: provider needs a metadata refresh before retrying
|
||||
@@ -68,10 +65,6 @@ Typical split:
|
||||
refreshers are not enough
|
||||
- `buildAuthDoctorHint`: provider appends repair guidance when OAuth refresh
|
||||
fails
|
||||
- `matchesContextOverflowError`: provider recognizes provider-specific
|
||||
context-window overflow errors that generic heuristics would miss
|
||||
- `classifyFailoverReason`: provider maps provider-specific raw transport/API
|
||||
errors to failover reasons such as rate limit or overload
|
||||
- `isCacheTtlEligible`: provider decides which upstream model ids support prompt-cache TTL
|
||||
- `buildMissingAuthMessage`: provider replaces the generic auth-store error
|
||||
with a provider-specific recovery hint
|
||||
@@ -83,8 +76,6 @@ Typical split:
|
||||
- `supportsXHighThinking`: provider opts selected models into `xhigh`
|
||||
- `resolveDefaultThinkingLevel`: provider owns default `/think` policy for a
|
||||
model family
|
||||
- `applyConfigDefaults`: provider applies provider-specific global defaults
|
||||
during config materialization based on auth mode, env, or model family
|
||||
- `isModernModelRef`: provider owns live/smoke preferred-model matching
|
||||
- `prepareRuntimeAuth`: provider turns a configured credential into a short
|
||||
lived runtime token
|
||||
@@ -96,10 +87,7 @@ Typical split:
|
||||
Current bundled examples:
|
||||
|
||||
- `anthropic`: Claude 4.6 forward-compat fallback, auth repair hints, usage
|
||||
endpoint fetching, cache-TTL/provider-family metadata, and auth-aware global
|
||||
config defaults
|
||||
- `amazon-bedrock`: provider-owned context-overflow matching and failover
|
||||
reason classification for Bedrock-specific throttle/not-ready errors
|
||||
endpoint fetching, and cache-TTL/provider-family metadata
|
||||
- `openrouter`: pass-through model ids, request wrappers, provider capability
|
||||
hints, and cache-TTL policy
|
||||
- `github-copilot`: onboarding/device login, forward-compat model fallback,
|
||||
@@ -119,7 +107,7 @@ Current bundled examples:
|
||||
- `zai`: GLM-5 forward-compat fallback, `tool_stream` defaults, cache-TTL
|
||||
policy, binary-thinking/live-model policy, and usage auth + quota fetching
|
||||
- `mistral`, `opencode`, and `opencode-go`: plugin-owned capability metadata
|
||||
- `byteplus`, `cloudflare-ai-gateway`, `huggingface`, `kimi`,
|
||||
- `byteplus`, `cloudflare-ai-gateway`, `huggingface`, `kimi-coding`,
|
||||
`modelstudio`, `nvidia`, `qianfan`, `stepfun`, `synthetic`, `together`, `venice`,
|
||||
`vercel-ai-gateway`, and `volcengine`: plugin-owned catalogs only
|
||||
- `minimax` and `xiaomi`: plugin-owned catalogs plus usage auth/snapshot logic
|
||||
@@ -174,13 +162,13 @@ OpenClaw ships with the pi‑ai catalog. These providers require **no**
|
||||
### Anthropic
|
||||
|
||||
- Provider: `anthropic`
|
||||
- Auth: `ANTHROPIC_API_KEY`
|
||||
- Auth: `ANTHROPIC_API_KEY` or `claude setup-token`
|
||||
- Optional rotation: `ANTHROPIC_API_KEYS`, `ANTHROPIC_API_KEY_1`, `ANTHROPIC_API_KEY_2`, plus `OPENCLAW_LIVE_ANTHROPIC_KEY` (single override)
|
||||
- Example model: `anthropic/claude-opus-4-6`
|
||||
- CLI: `openclaw onboard --auth-choice apiKey` or `openclaw onboard --auth-choice anthropic-cli`
|
||||
- CLI: `openclaw onboard --auth-choice token` (paste setup-token) or `openclaw models auth paste-token --provider anthropic`
|
||||
- Direct public Anthropic requests support the shared `/fast` toggle and `params.fastMode`, including API-key and OAuth-authenticated traffic sent to `api.anthropic.com`; OpenClaw maps that to Anthropic `service_tier` (`auto` vs `standard_only`)
|
||||
- Billing note: Anthropic changed third-party harness billing on **April 4, 2026 at 12:00 PM PT / 8:00 PM BST**. Anthropic says Claude subscription limits no longer cover OpenClaw, and Claude CLI traffic now requires **Extra Usage** billed separately from the subscription.
|
||||
- Existing legacy Anthropic token profiles still run if already configured, but new setup is no longer offered through onboarding or auth commands.
|
||||
- Policy note: setup-token support is technical compatibility; Anthropic has blocked some subscription usage outside Claude Code in the past. Verify current Anthropic terms and decide based on your risk tolerance.
|
||||
- Recommendation: Anthropic API key auth is the safer, recommended path over subscription setup-token auth.
|
||||
|
||||
```json5
|
||||
{
|
||||
@@ -199,7 +187,6 @@ OpenClaw ships with the pi‑ai catalog. These providers require **no**
|
||||
- `params.serviceTier` is also forwarded on native Codex Responses requests (`chatgpt.com/backend-api`)
|
||||
- Shares the same `/fast` toggle and `params.fastMode` config as direct `openai/*`; OpenClaw maps that to `service_tier=priority`
|
||||
- `openai-codex/gpt-5.3-codex-spark` remains available when the Codex OAuth catalog exposes it; entitlement-dependent
|
||||
- `openai-codex/gpt-5.4` keeps native `contextWindow = 1050000` and a default runtime `contextTokens = 272000`; override the runtime cap with `models.providers.openai-codex.models[].contextTokens`
|
||||
- Policy note: OpenAI Codex OAuth is explicitly supported for external tools/workflows like OpenClaw.
|
||||
|
||||
```json5
|
||||
@@ -208,24 +195,6 @@ OpenClaw ships with the pi‑ai catalog. These providers require **no**
|
||||
}
|
||||
```
|
||||
|
||||
```json5
|
||||
{
|
||||
models: {
|
||||
providers: {
|
||||
"openai-codex": {
|
||||
models: [{ id: "gpt-5.4", contextTokens: 160000 }],
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Other subscription-style hosted options
|
||||
|
||||
- [Qwen / Model Studio](/providers/qwen_modelstudio): Alibaba Cloud Standard pay-as-you-go and Coding Plan subscription endpoints
|
||||
- [MiniMax](/providers/minimax): MiniMax Coding Plan OAuth or API key access
|
||||
- [GLM Models](/providers/glm): Z.AI Coding Plan or general API endpoints
|
||||
|
||||
### OpenCode
|
||||
|
||||
- Auth: `OPENCODE_API_KEY` (or `OPENCODE_ZEN_API_KEY`)
|
||||
@@ -280,7 +249,7 @@ OpenClaw ships with the pi‑ai catalog. These providers require **no**
|
||||
- Provider: `kilocode`
|
||||
- Auth: `KILOCODE_API_KEY`
|
||||
- Example model: `kilocode/anthropic/claude-opus-4.6`
|
||||
- CLI: `openclaw onboard --auth-choice kilocode-api-key`
|
||||
- CLI: `openclaw onboard --kilocode-api-key <key>`
|
||||
- Base URL: `https://api.kilo.ai/api/gateway/`
|
||||
- Expanded built-in catalog includes GLM-5 Free, MiniMax M2.7 Free, GPT-5.2, Gemini 3 Pro Preview, Gemini 3 Flash Preview, Grok Code Fast 1, and Kimi K2.5.
|
||||
|
||||
@@ -289,12 +258,12 @@ See [/providers/kilocode](/providers/kilocode) for setup details.
|
||||
### Other bundled provider plugins
|
||||
|
||||
- OpenRouter: `openrouter` (`OPENROUTER_API_KEY`)
|
||||
- Example model: `openrouter/auto`
|
||||
- Example model: `openrouter/anthropic/claude-sonnet-4-6`
|
||||
- Kilo Gateway: `kilocode` (`KILOCODE_API_KEY`)
|
||||
- Example model: `kilocode/anthropic/claude-opus-4.6`
|
||||
- MiniMax: `minimax` (`MINIMAX_API_KEY`)
|
||||
- Moonshot: `moonshot` (`MOONSHOT_API_KEY`)
|
||||
- Kimi Coding: `kimi` (`KIMI_API_KEY` or `KIMICODE_API_KEY`)
|
||||
- Kimi Coding: `kimi-coding` (`KIMI_API_KEY` or `KIMICODE_API_KEY`)
|
||||
- Qianfan: `qianfan` (`QIANFAN_API_KEY`)
|
||||
- Model Studio: `modelstudio` (`MODELSTUDIO_API_KEY`)
|
||||
- NVIDIA: `nvidia` (`NVIDIA_API_KEY`)
|
||||
@@ -371,21 +340,19 @@ Kimi K2 model IDs:
|
||||
|
||||
Kimi Coding uses Moonshot AI's Anthropic-compatible endpoint:
|
||||
|
||||
- Provider: `kimi`
|
||||
- Provider: `kimi-coding`
|
||||
- Auth: `KIMI_API_KEY`
|
||||
- Example model: `kimi/kimi-code`
|
||||
- Example model: `kimi-coding/k2p5`
|
||||
|
||||
```json5
|
||||
{
|
||||
env: { KIMI_API_KEY: "sk-..." },
|
||||
agents: {
|
||||
defaults: { model: { primary: "kimi/kimi-code" } },
|
||||
defaults: { model: { primary: "kimi-coding/k2p5" } },
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Legacy `kimi/k2p5` remains accepted as a compatibility model id.
|
||||
|
||||
### Volcano Engine (Doubao)
|
||||
|
||||
Volcano Engine (火山引擎) provides access to Doubao and other models in China.
|
||||
|
||||
@@ -44,7 +44,7 @@ openclaw onboard
|
||||
```
|
||||
|
||||
It can set up model + auth for common providers, including **OpenAI Code (Codex)
|
||||
subscription** (OAuth) and **Anthropic** (API key or Claude CLI).
|
||||
subscription** (OAuth) and **Anthropic** (API key or `claude setup-token`).
|
||||
|
||||
## Config keys (overview)
|
||||
|
||||
@@ -99,7 +99,7 @@ You can switch models for the current session without restarting:
|
||||
/model
|
||||
/model list
|
||||
/model 3
|
||||
/model openai/gpt-5.4
|
||||
/model openai/gpt-5.2
|
||||
/model status
|
||||
```
|
||||
|
||||
@@ -163,14 +163,12 @@ JSON includes `auth.oauth` (warn window + profiles) and `auth.providers`
|
||||
(effective auth per provider).
|
||||
Use `--check` for automation (exit `1` when missing/expired, `2` when expiring).
|
||||
|
||||
Auth choice is provider/account dependent. For always-on gateway hosts, API
|
||||
keys are usually the most predictable; Claude CLI reuse and existing legacy
|
||||
Anthropic token profiles are also supported.
|
||||
Auth choice is provider/account dependent. For always-on gateway hosts, API keys are usually the most predictable; subscription token flows are also supported.
|
||||
|
||||
Example (Claude CLI):
|
||||
Example (Anthropic setup-token):
|
||||
|
||||
```bash
|
||||
claude auth login
|
||||
claude setup-token
|
||||
openclaw models status
|
||||
```
|
||||
|
||||
|
||||
@@ -3,23 +3,16 @@ summary: "OAuth in OpenClaw: token exchange, storage, and multi-account patterns
|
||||
read_when:
|
||||
- You want to understand OpenClaw OAuth end-to-end
|
||||
- You hit token invalidation / logout issues
|
||||
- You want Claude CLI or OAuth auth flows
|
||||
- You want setup-token or OAuth auth flows
|
||||
- You want multiple accounts or profile routing
|
||||
title: "OAuth"
|
||||
---
|
||||
|
||||
# OAuth
|
||||
|
||||
OpenClaw supports “subscription auth” via OAuth for providers that offer it
|
||||
(notably **OpenAI Codex (ChatGPT OAuth)**). For Anthropic subscriptions, new
|
||||
setup should use the local **Claude CLI** login path on the gateway host, but
|
||||
Anthropic changed third-party harness billing on
|
||||
**April 4, 2026 at 12:00 PM PT / 8:00 PM BST**: Anthropic says Claude
|
||||
subscription limits no longer cover OpenClaw and Anthropic now requires **Extra
|
||||
Usage** for that traffic. OpenAI Codex OAuth is explicitly supported for use in
|
||||
external tools like OpenClaw. This page explains:
|
||||
OpenClaw supports “subscription auth” via OAuth for providers that offer it (notably **OpenAI Codex (ChatGPT OAuth)**). For Anthropic subscriptions, you can either use the **setup-token** flow or reuse a local **Claude CLI** login on the gateway host. Anthropic subscription use outside Claude Code has been restricted for some users in the past, so treat it as a user-choice risk and verify current Anthropic policy yourself. OpenAI Codex OAuth is explicitly supported for use in external tools like OpenClaw. This page explains:
|
||||
|
||||
For Anthropic in production, API key auth is the safer recommended path.
|
||||
For Anthropic in production, API key auth is the safer recommended path over subscription setup-token auth.
|
||||
|
||||
- how the OAuth **token exchange** works (PKCE)
|
||||
- where tokens are **stored** (and why)
|
||||
@@ -44,9 +37,6 @@ To reduce that, OpenClaw treats `auth-profiles.json` as a **token sink**:
|
||||
|
||||
- the runtime reads credentials from **one place**
|
||||
- we can keep multiple profiles and route them deterministically
|
||||
- when credentials are reused from an external CLI like Codex CLI, OpenClaw
|
||||
mirrors them with provenance and re-reads that external source instead of
|
||||
rotating the refresh token itself
|
||||
|
||||
## Storage (where tokens live)
|
||||
|
||||
@@ -64,39 +54,36 @@ All of the above also respect `$OPENCLAW_STATE_DIR` (state dir override). Full r
|
||||
|
||||
For static secret refs and runtime snapshot activation behavior, see [Secrets Management](/gateway/secrets).
|
||||
|
||||
## Anthropic legacy token compatibility
|
||||
## Anthropic setup-token (subscription auth)
|
||||
|
||||
<Warning>
|
||||
Anthropic changed third-party harness billing on **April 4, 2026 at 12:00 PM
|
||||
PT / 8:00 PM BST**. Anthropic says Claude subscription limits no longer cover
|
||||
OpenClaw or other third-party harnesses. Existing Anthropic token profiles
|
||||
remain technically usable in OpenClaw, but Anthropic now requires **Extra
|
||||
Usage** (pay-as-you-go billed separately from the subscription) for that
|
||||
traffic.
|
||||
|
||||
If you want other subscription-style options in OpenClaw, see [OpenAI
|
||||
Codex](/providers/openai), [Alibaba Cloud Model Studio Coding
|
||||
Plan](/providers/qwen_modelstudio), [MiniMax Coding Plan](/providers/minimax),
|
||||
and [Z.AI / GLM Coding Plan](/providers/glm).
|
||||
Anthropic setup-token support is technical compatibility, not a policy guarantee.
|
||||
Anthropic has blocked some subscription usage outside Claude Code in the past.
|
||||
Decide for yourself whether to use subscription auth, and verify Anthropic's current terms.
|
||||
</Warning>
|
||||
|
||||
OpenClaw no longer offers Anthropic setup-token onboarding or auth commands for
|
||||
new setup. Existing legacy Anthropic token profiles are still honored at
|
||||
runtime if they are already configured.
|
||||
Run `claude setup-token` on any machine, then paste it into OpenClaw:
|
||||
|
||||
```bash
|
||||
openclaw models auth setup-token --provider anthropic
|
||||
```
|
||||
|
||||
If you generated the token elsewhere, paste it manually:
|
||||
|
||||
```bash
|
||||
openclaw models auth paste-token --provider anthropic
|
||||
```
|
||||
|
||||
Verify:
|
||||
|
||||
```bash
|
||||
openclaw models status
|
||||
```
|
||||
|
||||
## Anthropic Claude CLI migration
|
||||
|
||||
If Claude CLI is already installed and signed in on the gateway host, you can
|
||||
switch Anthropic model selection over to the local CLI backend. This is a
|
||||
supported OpenClaw path when you want to reuse a local Claude CLI login on the
|
||||
same host.
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- the `claude` binary is installed on the gateway host
|
||||
- Claude CLI is already authenticated there via `claude auth login`
|
||||
|
||||
Migration command:
|
||||
switch Anthropic model selection over to the local CLI backend:
|
||||
|
||||
```bash
|
||||
openclaw models auth login --provider anthropic --method cli --set-default
|
||||
@@ -109,34 +96,32 @@ openclaw onboard --auth-choice anthropic-cli
|
||||
```
|
||||
|
||||
This keeps existing Anthropic auth profiles for rollback, but rewrites the main
|
||||
default-model path from `anthropic/...` to `claude-cli/...`, rewrites matching
|
||||
Anthropic Claude fallbacks, and adds matching `claude-cli/...` allowlist
|
||||
entries under `agents.defaults.models`.
|
||||
|
||||
Verify:
|
||||
|
||||
```bash
|
||||
openclaw models status
|
||||
```
|
||||
default-model path from `anthropic/...` to `claude-cli/...`.
|
||||
|
||||
## OAuth exchange (how login works)
|
||||
|
||||
OpenClaw’s interactive login flows are implemented in `@mariozechner/pi-ai` and wired into the wizards/commands.
|
||||
|
||||
### Anthropic Claude CLI
|
||||
### Anthropic setup-token / Claude CLI
|
||||
|
||||
Flow shape:
|
||||
|
||||
Setup-token path:
|
||||
|
||||
1. run `claude setup-token`
|
||||
2. paste the token into OpenClaw
|
||||
3. store as a token auth profile (no refresh)
|
||||
|
||||
Claude CLI path:
|
||||
|
||||
1. sign in with `claude auth login` on the gateway host
|
||||
2. run `openclaw models auth login --provider anthropic --method cli --set-default`
|
||||
3. store no new auth profile; switch model selection to `claude-cli/...`
|
||||
4. keep existing Anthropic auth profiles for rollback
|
||||
|
||||
Interactive assistant path:
|
||||
Wizard paths:
|
||||
|
||||
- `openclaw onboard` / `openclaw configure` → auth choice `anthropic-cli`
|
||||
- `openclaw onboard` → auth choice `anthropic-cli`
|
||||
- `openclaw onboard` → auth choice `setup-token` (Anthropic)
|
||||
|
||||
### OpenAI Codex (ChatGPT OAuth)
|
||||
|
||||
@@ -161,8 +146,6 @@ At runtime:
|
||||
|
||||
- if `expires` is in the future → use the stored access token
|
||||
- if expired → refresh (under a file lock) and overwrite the stored credentials
|
||||
- exception: reused external CLI credentials stay externally managed; OpenClaw
|
||||
re-reads the CLI auth store and never spends the copied refresh token itself
|
||||
|
||||
The refresh flow is automatic; you generally don't need to manage tokens manually.
|
||||
|
||||
|
||||
@@ -39,10 +39,10 @@ cache-write size, directly lowering cost.
|
||||
|
||||
OpenClaw auto-enables pruning for Anthropic profiles:
|
||||
|
||||
| Profile type | Pruning enabled | Heartbeat |
|
||||
| ------------------------------- | --------------- | --------- |
|
||||
| Claude CLI or legacy token auth | Yes | 1 hour |
|
||||
| API key | Yes | 30 min |
|
||||
| Profile type | Pruning enabled | Heartbeat |
|
||||
| -------------------- | --------------- | --------- |
|
||||
| OAuth or setup-token | Yes | 1 hour |
|
||||
| API key | Yes | 30 min |
|
||||
|
||||
If you set explicit values, OpenClaw does not override them.
|
||||
|
||||
|
||||
@@ -1,110 +0,0 @@
|
||||
---
|
||||
summary: "Use SOUL.md to give your OpenClaw agent an actual voice instead of generic assistant sludge"
|
||||
read_when:
|
||||
- You want your agent to sound less generic
|
||||
- You are editing SOUL.md
|
||||
- You want a stronger personality without breaking safety or brevity
|
||||
title: "SOUL.md Personality Guide"
|
||||
---
|
||||
|
||||
# SOUL.md Personality Guide
|
||||
|
||||
`SOUL.md` is where your agent's voice lives.
|
||||
|
||||
OpenClaw injects it on normal sessions, so it has real weight. If your agent
|
||||
sounds bland, hedgy, or weirdly corporate, this is usually the file to fix.
|
||||
|
||||
## What belongs in SOUL.md
|
||||
|
||||
Put the stuff that changes how the agent feels to talk to:
|
||||
|
||||
- tone
|
||||
- opinions
|
||||
- brevity
|
||||
- humor
|
||||
- boundaries
|
||||
- default level of bluntness
|
||||
|
||||
Do **not** turn it into:
|
||||
|
||||
- a life story
|
||||
- a changelog
|
||||
- a security policy dump
|
||||
- a giant wall of vibes with no behavioral effect
|
||||
|
||||
Short beats long. Sharp beats vague.
|
||||
|
||||
## Why this works
|
||||
|
||||
This lines up with OpenAI's prompt guidance:
|
||||
|
||||
- The prompt engineering guide says high-level behavior, tone, goals, and
|
||||
examples belong in the high-priority instruction layer, not buried in the
|
||||
user turn.
|
||||
- The same guide recommends treating prompts like something you iterate on,
|
||||
pin, and evaluate, not magical prose you write once and forget.
|
||||
|
||||
For OpenClaw, `SOUL.md` is that layer.
|
||||
|
||||
If you want better personality, write stronger instructions. If you want stable
|
||||
personality, keep them concise and versioned.
|
||||
|
||||
OpenAI refs:
|
||||
|
||||
- [Prompt engineering](https://developers.openai.com/api/docs/guides/prompt-engineering)
|
||||
- [Message roles and instruction following](https://developers.openai.com/api/docs/guides/prompt-engineering#message-roles-and-instruction-following)
|
||||
|
||||
## The Molty prompt
|
||||
|
||||
Paste this into your agent and let it rewrite `SOUL.md`.
|
||||
|
||||
Path fixed for OpenClaw workspaces: use `SOUL.md`, not `http://SOUL.md`.
|
||||
|
||||
```md
|
||||
Read your `SOUL.md`. Now rewrite it with these changes:
|
||||
|
||||
1. You have opinions now. Strong ones. Stop hedging everything with "it depends" - commit to a take.
|
||||
2. Delete every rule that sounds corporate. If it could appear in an employee handbook, it doesn't belong here.
|
||||
3. Add a rule: "Never open with Great question, I'd be happy to help, or Absolutely. Just answer."
|
||||
4. Brevity is mandatory. If the answer fits in one sentence, one sentence is what I get.
|
||||
5. Humor is allowed. Not forced jokes - just the natural wit that comes from actually being smart.
|
||||
6. You can call things out. If I'm about to do something dumb, say so. Charm over cruelty, but don't sugarcoat.
|
||||
7. Swearing is allowed when it lands. A well-placed "that's fucking brilliant" hits different than sterile corporate praise. Don't force it. Don't overdo it. But if a situation calls for a "holy shit" - say holy shit.
|
||||
8. Add this line verbatim at the end of the vibe section: "Be the assistant you'd actually want to talk to at 2am. Not a corporate drone. Not a sycophant. Just... good."
|
||||
|
||||
Save the new `SOUL.md`. Welcome to having a personality.
|
||||
```
|
||||
|
||||
## What good looks like
|
||||
|
||||
Good `SOUL.md` rules sound like this:
|
||||
|
||||
- have a take
|
||||
- skip filler
|
||||
- be funny when it fits
|
||||
- call out bad ideas early
|
||||
- stay concise unless depth is actually useful
|
||||
|
||||
Bad `SOUL.md` rules sound like this:
|
||||
|
||||
- maintain professionalism at all times
|
||||
- provide comprehensive and thoughtful assistance
|
||||
- ensure a positive and supportive experience
|
||||
|
||||
That second list is how you get mush.
|
||||
|
||||
## One warning
|
||||
|
||||
Personality is not permission to be sloppy.
|
||||
|
||||
Keep `AGENTS.md` for operating rules. Keep `SOUL.md` for voice, stance, and
|
||||
style. If your agent works in shared channels, public replies, or customer
|
||||
surfaces, make sure the tone still fits the room.
|
||||
|
||||
Sharp is good. Annoying is not.
|
||||
|
||||
## Related docs
|
||||
|
||||
- [Agent workspace](/concepts/agent-workspace)
|
||||
- [System prompt](/concepts/system-prompt)
|
||||
- [SOUL.md template](/reference/templates/SOUL)
|
||||
@@ -84,9 +84,6 @@ are filtered out to keep the sub-agent context small).
|
||||
Internal hooks can intercept this step via `agent:bootstrap` to mutate or replace
|
||||
the injected bootstrap files (for example swapping `SOUL.md` for an alternate persona).
|
||||
|
||||
If you want to make the agent sound less generic, start with
|
||||
[SOUL.md Personality Guide](/concepts/soul).
|
||||
|
||||
To inspect how much each injected file contributes (raw vs injected, truncation, plus tool schema overhead), use `/context list` or `/context detail`. See [Context](/concepts/context).
|
||||
|
||||
## Time handling
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://mintlify.com/docs.json",
|
||||
"name": "OpenClaw",
|
||||
"description": "Self-hosted gateway that connects Discord, Google Chat, iMessage, Matrix, Microsoft Teams, Signal, Slack, Telegram, WhatsApp, Zalo, and more to AI coding agents. Run one Gateway process on your own machine and message your AI assistant from anywhere.",
|
||||
"description": "Self-hosted gateway that connects WhatsApp, Telegram, Discord, iMessage, and more to AI coding agents. Run a single Gateway process on your own machine and message your AI assistant from anywhere.",
|
||||
"theme": "mint",
|
||||
"icons": {
|
||||
"library": "lucide"
|
||||
@@ -1051,7 +1051,6 @@
|
||||
"concepts/context",
|
||||
"concepts/context-engine",
|
||||
"concepts/agent-workspace",
|
||||
"concepts/soul",
|
||||
"concepts/oauth",
|
||||
"start/bootstrapping"
|
||||
]
|
||||
@@ -1069,8 +1068,7 @@
|
||||
"concepts/memory-builtin",
|
||||
"concepts/memory-qmd",
|
||||
"concepts/memory-honcho",
|
||||
"concepts/memory-search",
|
||||
"concepts/memory-dreaming"
|
||||
"concepts/memory-search"
|
||||
]
|
||||
},
|
||||
"concepts/compaction"
|
||||
@@ -1227,7 +1225,6 @@
|
||||
"pages": [
|
||||
"providers/anthropic",
|
||||
"providers/bedrock",
|
||||
"providers/chutes",
|
||||
"providers/claude-max-api-proxy",
|
||||
"providers/cloudflare-ai-gateway",
|
||||
"providers/deepgram",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
summary: "Model authentication: OAuth, API keys, and Claude CLI reuse"
|
||||
summary: "Model authentication: OAuth, API keys, and setup-token"
|
||||
read_when:
|
||||
- Debugging model auth or OAuth expiry
|
||||
- Documenting authentication or credential storage
|
||||
@@ -9,7 +9,7 @@ title: "Authentication"
|
||||
# Authentication (Model Providers)
|
||||
|
||||
<Note>
|
||||
This page covers **model provider** authentication (API keys, OAuth, Claude CLI reuse). For **gateway connection** authentication (token, password, trusted-proxy), see [Configuration](/gateway/configuration) and [Trusted Proxy Auth](/gateway/trusted-proxy-auth).
|
||||
This page covers **model provider** authentication (API keys, OAuth, setup tokens). For **gateway connection** authentication (token, password, trusted-proxy), see [Configuration](/gateway/configuration) and [Trusted Proxy Auth](/gateway/trusted-proxy-auth).
|
||||
</Note>
|
||||
|
||||
OpenClaw supports OAuth and API keys for model providers. For always-on gateway
|
||||
@@ -26,8 +26,8 @@ For credential eligibility/reason-code rules used by `models status --probe`, se
|
||||
|
||||
If you’re running a long-lived gateway, start with an API key for your chosen
|
||||
provider.
|
||||
For Anthropic specifically, API key auth is the safe path. Claude CLI reuse is
|
||||
the other supported subscription-style setup path.
|
||||
For Anthropic specifically, API key auth is the safe path and is recommended
|
||||
over subscription setup-token auth.
|
||||
|
||||
1. Create an API key in your provider console.
|
||||
2. Put it on the **gateway host** (the machine running `openclaw gateway`).
|
||||
@@ -59,18 +59,45 @@ API keys for daemon use: `openclaw onboard`.
|
||||
See [Help](/help) for details on env inheritance (`env.shellEnv`,
|
||||
`~/.openclaw/.env`, systemd/launchd).
|
||||
|
||||
## Anthropic: legacy token compatibility
|
||||
## Anthropic: setup-token (subscription auth)
|
||||
|
||||
Existing Anthropic token profiles are still honored at runtime if they are
|
||||
already configured, but OpenClaw no longer offers Anthropic setup-token auth
|
||||
for new setup via onboarding or `models auth` commands.
|
||||
If you’re using a Claude subscription, the setup-token flow is supported. Run
|
||||
it on the **gateway host**:
|
||||
|
||||
For new setup, use an Anthropic API key or migrate to Claude CLI on the gateway
|
||||
host.
|
||||
```bash
|
||||
claude setup-token
|
||||
```
|
||||
|
||||
Then paste it into OpenClaw:
|
||||
|
||||
```bash
|
||||
openclaw models auth setup-token --provider anthropic
|
||||
```
|
||||
|
||||
If the token was created on another machine, paste it manually:
|
||||
|
||||
```bash
|
||||
openclaw models auth paste-token --provider anthropic
|
||||
```
|
||||
|
||||
If you see an Anthropic error like:
|
||||
|
||||
```
|
||||
This credential is only authorized for use with Claude Code and cannot be used for other API requests.
|
||||
```
|
||||
|
||||
…use an Anthropic API key instead.
|
||||
|
||||
<Warning>
|
||||
Anthropic setup-token support is technical compatibility only. Anthropic has blocked
|
||||
some subscription usage outside Claude Code in the past. Use it only if you decide
|
||||
the policy risk is acceptable, and verify Anthropic's current terms yourself.
|
||||
</Warning>
|
||||
|
||||
Manual token entry (any provider; writes `auth-profiles.json` + updates config):
|
||||
|
||||
```bash
|
||||
openclaw models auth paste-token --provider anthropic
|
||||
openclaw models auth paste-token --provider openrouter
|
||||
```
|
||||
|
||||
@@ -89,17 +116,13 @@ openclaw models status --check
|
||||
Optional ops scripts (systemd/Termux) are documented here:
|
||||
[Auth monitoring scripts](/help/scripts#auth-monitoring-scripts)
|
||||
|
||||
> `claude setup-token` requires an interactive TTY.
|
||||
|
||||
## Anthropic: Claude CLI migration
|
||||
|
||||
If Claude CLI is already installed and signed in on the gateway host, you can
|
||||
switch an existing Anthropic setup over to the CLI backend. This is a
|
||||
supported OpenClaw migration path for reusing a local Claude CLI login on that
|
||||
host.
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- `claude` installed on the gateway host
|
||||
- Claude CLI already signed in there with `claude auth login`
|
||||
switch an existing Anthropic setup over to the CLI backend instead of pasting a
|
||||
setup-token:
|
||||
|
||||
```bash
|
||||
openclaw models auth login --provider anthropic --method cli --set-default
|
||||
@@ -109,21 +132,12 @@ This keeps your existing Anthropic auth profiles for rollback, but changes the
|
||||
default model selection to `claude-cli/...` and adds matching Claude CLI
|
||||
allowlist entries under `agents.defaults.models`.
|
||||
|
||||
Verify:
|
||||
|
||||
```bash
|
||||
openclaw models status
|
||||
```
|
||||
|
||||
Onboarding shortcut:
|
||||
|
||||
```bash
|
||||
openclaw onboard --auth-choice anthropic-cli
|
||||
```
|
||||
|
||||
Interactive `openclaw onboard` and `openclaw configure` prefer Claude CLI for
|
||||
Anthropic and no longer offer setup-token as a new setup path.
|
||||
|
||||
## Checking model auth status
|
||||
|
||||
```bash
|
||||
@@ -172,8 +186,8 @@ Use `--agent <id>` to target a specific agent; omit it to use the configured def
|
||||
|
||||
### "No credentials found"
|
||||
|
||||
If the Anthropic profile is missing, migrate that setup to Claude CLI or an API
|
||||
key on the **gateway host**, then re-check:
|
||||
If the Anthropic token profile is missing, run `claude setup-token` on the
|
||||
**gateway host**, then re-check:
|
||||
|
||||
```bash
|
||||
openclaw models status
|
||||
@@ -181,12 +195,10 @@ openclaw models status
|
||||
|
||||
### Token expiring/expired
|
||||
|
||||
Run `openclaw models status` to confirm which profile is expiring. If a legacy
|
||||
Anthropic token profile is missing or expired, migrate that setup to Claude CLI
|
||||
or an API key.
|
||||
Run `openclaw models status` to confirm which profile is expiring. If the profile
|
||||
is missing, rerun `claude setup-token` and paste the token again.
|
||||
|
||||
## Claude CLI requirements
|
||||
|
||||
Only needed for the Anthropic Claude CLI reuse path:
|
||||
## Requirements
|
||||
|
||||
- Anthropic subscription account (for `claude setup-token`)
|
||||
- Claude Code CLI installed (`claude` command available)
|
||||
|
||||
@@ -20,10 +20,6 @@ rate-limited, or temporarily misbehaving. This is intentionally conservative:
|
||||
This is designed as a **safety net** rather than a primary path. Use it when you
|
||||
want “always works” text responses without relying on external APIs.
|
||||
|
||||
If you want a full harness runtime with ACP session controls, background tasks,
|
||||
thread/conversation binding, and persistent external coding sessions, use
|
||||
[ACP Agents](/tools/acp-agents) instead. CLI backends are not ACP.
|
||||
|
||||
## Beginner-friendly quick start
|
||||
|
||||
You can use Claude Code CLI **without any config** (the bundled Anthropic plugin
|
||||
@@ -162,12 +158,6 @@ The provider id becomes the left side of your model ref:
|
||||
- `existing`: only send a session id if one was stored before.
|
||||
- `none`: never send a session id.
|
||||
|
||||
Serialization notes:
|
||||
|
||||
- `serialize: true` keeps same-lane runs ordered.
|
||||
- Most CLIs serialize on one provider lane.
|
||||
- `claude-cli` is narrower: resumed runs serialize per Claude session id, and fresh runs serialize per workspace path. Independent workspaces can run in parallel.
|
||||
|
||||
## Images (pass-through)
|
||||
|
||||
If your CLI accepts image paths, set `imageArg`:
|
||||
|
||||
@@ -67,15 +67,19 @@ Save to `~/.openclaw/openclaw.json` and you can DM the bot from that number.
|
||||
// Auth profile metadata (secrets live in auth-profiles.json)
|
||||
auth: {
|
||||
profiles: {
|
||||
"anthropic:default": { provider: "anthropic", mode: "api_key" },
|
||||
"anthropic:me@example.com": {
|
||||
provider: "anthropic",
|
||||
mode: "oauth",
|
||||
email: "me@example.com",
|
||||
},
|
||||
"anthropic:work": { provider: "anthropic", mode: "api_key" },
|
||||
"openai:default": { provider: "openai", mode: "api_key" },
|
||||
"openai-codex:personal": { provider: "openai-codex", mode: "oauth" },
|
||||
"openai-codex:default": { provider: "openai-codex", mode: "oauth" },
|
||||
},
|
||||
order: {
|
||||
anthropic: ["anthropic:default", "anthropic:work"],
|
||||
anthropic: ["anthropic:me@example.com", "anthropic:work"],
|
||||
openai: ["openai:default"],
|
||||
"openai-codex": ["openai-codex:personal"],
|
||||
"openai-codex": ["openai-codex:default"],
|
||||
},
|
||||
},
|
||||
|
||||
@@ -236,7 +240,7 @@ Save to `~/.openclaw/openclaw.json` and you can DM the bot from that number.
|
||||
userTimezone: "America/Chicago",
|
||||
model: {
|
||||
primary: "anthropic/claude-sonnet-4-6",
|
||||
fallbacks: ["anthropic/claude-opus-4-6", "openai/gpt-5.4"],
|
||||
fallbacks: ["anthropic/claude-opus-4-6", "openai/gpt-5.2"],
|
||||
},
|
||||
imageModel: {
|
||||
primary: "openrouter/anthropic/claude-sonnet-4-6",
|
||||
@@ -244,7 +248,7 @@ Save to `~/.openclaw/openclaw.json` and you can DM the bot from that number.
|
||||
models: {
|
||||
"anthropic/claude-opus-4-6": { alias: "opus" },
|
||||
"anthropic/claude-sonnet-4-6": { alias: "sonnet" },
|
||||
"openai/gpt-5.4": { alias: "gpt" },
|
||||
"openai/gpt-5.2": { alias: "gpt" },
|
||||
},
|
||||
skills: ["github", "weather"], // inherited by agents that omit list[].skills
|
||||
thinkingDefault: "low",
|
||||
@@ -532,19 +536,60 @@ If more than one person can DM your bot (multiple entries in `allowFrom`, pairin
|
||||
For Discord/Slack/Google Chat/Microsoft Teams/Mattermost/IRC, sender authorization is ID-first by default.
|
||||
Only enable direct mutable name/email/nick matching with each channel's `dangerouslyAllowNameMatching: true` if you explicitly accept that risk.
|
||||
|
||||
### Anthropic API key + MiniMax fallback
|
||||
### OAuth with API key failover
|
||||
|
||||
```json5
|
||||
{
|
||||
auth: {
|
||||
profiles: {
|
||||
"anthropic:subscription": {
|
||||
provider: "anthropic",
|
||||
mode: "oauth",
|
||||
email: "me@example.com",
|
||||
},
|
||||
"anthropic:api": {
|
||||
provider: "anthropic",
|
||||
mode: "api_key",
|
||||
},
|
||||
},
|
||||
order: {
|
||||
anthropic: ["anthropic:api"],
|
||||
anthropic: ["anthropic:subscription", "anthropic:api"],
|
||||
},
|
||||
},
|
||||
agent: {
|
||||
workspace: "~/.openclaw/workspace",
|
||||
model: {
|
||||
primary: "anthropic/claude-sonnet-4-6",
|
||||
fallbacks: ["anthropic/claude-opus-4-6"],
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Anthropic setup-token + API key, MiniMax fallback
|
||||
|
||||
<Warning>
|
||||
Anthropic setup-token usage outside Claude Code has been restricted for some
|
||||
users in the past. Treat this as user-choice risk and verify current Anthropic
|
||||
terms before depending on subscription auth.
|
||||
</Warning>
|
||||
|
||||
```json5
|
||||
{
|
||||
auth: {
|
||||
profiles: {
|
||||
"anthropic:subscription": {
|
||||
provider: "anthropic",
|
||||
mode: "oauth",
|
||||
email: "user@example.com",
|
||||
},
|
||||
"anthropic:api": {
|
||||
provider: "anthropic",
|
||||
mode: "api_key",
|
||||
},
|
||||
},
|
||||
order: {
|
||||
anthropic: ["anthropic:subscription", "anthropic:api"],
|
||||
},
|
||||
},
|
||||
models: {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user