Compare commits

..

346 Commits

Author SHA1 Message Date
Peter Steinberger
cd5133f50e fix: correct MiniMax coding plan remaining quota (#60222) (thanks @YangManBOBO) 2026-04-04 09:31:46 +01:00
潘晓波0668000512
ed490f446e 修复:MiniMax coding_plan 将 interval/weekly usage_count 按剩余配额解析 2026-04-04 09:26:30 +01:00
Peter Steinberger
73572e04c1 fix: preserve generic DashScope streaming usage (#52395) (thanks @IVY-AI-gif) 2026-04-04 17:25:33 +09:00
Peter Steinberger
a192f345d4 docs: refresh key-free web search ordering 2026-04-04 09:25:20 +01:00
Peter Steinberger
54cfd746de docs: polish moonshot kimi docs (#57883) (thanks @chenxin-yan) 2026-04-04 17:23:29 +09:00
Chenxin Yan
8347022b50 remove redundency 2026-04-04 17:23:29 +09:00
Chenxin Yan
6615c5788b docs: fix incorrect Kimi Coding provider ID and model refs
The Kimi Coding plugin registers with provider ID `kimi` and default
model ID `kimi-code`, making the correct model ref `kimi/kimi-code`.

The docs incorrectly showed `kimi-coding/k2p5` as the provider/model
ref. This is confusing because `kimi-coding` is only a plugin alias,
not the actual provider ID used in config.

Updated all references in:
- docs/concepts/model-providers.md
- docs/providers/moonshot.md
- docs/zh-CN/concepts/model-providers.md
- docs/zh-CN/providers/moonshot.md
2026-04-04 17:23:29 +09:00
Peter Steinberger
df4b5d2137 docs: refresh self-hosted web search references 2026-04-04 09:22:30 +01:00
Vincent Koc
cdccbf2c1c fix(github-copilot): send IDE auth headers on runtime requests (#60755)
* Fix Copilot IDE auth headers

* fix(github-copilot): align tests and changelog

* fix(changelog): scope copilot replacement entry

---------

Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>
2026-04-04 17:22:19 +09:00
Peter Steinberger
38ed8c355a docs: refresh perplexity web search references 2026-04-04 09:21:06 +01:00
Vincent Koc
e4c3df2fb6 docs(changelog): note cache boundary fix 2026-04-04 17:20:24 +09:00
Vincent Koc
a50b838dc2 test(agents): annotate cache trace wrapper params 2026-04-04 17:20:23 +09:00
Vincent Koc
1a13c34f5b fix(agents): close cache boundary transport gaps 2026-04-04 17:20:23 +09:00
Peter Steinberger
58a56d9a82 feat: add MiniMax TTS provider (#55921) (thanks @duncanita) 2026-04-04 09:19:45 +01:00
Peter Steinberger
a746f0e8c3 style: normalize telegram fetch test formatting 2026-04-04 09:19:45 +01:00
Vincent Koc
7ad43f21d3 style(msteams): format split graph message import 2026-04-04 09:19:45 +01:00
gnuduncan
e934211170 fix(minimax): use global TTS endpoint default and add missing Talk Mode overrides
Switch DEFAULT_MINIMAX_TTS_BASE_URL from api.minimaxi.com (CN) to
api.minimax.io (global) so international API keys work out of the box.
Add vol and pitch to resolveTalkOverrides for parity with resolveTalkConfig.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 09:19:45 +01:00
gnuduncan
7d7f5d85b4 feat(minimax): add native TTS speech provider (T2A v2)
Add MiniMax as a fourth TTS provider alongside OpenAI, ElevenLabs, and
Microsoft. Registers a SpeechProviderPlugin in the existing minimax
extension with config resolution, directive parsing, and Talk Mode
support. Hex-encoded audio response from the T2A v2 API is decoded to
MP3.

Closes #52720

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 09:19:45 +01:00
Peter Steinberger
49d962a82f docs: refresh brave web search references 2026-04-04 09:19:11 +01:00
Peter Steinberger
d1a4363783 fix(runtime): restore gateway watch on legacy state 2026-04-04 09:18:28 +01:00
Peter Steinberger
0051a86b8f docs: clarify synthesized web search count behavior 2026-04-04 09:17:49 +01:00
Gaston Rodriguez
b6b1d5dd6c Moonshot: reuse native base URL for Kimi web search 2026-04-04 17:16:29 +09:00
Peter Steinberger
e5d03f734a docs: refresh kimi web search setup 2026-04-04 09:15:01 +01:00
Peter Steinberger
21ca006eca fix(infra): restore approval account binding compatibility 2026-04-04 09:13:11 +01:00
Peter Steinberger
af7c6f4c68 fix: harden kimi web search setup (#59356) (thanks @Innocent-children) 2026-04-04 17:11:47 +09:00
innocent-children
216294765b fix(kimi): unify runtime model fallback to kimi-k2.5
Remove DEFAULT_KIMI_MODEL (moonshot-v1-128k) and align resolveKimiModel
fallback to DEFAULT_KIMI_SEARCH_MODEL (kimi-k2.5). The legacy model
does not support the $web_search builtin_function tool, so env-var-only
users without a configured model would hit the original bug.
2026-04-04 17:11:47 +09:00
innocent-children
111495d3ca 修复kimi web_search错误 2026-04-04 17:11:47 +09:00
Peter Steinberger
11a87b4b7a docs: clarify plugin facade runtime snapshots 2026-04-04 09:11:25 +01:00
Peter Steinberger
85ade25003 docs: refresh minimax multimodal references 2026-04-04 09:09:06 +01:00
Peter Steinberger
daac149744 fix(ci): honor runtime config snapshots for facades 2026-04-04 09:08:25 +01:00
Peter Steinberger
42f6de16b2 fix: advertise MiniMax M2.7 image input (#54843) (thanks @MerlinMiao88888888) 2026-04-04 17:07:35 +09:00
王淼0668000666
51d998d828 minimax: add image capability to MiniMax-M2.7 model 2026-04-04 17:07:35 +09:00
王淼0668000666
87b41ca693 minimax: add image capability to MiniMax-M2.7 model 2026-04-04 17:07:35 +09:00
Peter Steinberger
ed866020df docs: refresh task reconciliation references 2026-04-04 09:07:08 +01:00
Peter Steinberger
7d1575b5df fix: reconcile stale cron and chat-backed tasks (#60310) (thanks @lml2468) 2026-04-04 17:05:57 +09:00
Peter Steinberger
7036e5afbf fix: honor exec approval security from approvals (#60310) (thanks @lml2468) 2026-04-04 17:05:57 +09:00
Peter Steinberger
8cec7c68b9 fix(ci): restore typecheck on main 2026-04-04 09:05:17 +01:00
Peter Steinberger
da50b492c8 docs: refresh gateway status diagnostics refs 2026-04-04 09:05:08 +01:00
Peter Steinberger
dc2575f6c4 docs: clarify local agent plugin preload 2026-04-04 09:04:11 +01:00
Peter Steinberger
7671f4f1e3 docs: clarify gateway and plugin http auth scopes 2026-04-04 09:01:05 +01:00
Peter Steinberger
bc75968074 perf(cli): trim gateway status startup imports 2026-04-04 08:59:56 +01:00
Peter Steinberger
be15805a84 refactor(runtime): lazy-load control-ui and channel-config surfaces 2026-04-04 08:59:56 +01:00
Peter Steinberger
f9e9d4e357 fix(cli): preload plugins for local agent runs 2026-04-04 08:59:37 +01:00
Vincent Koc
12be79ac48 docs(agents): clarify mobile pairing ws scope 2026-04-04 16:57:58 +09:00
Peter Steinberger
7286a10679 fix: resolve rebase gate drift (#59815) 2026-04-04 16:57:44 +09:00
Peter Steinberger
36987831ce fix: restore current-main gate (#59815) 2026-04-04 16:57:44 +09:00
Peter Steinberger
926c107fe5 fix: narrow plugin route runtime scope fallback (#59815) (thanks @pgondhi987) 2026-04-04 16:57:44 +09:00
Pavan Kumar Gondhi
0e04ca36b9 fix: finalize issue changes 2026-04-04 16:57:44 +09:00
Pavan Kumar Gondhi
74270762ff fix: address review feedback 2026-04-04 16:57:44 +09:00
Pavan Kumar Gondhi
b02b2c3a0b fix: address issue 2026-04-04 16:57:44 +09:00
Peter Steinberger
4f3ad7c6fc docs(changelog): dedupe prompt cache entry 2026-04-04 16:57:30 +09:00
Peter Steinberger
8f85c7386b docs: close remaining cli index coverage gaps 2026-04-04 08:57:20 +01:00
Peter Steinberger
5a13756ca3 docs: expand cli index coverage refs 2026-04-04 08:56:23 +01:00
Peter Steinberger
a81cf1da1f refactor: share sdk lazy config and cli test helpers 2026-04-04 16:55:04 +09:00
Peter Steinberger
6a55556b83 docs: expand sandbox and daemon index refs 2026-04-04 08:54:21 +01:00
Peter Steinberger
edfaa01d1d refactor(plugin-sdk): split runtime helper seams 2026-04-04 08:53:19 +01:00
Peter Steinberger
470898b5e1 docs: refresh gateway update and memory refs 2026-04-04 08:52:43 +01:00
Peter Steinberger
4c450ede65 fix(feishu): narrow channel sdk seams 2026-04-04 08:50:28 +01:00
Peter Steinberger
19036ef394 fix: unblock current main checks 2026-04-04 16:50:25 +09:00
Peter Steinberger
cbc6a1ddb8 fix: restore main type surfaces 2026-04-04 16:50:25 +09:00
Peter Steinberger
04b539e98c fix: restore channel sdk schema typing 2026-04-04 16:50:25 +09:00
Peter Steinberger
f6df3ed70c fix: clean up stale cron and chat-backed tasks (#60310) 2026-04-04 16:50:25 +09:00
Peter Steinberger
6afdf10266 fix: honor exec approval security from approvals (#60310) 2026-04-04 16:50:25 +09:00
Peter Steinberger
b5265a07d7 refactor: replace 156k-line generated baselines with SHA-256 hash files
Config and Plugin SDK drift detection now compares SHA-256 hashes instead
of full JSON content. The .sha256 files (6 lines total) are tracked in git;
the full JSON baselines are gitignored and generated locally for inspection.

Same CI guarantee, zero repo churn on schema changes.
2026-04-04 16:49:21 +09:00
Peter Steinberger
b4e9802ef3 test: tidy gateway scope forwarding coverage 2026-04-04 16:48:26 +09:00
Peter Steinberger
22dad753a5 docs: refresh setup and config refs 2026-04-04 08:48:15 +01:00
Peter Steinberger
1d1c52e6e6 docs: refresh mcp approvals and hooks refs 2026-04-04 08:46:37 +01:00
sudie-codes
928a5128f4 msteams: add channel-list and channel-info actions (#57529)
* msteams: add channel-list and channel-info actions via Graph API

* msteams: use action helpers, add channel-list pagination

* msteams: address PR #57529 review feedback
2026-04-04 02:43:08 -05:00
Peter Steinberger
3967ffec22 docs: refresh agent and agents refs 2026-04-04 08:42:55 +01:00
Peter Steinberger
9bbedf3caa test: replace hanging pair approve poc coverage 2026-04-04 16:42:46 +09:00
Peter Steinberger
2c0f096688 docs: refresh channel support messaging 2026-04-04 16:41:56 +09:00
Peter Steinberger
138ef136ee docs: refresh message and channels refs 2026-04-04 08:39:04 +01:00
Brad Groux
c88d6d67c8 feat(msteams): add OpenClaw User-Agent header to Microsoft HTTP calls (#51568) (#60433)
Co-authored-by: Brad Groux <bradgroux@users.noreply.github.com>
2026-04-04 02:38:57 -05:00
Brad Groux
dd2faa3764 fix(msteams): persist conversation reference during DM pairing (#60432)
* fix(msteams): persist conversation reference during DM pairing (#43323)

* ci: retrigger checks

---------

Co-authored-by: Brad Groux <bradgroux@users.noreply.github.com>
2026-04-04 02:38:54 -05:00
Brad Groux
06c6ff6670 fix(msteams): handle Adaptive Card Action.Submit invoke activities (#60431)
* fix(msteams): handle Adaptive Card Action.Submit invoke activities (#55384)

* ci: retrigger checks

---------

Co-authored-by: Brad Groux <bradgroux@users.noreply.github.com>
2026-04-04 02:38:51 -05:00
Brad Groux
1b2fb6b98b feat: add bundled StepFun provider plugin (#60032) (#60430)
Co-authored-by: hengm3467 <100685635+hengm3467@users.noreply.github.com>
Co-authored-by: George Zhang <georgezhangtj97@gmail.com>
2026-04-04 02:38:49 -05:00
Peter Steinberger
7a03027e7f docs: refresh pairing devices and dns refs 2026-04-04 08:36:27 +01:00
Peter Steinberger
545ecc63bd docs: refresh docs search and tui refs 2026-04-04 08:34:43 +01:00
Peter Steinberger
4b490d90ec docs: expand cli security and webhook refs 2026-04-04 08:33:50 +01:00
Vincent Koc
74f60dfd0b test(agents): extend live cache runner scenarios 2026-04-04 16:33:14 +09:00
Peter Steinberger
f79c00b972 docs: expand cli maintenance summaries 2026-04-04 08:31:36 +01:00
Peter Steinberger
5d7979c5c7 docs: refresh reset and uninstall refs 2026-04-04 08:30:25 +01:00
Vincent Koc
c76646adb1 feat(agents): add prompt cache break diagnostics (#60707)
* feat(agents): add prompt cache break diagnostics

* test(agents): wire cache trace into live cache suite

* fix(agents): always record cache trace result stage

* feat(status): show cache reuse in verbose output

* fix(agents): ignore missing prompt cache usage

* chore(changelog): note prompt cache diagnostics

* fix(agents): harden prompt cache diagnostics
2026-04-04 16:29:32 +09:00
Peter Steinberger
df09fe9adf docs: refresh system health and sessions refs 2026-04-04 08:28:41 +01:00
Peter Steinberger
d584ccfc77 docs: expand logs cli reference 2026-04-04 08:27:14 +01:00
Vincent Koc
9ea37202a8 fix(config): strip legacy googlechat streamMode on load 2026-04-04 16:26:35 +09:00
Peter Steinberger
09997f032f docs: refresh tasks and status references 2026-04-04 08:24:24 +01:00
Peter Steinberger
0a5bce21a6 fix: tighten pairing guard and unblock landing gate (#60491) (thanks @eleqtrizit) 2026-04-04 16:24:10 +09:00
Agustin Rivera
cb0b15a195 fix(pair): guard setup fallback subcommands 2026-04-04 16:24:10 +09:00
Agustin Rivera
9bb97b54fe fix(pair): fail fast before qr setup lookup 2026-04-04 16:24:10 +09:00
Agustin Rivera
83e5fe5e8b fix(pair): enforce pairing scope for setup commands 2026-04-04 16:24:10 +09:00
Peter Steinberger
c3a2701c45 fix(android): delay operator bootstrap reconnect until stored auth 2026-04-04 16:23:37 +09:00
Peter Steinberger
4f95822aa8 docs: refresh cron cli references 2026-04-04 08:22:24 +01:00
Vincent Koc
d75a8933e7 fix(agents): stabilize prompt cache fingerprints (#60731)
* fix(agents): stabilize prompt cache fingerprints

* chore(changelog): note prompt cache fingerprint stability

* refactor(agents): simplify capability normalization

* refactor(agents): simplify prompt capability normalization helper
2026-04-04 16:20:36 +09:00
Peter Steinberger
0660bef81e docs: refresh cli acp and approvals summaries 2026-04-04 08:20:20 +01:00
Peter Steinberger
3e5c571e57 docs: sync browser cli summary 2026-04-04 08:18:14 +01:00
Peter Steinberger
53e2554281 docs: expand browser cli reference 2026-04-04 08:17:19 +01:00
Vincent Koc
5c685eee9c fix(config): remove lingering channel streamMode leaks (#60733) 2026-04-04 16:14:38 +09:00
Peter Steinberger
644ed24ed8 docs(changelog): clarify breaking config aliases 2026-04-04 16:14:28 +09:00
Vincent Koc
65842aabad refactor(providers): share google and xai provider helpers (#60722)
* refactor(google): share oauth token helpers

* refactor(xai): share tool auth fallback helpers

* refactor(xai): share tool auth resolution

* refactor(xai): share tool config helpers

* refactor(xai): share fallback auth helpers

* refactor(xai): share responses tool helpers

* refactor(google): share http request config helper

* fix(xai): re-export shared web search extractor

* fix(xai): import plugin config type

* fix(providers): preserve default google network guard
2026-04-04 16:14:15 +09:00
Peter Steinberger
c87903a4c6 fix(ci): restore build and typecheck on main 2026-04-04 08:13:16 +01:00
Peter Steinberger
d2bace59d1 docs: refresh live testing auth storage 2026-04-04 08:12:52 +01:00
Peter Steinberger
66b1520d92 docs: refresh auth command references 2026-04-04 08:10:34 +01:00
Peter Steinberger
32d2654340 build: bump version to 2026.4.4 2026-04-04 16:09:42 +09:00
Peter Steinberger
95a6d386c0 docs: expand provider overview coverage 2026-04-04 08:07:36 +01:00
Peter Steinberger
14cfcdba1a docs(test): refresh stale model refs 2026-04-04 08:05:49 +01:00
Peter Steinberger
f38a3ae996 docs(changelog): reorder unreleased notes 2026-04-04 16:04:40 +09:00
Peter Steinberger
9195cf839b docs: refresh provider overview references 2026-04-04 08:03:56 +01:00
Peter Steinberger
1738900a9a docs: refresh moonshot kimi coding refs 2026-04-04 08:01:41 +01:00
Peter Steinberger
0013568500 docs: refresh google and openrouter onboarding docs 2026-04-04 07:59:52 +01:00
Peter Steinberger
406a47284a fix(ci): restore channel typing and root-help metadata build 2026-04-04 07:59:32 +01:00
Peter Steinberger
7b4e20fc8c docs: sync cloudflare and synthetic provider docs 2026-04-04 07:57:43 +01:00
Peter Steinberger
20266ff7dd fix: preserve mobile bootstrap auth fallback (#60238) (thanks @ngutman) 2026-04-04 15:57:38 +09:00
Nimrod Gutman
226ca1f324 fix(auth): address qr bootstrap review feedback 2026-04-04 15:57:38 +09:00
Nimrod Gutman
a9140abea6 fix(auth): hand off qr bootstrap to bounded device tokens 2026-04-04 15:57:38 +09:00
Vincent Koc
c4597992ca fix(config): remove remaining legacy surface leaks (#60726) 2026-04-04 15:55:31 +09:00
Peter Steinberger
1c42f0e866 docs: refresh auth storage reference examples 2026-04-04 07:52:22 +01:00
Peter Steinberger
ad7461b639 docs: align auth storage and token auth guidance 2026-04-04 07:50:26 +01:00
Peter Steinberger
da3f5e9bca docs(providers): refresh model examples and env defaults 2026-04-04 07:49:22 +01:00
Vincent Koc
0609bf8581 feat(memory): harden dreaming and multilingual memory promotion (#60697)
* feat(memory): add recall audit and doctor repair flow

* refactor(memory): rename symbolic scoring and harden dreaming

* feat(memory): add multilingual concept vocabulary

* docs(changelog): note dreaming memory follow-up

* docs(changelog): shorten dreaming follow-up entry

* fix(memory): address review follow-ups

* chore(skills): tighten security triage trust model

* Update CHANGELOG.md
2026-04-04 15:48:13 +09:00
Peter Steinberger
0ab160cda9 docs(anthropic): remove setup-token setup docs 2026-04-04 15:46:25 +09:00
Peter Steinberger
1b4bb5be19 fix(anthropic): remove setup-token onboarding path 2026-04-04 15:46:25 +09:00
Peter Steinberger
15bee338e9 docs: refresh provider hook docs 2026-04-04 07:46:15 +01:00
Peter Steinberger
359c6dedbe docs: prefer channel-core in channel sdk docs 2026-04-04 07:46:15 +01:00
Peter Steinberger
6e6b4f6004 ci: gate releases on live cache floors 2026-04-04 15:44:34 +09:00
Peter Steinberger
be4eb269fc refactor: tighten ACP spawn failure typing 2026-04-04 15:43:23 +09:00
Peter Steinberger
b167ad052c refactor(providers): move defaults and error policy into plugins 2026-04-04 07:43:14 +01:00
Peter Steinberger
e34f42559f docs: refresh plugin sdk import reference 2026-04-04 07:41:44 +01:00
Peter Steinberger
27aa659498 docs: clarify plugin entry export contract 2026-04-04 07:40:24 +01:00
Peter Steinberger
d5cb8cebcd refactor(extensions): split channel runtime helper seams 2026-04-04 07:39:53 +01:00
Peter Steinberger
667a54a4b7 refactor(plugins): narrow bundled channel core seams 2026-04-04 07:39:53 +01:00
Peter Steinberger
381ee4d218 docs: align bundled plugin defaults in docs 2026-04-04 07:38:55 +01:00
Peter Steinberger
50a1fac1c5 docs: remove stale plugins status command 2026-04-04 07:37:25 +01:00
Peter Steinberger
c8be1ca6ae docs: note sdk config schema memoization 2026-04-04 07:35:04 +01:00
Peter Steinberger
25b069a6f3 refactor(gateway): split MCP loopback transport helpers 2026-04-04 15:34:13 +09:00
Peter Steinberger
f856aaea40 fix(ci): pick the real root-help bundle 2026-04-04 07:33:48 +01:00
Vincent Koc
26f0c7ee90 refactor(plugin-sdk): lazily resolve plugin config schemas 2026-04-04 15:32:33 +09:00
Peter Steinberger
85c5d90c11 docs: sync acp spawn workspace behavior 2026-04-04 07:32:09 +01:00
Peter Steinberger
71c0c2cc06 fix: harden ACP spawn workspace resolution 2026-04-04 15:29:56 +09:00
zssggle-rgb
d718d17b5b fix(acp): fall back when inherited target workspace is missing 2026-04-04 15:29:56 +09:00
Peter Steinberger
6507f54965 docs: refresh generic model examples 2026-04-04 07:27:32 +01:00
Vincent Koc
bcd11176ef refactor(amazon-bedrock): lazy-load provider registration 2026-04-04 15:26:37 +09:00
Peter Steinberger
195e380e05 docs: remove legacy cache retention notes 2026-04-04 15:26:19 +09:00
Peter Steinberger
cb6d0576be docs: refresh media understanding examples 2026-04-04 07:25:52 +01:00
Peter Steinberger
332caa4cb1 style: normalize embedded runner imports 2026-04-04 15:24:50 +09:00
Peter Steinberger
3d55b28853 style: wrap long runtime and test lines 2026-04-04 15:24:50 +09:00
Peter Steinberger
b379dac798 chore: ignore dist-runtime artifacts 2026-04-04 15:24:50 +09:00
Peter Steinberger
1809da659e docs: refresh cli and node pairing references 2026-04-04 07:23:11 +01:00
Vincent Koc
6fc69f5d33 fix(secrets): drop legacy talk apiKey target surface (#60717) 2026-04-04 15:22:41 +09:00
Peter Steinberger
e7e1707277 fix(ci): restore build and typecheck on main 2026-04-04 07:22:16 +01:00
Vincent Koc
7e7460c2f9 refactor(anthropic): lazy-load provider registration 2026-04-04 15:20:28 +09:00
Peter Steinberger
666f1f4db0 refactor(providers): remove core default and usage bias 2026-04-04 07:19:29 +01:00
Peter Steinberger
9e4cf3996e test: add gateway durable allow-always coverage (#59880) (thanks @luoyanglang) 2026-04-04 15:18:24 +09:00
Peter Steinberger
cdb572d703 test: tune live cache assertions 2026-04-04 15:18:09 +09:00
Vincent Koc
c4d2c4899d refactor(browser): lazy-load plugin registration 2026-04-04 15:17:44 +09:00
Peter Steinberger
3de09fbe74 fix: restore claude cli loopback mcp bridge (#35676) (thanks @mylukin) 2026-04-04 15:16:20 +09:00
Vincent Koc
c2435306a7 refactor(acpx): lazy-load runtime service entry 2026-04-04 15:14:51 +09:00
Peter Steinberger
e2454d4b8a docs: align provider and onboarding references 2026-04-04 07:14:28 +01:00
Peter Steinberger
dd16080af7 refactor(exec): dedupe durable approval checks 2026-04-04 07:12:26 +01:00
Peter Steinberger
b32a2cadc2 docs(acp): clarify default startup and runtime paths 2026-04-04 15:10:26 +09:00
Vincent Koc
e56ffd48df refactor(github-copilot): lazy-load provider registration 2026-04-04 15:06:02 +09:00
Ayaan Zaidi
1c1f32e756 fix: trust local bot api media roots (#60705) 2026-04-04 11:35:36 +05:30
Ayaan Zaidi
cfc52fcf2b fix(telegram): trust local bot api media roots 2026-04-04 11:35:36 +05:30
Peter Steinberger
c91b6bf322 fix(ci): unblock agent typing and cache startup metadata 2026-04-04 07:04:17 +01:00
Peter Steinberger
3a3f88a80a refactor(media): move provider defaults into media metadata 2026-04-04 07:00:47 +01:00
Peter Steinberger
fca80d2ee2 refactor(thinking): move provider thinking fallback out of core 2026-04-04 07:00:47 +01:00
Peter Steinberger
b59ce0903c docs: add SOUL personality guide 2026-04-04 14:59:35 +09:00
Vincent Koc
3437818b91 refactor(vllm): lazy-load provider registration 2026-04-04 14:56:04 +09:00
Peter Steinberger
0587fb3fc8 fix: note gateway allow-always reuse (#59880) (thanks @luoyanglang) 2026-04-04 14:55:26 +09:00
luoyanglang
b54acd97b3 fix(exec): reuse gateway allow-always approvals 2026-04-04 14:55:26 +09:00
Vincent Koc
ede6d03850 refactor(openrouter): lazy-load provider registration 2026-04-04 14:54:59 +09:00
Vincent Koc
0099c309c9 refactor(openai): lazy-load provider registration 2026-04-04 14:53:02 +09:00
Vincent Koc
fcf1aee2b4 refactor(microsoft): lazy-load speech provider 2026-04-04 14:51:55 +09:00
Vincent Koc
73115b5480 fix(zalouser): migrate legacy group allow aliases (#60702)
* fix(channels): prefer source contract surfaces in source checkouts

* fix(zalouser): migrate legacy group allow aliases
2026-04-04 14:50:15 +09:00
Peter Steinberger
ae7942bf5e fix: prefer Claude CLI in Anthropic onboarding 2026-04-04 14:49:55 +09:00
Peter Steinberger
1ab37d7a12 refactor(gateway): classify pairing locality 2026-04-04 06:47:14 +01:00
Vincent Koc
b3186aeef9 test(agents): expand live cache runner scenarios 2026-04-04 14:46:56 +09:00
Vincent Koc
32dd0aa7e7 fix(plugin-sdk): lazy acp runtime testing merge 2026-04-04 14:43:53 +09:00
Vincent Koc
fd01561327 fix(agents): close remaining prompt cache boundary gaps (#60691)
* fix(agents): route default stream fallbacks through boundary shapers

* fix(agents): close remaining cache boundary gaps

* chore(changelog): note cache prefix follow-up rollout

* fix(agents): preserve cache-safe fallback stream bases
2026-04-04 14:41:47 +09:00
Peter Steinberger
30ba837a7b test: isolate MCP live cache probe 2026-04-04 14:39:51 +09:00
Peter Steinberger
0ebc7b6077 docs: clarify anthropic claude cli migration 2026-04-04 14:38:42 +09:00
Peter Steinberger
40da986b21 fix: preserve docker cli pairing locality (#55113) (thanks @sar618) 2026-04-04 14:36:30 +09:00
sar618
224fceee1a fix(gateway): skip device pairing for authenticated CLI connections in Docker
CLI connections with valid shared auth (token/password) now bypass device
pairing, fixing the chicken-and-egg problem where Docker CLI commands fail
with 'pairing required' (1008) despite sharing the gateway's network
namespace and auth token.

The existing shouldSkipBackendSelfPairing only matched gateway-client/backend
mode. CLI connections use cli/cli mode and were excluded. Additionally,
isLocalDirectRequest produces false negatives in Docker (host networking,
network_mode sharing) even when remoteAddress is 127.0.0.1, so CLI connections
with valid shared auth skip the locality check entirely — the token is the
trust anchor.

Closes #55067
Related: #12210, #23471, #30740
2026-04-04 14:36:30 +09:00
Peter Steinberger
2b538464e1 fix(docs): format dreaming memory tables 2026-04-04 06:31:40 +01:00
Vincent Koc
71562cc570 docs(changelog): add config surface breaking note 2026-04-04 14:30:46 +09:00
Vincent Koc
b390591779 fix(matrix): migrate room allow aliases to enabled (#60690)
* fix(matrix): migrate room allow aliases to enabled

* test(matrix): keep migration coverage on the channel seam

* chore(config): refresh baselines after matrix alias cleanup
2026-04-04 14:27:50 +09:00
Vincent Koc
6e0fe1b91e docs: expand dreaming memory documentation 2026-04-04 14:25:29 +09:00
Vignesh Natarajan
10d5b8813d Agents/logging: reduce orphaned-user warning noise for background runs 2026-04-03 22:24:02 -07:00
Peter Steinberger
e4dc03f108 refactor(acpx): split Windows command parsing 2026-04-04 14:19:20 +09:00
Peter Steinberger
41243529fb refactor(providers): centralize provider model policy 2026-04-04 06:16:48 +01:00
Vincent Koc
e07d8fd20b docs(agents): tighten provider boundary guidance 2026-04-04 14:13:46 +09:00
Peter Steinberger
026ca40be9 fix(ci): repair voice-call provider resolution typing 2026-04-04 06:11:30 +01:00
Vignesh Natarajan
18016e7546 Docs/memory: add Dreaming concept page and overview links 2026-04-03 22:10:32 -07:00
Peter Steinberger
db177ab2ac docs: add changelog for #60689 2026-04-04 14:10:20 +09:00
Peter Steinberger
e985324d87 fix(acpx): preserve Windows Claude CLI paths 2026-04-04 14:10:20 +09:00
Vignesh Natarajan
9802c060bf Dreaming UI: explain modes on hover in header controls 2026-04-03 22:08:49 -07:00
Peter Steinberger
b392c78bab fix(ci): align settings host test fixtures 2026-04-04 06:08:26 +01:00
Peter Steinberger
cff8b5bebd fix(agents): preserve acp and openai wrapper defaults 2026-04-04 14:07:19 +09:00
Peter Steinberger
bc8048250e fix(agents): harden claude cli parsing and queueing 2026-04-04 14:07:19 +09:00
Peter Steinberger
4ed17fd987 refactor(voice-call): migrate legacy config via doctor 2026-04-04 14:06:52 +09:00
Vincent Koc
561db47566 docs(boundaries): add import-topology guardrails 2026-04-04 14:06:18 +09:00
Peter Steinberger
0777ddace8 perf: split more targeted test lanes 2026-04-04 06:05:24 +01:00
Peter Steinberger
5ddc57aa22 style(ui): format chat view templates 2026-04-04 06:02:57 +01:00
Peter Steinberger
64d9b65b56 style(core): format reply and infra helpers 2026-04-04 06:02:47 +01:00
Peter Steinberger
fd75d214f2 style(extensions): format channel integration updates 2026-04-04 06:02:37 +01:00
Peter Steinberger
8b5672bda4 test: align ui vitest configs with thread policy 2026-04-04 06:00:15 +01:00
Vignesh Natarajan
f8c4777515 Dreaming: move setup controls to header and tighten status plumbing 2026-04-03 21:58:46 -07:00
Vignesh Natarajan
a5f66b5c48 fix(plugins): constrain workspace discovery to .openclaw/extensions 2026-04-03 21:57:58 -07:00
Peter Steinberger
02cc09dafe test: refresh vitest config assertions 2026-04-04 05:57:27 +01:00
Peter Steinberger
ca9d2f3b41 ci: align vitest entrypoints with root config 2026-04-04 05:57:27 +01:00
Peter Steinberger
757a20b656 test: enforce thread-first vitest configs 2026-04-04 05:57:26 +01:00
Peter Steinberger
33e10c4772 fix(ci): repair bundled test selection and compat typing 2026-04-04 05:56:55 +01:00
Vincent Koc
230a39797a fix(infra): break exec safe-bin import cycle 2026-04-04 13:53:32 +09:00
Peter Steinberger
8a3d946f4a test: cover vitest contention scheduling 2026-04-04 05:51:27 +01:00
Peter Steinberger
55812eaf14 fix: throttle vitest under local contention 2026-04-04 05:50:46 +01:00
Vincent Koc
9afaec1b0c docs(changelog): add cache-prefix attribution 2026-04-04 13:47:43 +09:00
Peter Steinberger
53fd262173 ci: align pnpm pins and vitest config 2026-04-04 05:44:29 +01:00
Peter Steinberger
22e6225dd0 perf: split hooks, tui, and extension lanes 2026-04-04 05:38:47 +01:00
Peter Steinberger
af102907c5 docs: add GitHub sponsor to README 2026-04-04 13:36:58 +09:00
Peter Steinberger
39135ca3a4 refactor(voice-call): isolate config compatibility 2026-04-04 13:34:05 +09:00
Vincent Koc
64f28906de fix(agents): split system prompt cache prefix by transport (#59054)
* fix(agents): restore Anthropic prompt cache seam

* fix(agents): strip cache boundary for completions

* fix(agents): strip cache boundary for cli backends

* chore(changelog): note cross-transport cache boundary rollout

* fix(agents): route default stream fallbacks through boundary shapers

* fix(agents): strip cache boundary for provider streams
2026-04-04 13:32:32 +09:00
Peter Steinberger
b0e1551eb8 refactor(extensions): add channel-owned config schema seams 2026-04-04 05:31:11 +01:00
Peter Steinberger
c17985aa9f test: align hook install unsafe flag assertion 2026-04-04 05:27:57 +01:00
Peter Steinberger
e95b723b82 fix: load telegram command config from contract surfaces 2026-04-04 05:26:54 +01:00
Peter Steinberger
c7cb43cac9 perf: split more scoped vitest lanes 2026-04-04 05:26:32 +01:00
Peter Steinberger
64b971b2b0 fix: resolve config write test drift 2026-04-04 05:25:57 +01:00
Peter Steinberger
3a62b0e75b fix(ci): remove invalid live cache reasoning flag 2026-04-04 05:24:29 +01:00
Peter Steinberger
b16e70e37f refactor(plugins): route bundled channel config runtime through metadata 2026-04-04 05:20:43 +01:00
Peter Steinberger
5b294b7fbd test: keep vitest thread workers conservative 2026-04-04 05:20:19 +01:00
Peter Steinberger
943da1864a test: add tool-turn cache coverage 2026-04-04 13:19:00 +09:00
Peter Steinberger
53b5b1b32d fix(ci): repair redundant channel union types 2026-04-04 05:08:02 +01:00
Peter Steinberger
1246e2b03a refactor(extensions): move channel-specific config surfaces out of core 2026-04-04 05:06:32 +01:00
Peter Steinberger
0f544fa1ca fix(ci): repair bluebubbles status test import 2026-04-04 05:03:19 +01:00
Peter Steinberger
39d3cad479 fix(ci): repair check lane type drift 2026-04-04 04:59:18 +01:00
Peter Steinberger
e277ac0838 fix: defer command secret target registry loading 2026-04-04 04:58:09 +01:00
Peter Steinberger
f84486157e refactor(channels): remove bluebubbles core status collector 2026-04-04 04:53:38 +01:00
Peter Steinberger
bc457fd1b8 refactor(channels): move bootstrap channel logic behind extension seams 2026-04-04 04:53:38 +01:00
Peter Steinberger
fff7e610df feat(plugins): auto-load provider plugins from model support 2026-04-04 04:52:25 +01:00
Peter Steinberger
5b144655f2 test(ci): align channel defaults and clean stale hook tests 2026-04-04 04:51:33 +01:00
Peter Steinberger
f4fa53de3f fix(ci): repair zalouser sdk path and exec timeout kill 2026-04-04 04:51:33 +01:00
Peter Steinberger
ca99ad0af8 test: add live cache provider probes 2026-04-04 12:46:10 +09:00
Peter Steinberger
efefa5560d perf: optimize vitest jsdom and isolated lanes 2026-04-04 04:45:01 +01:00
Marcus Castro
9d1a58f551 fix(auto-reply): preserve reasoning markers during block coalescing (#60655)
* fix: preserve reasoning markers during block coalescing

* docs(changelog): add auto-reply reasoning fix entry
2026-04-04 00:44:11 -03:00
Peter Steinberger
ed0cbcba2f refactor(voice-call): use config for realtime tuning 2026-04-04 12:43:23 +09:00
@zimeg
e636ba6ab0 docs(slack): move slash command settings to matching section 2026-04-03 20:42:23 -07:00
Peter Steinberger
32ba917079 perf: split infra, tooling, and provider test lanes 2026-04-04 04:39:47 +01:00
Vignesh Natarajan
f62db7950a fix(control-ui): keep session key helpers browser-safe 2026-04-03 20:39:36 -07:00
Vincent Koc
b7ec90258b fix(plugins): preserve bundled origin when workspace matches bundled root 2026-04-04 12:38:43 +09:00
Peter Steinberger
0ad75cffe3 test: restore native root vitest entrypoint 2026-04-04 04:37:08 +01:00
Peter Steinberger
bb1cc84d50 test: default vitest root projects to threads 2026-04-04 04:37:08 +01:00
Vincent Koc
fb5066dfb1 refactor(zalouser): lazy-load account runtimes 2026-04-04 12:36:39 +09:00
Peter Steinberger
6b003a7f2b refactor(cli): reuse install safety overrides 2026-04-04 12:35:58 +09:00
Peter Steinberger
406f06dcc5 fix: preserve linked install unsafe flag and baseline regressions 2026-04-04 12:34:55 +09:00
JD Davis
8a8ea94228 CLI: forward unsafe flag to linked hook-pack probes 2026-04-04 12:34:55 +09:00
JD Davis
bac15a7313 CLI: pass unsafe flag through linked plugin probes 2026-04-04 12:34:55 +09:00
Peter Steinberger
7cd40ad565 refactor(voice-call): clean provider boundaries 2026-04-04 12:33:47 +09:00
Vincent Koc
6964e4acf7 refactor(discord): lazy-load action and audit runtimes 2026-04-04 12:32:21 +09:00
Peter Steinberger
a82bc7d887 fix(ci): align contract expectations 2026-04-04 12:29:11 +09:00
Peter Steinberger
df48a7bfc0 fix: resolve stale plugin-sdk and test type regressions 2026-04-04 04:28:59 +01:00
Peter Steinberger
eb9051cc7c refactor(openai): move native transport policy into extension 2026-04-04 04:27:14 +01:00
Peter Steinberger
585b1c9413 fix(ci): repair openai codex provider test syntax 2026-04-04 04:27:02 +01:00
Vignesh
4c1022c73b feat(memory-core): add dreaming promotion with weighted recall thresholds (#60569)
* memory-core: add dreaming promotion flow with weighted thresholds

* docs(memory): mark dreaming as experimental

* memory-core: address dreaming promotion review feedback

* memory-core: harden short-term promotion concurrency

* acpx: make abort-process test timer-independent

* memory-core: simplify dreaming config with mode presets

* memory-core: add /dreaming command and tighten recall tracking

* ui: add Dreams tab with sleeping lobster animation

Adds a new Dreams tab to the gateway UI under the Agent group.
The tab is gated behind the memory-core dreaming config — it only
appears in the sidebar when dreaming.mode is not 'off'.

Features:
- Sleeping vector lobster with breathing animation
- Floating Z's, twinkling starfield, moon glow
- Rotating dream phrase bubble (17 whimsical phrases)
- Memory stats bar (short-term, long-term, promoted)
- Active/idle visual states
- 14 unit tests

* plugins: fix --json stdout pollution from hook runner log

The hook runner initialization message was using log.info() which
writes to stdout via console.log, breaking JSON.parse() in the
Docker smoke test for 'openclaw plugins list --json'. Downgrade to
log.debug() so it only appears when debugging is enabled.

* ui: keep Dreams tab visible when dreaming is off

* tests: fix contracts and stabilize extension shards

* memory-core: harden dreaming recall persistence and locking

* fix: stabilize dreaming PR gates (#60569) (thanks @vignesh07)

* test: fix rebase drift in telegram and plugin guards
2026-04-03 20:26:53 -07:00
Vincent Koc
2687a49575 refactor(line): lazy-load channel runtime seams 2026-04-04 12:26:20 +09:00
Peter Steinberger
eeb2888f6e fix(ci): sync openai provider lockfile 2026-04-04 04:24:31 +01:00
Ayaan Zaidi
d7b8faa7bf fix: keep Kimi anthropic tool payloads native (#60391) (thanks @Eric-Guo) 2026-04-04 08:53:57 +05:30
Peter Steinberger
41e16a883b fix(cli): honor unsafe override for linked installs 2026-04-04 12:22:49 +09:00
Peter Steinberger
2416e2d51d fix(ci): repair seam drift and matrix test timing 2026-04-04 04:22:17 +01:00
Peter Steinberger
d7ba6d3e68 test: move vitest config regression under active unit surface 2026-04-04 04:19:08 +01:00
Peter Steinberger
33453838da perf: route test commands through scoped lanes 2026-04-04 04:18:10 +01:00
tmimmanuel
0fef95b17d fix: preserve Windows scheduled task restart/install behavior (#59335) (thanks @tmimmanuel)
* fix(daemon): preserve Windows Task Scheduler settings on reinstall and exit early on failed restart

* fix(daemon): add test coverage for Create/Change paths, fix early exit grace period

* fix(daemon): fix startup-fallback tests for new isRegisteredScheduledTask call

* fix(daemon): report early restart failure accurately

* fix: preserve Windows scheduled task restart/install behavior (#59335) (thanks @tmimmanuel)

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-04-04 08:46:00 +05:30
Peter Steinberger
ff0c1b57a7 fix(auth): respect externally managed codex refresh tokens 2026-04-04 04:12:05 +01:00
Vincent Koc
26c9a4ce63 fix(contracts): align runtime seams and codex expectations 2026-04-04 12:11:07 +09:00
Vincent Koc
fc79ebe098 refactor(zalouser): narrow channel runtime imports 2026-04-04 12:09:58 +09:00
Vincent Koc
20937422ca refactor(mattermost): narrow channel runtime imports 2026-04-04 12:09:54 +09:00
Vincent Koc
e750c10577 refactor(nostr): narrow channel runtime imports 2026-04-04 12:08:38 +09:00
Vincent Koc
ba20e6cd98 refactor(nextcloud-talk): narrow channel runtime imports 2026-04-04 12:08:38 +09:00
Vincent Koc
6349e6aa3e refactor(irc): narrow channel runtime imports 2026-04-04 12:08:38 +09:00
Vincent Koc
c4bae0f7bf refactor(msteams): narrow channel runtime imports 2026-04-04 12:08:38 +09:00
Peter Steinberger
a23ab9b906 refactor: move voice-call realtime providers into extensions 2026-04-04 12:07:23 +09:00
Vincent Koc
61f93540b2 refactor(discord): narrow channel runtime imports 2026-04-04 12:06:00 +09:00
Vincent Koc
9bfaf7b681 refactor(slack): narrow channel runtime imports 2026-04-04 12:06:00 +09:00
Peter Steinberger
7e69c2f6a7 test: trim remaining mock drift 2026-04-04 04:04:12 +01:00
Vincent Koc
2f5509e36d refactor(slack): lazy-load directory config seam 2026-04-04 12:03:14 +09:00
Vincent Koc
f9cf868553 refactor(slack): lazy-load target resolution seams 2026-04-04 12:02:58 +09:00
Vincent Koc
bc6b20e542 refactor(discord): lazy-load directory and resolver seams 2026-04-04 12:02:58 +09:00
Peter Steinberger
af94a3a89b test: use native vitest root projects 2026-04-04 04:01:32 +01:00
Vincent Koc
2050ef2740 refactor(whatsapp): lazy-load channel directory and action seams 2026-04-04 12:01:30 +09:00
Peter Steinberger
df86f4dc00 docs(changelog): reorder unreleased highlights 2026-04-04 12:00:10 +09:00
Vincent Koc
6c31b2fbc5 refactor(imessage): narrow channel runtime imports 2026-04-04 11:59:38 +09:00
Vincent Koc
0737816010 refactor(line): narrow channel runtime imports 2026-04-04 11:59:38 +09:00
Vincent Koc
e9d802c32b fix(openai): align gpt-5.4 codex context test 2026-04-04 11:59:05 +09:00
Peter Steinberger
94b0062e90 fix: keep local marketplace paths stable (#60556) (thanks @eleqtrizit) 2026-04-04 11:58:52 +09:00
Agustin Rivera
e8ebd6ab8c fix(marketplace): narrow canonical path checks 2026-04-04 11:58:52 +09:00
Agustin Rivera
750d963cb9 fix(marketplace): preserve local symlink installs 2026-04-04 11:58:52 +09:00
Agustin Rivera
b1dd3ded35 fix(marketplace): canonicalize remote plugin paths 2026-04-04 11:58:52 +09:00
Peter Steinberger
f25f147fc3 refactor(whatsapp): move legacy group session detection into contract surface 2026-04-04 03:57:56 +01:00
Vincent Koc
098abd484d fix(channels): keep feishu override parent fallbacks 2026-04-04 11:57:27 +09:00
Vincent Koc
5eb32f24ea refactor(discord): normalize lazy loader formatting 2026-04-04 11:53:21 +09:00
Vincent Koc
bf1b1d63bd refactor(discord): lazy-load channel send seams 2026-04-04 11:53:21 +09:00
Vincent Koc
e249a852ae refactor(slack): lazy-load async channel seams 2026-04-04 11:53:21 +09:00
Peter Steinberger
a3a06524f2 fix(ci): restore session and setup fallbacks 2026-04-04 03:52:37 +01:00
Peter Steinberger
3c23126980 fix(ci): tolerate missing contract surface roots 2026-04-04 03:52:37 +01:00
Peter Steinberger
6b3ff0dd4f feat(openai): add codex gpt-5.4-mini support 2026-04-04 11:51:57 +09:00
Vincent Koc
7df763b04d refactor(providers): share xai compat helper 2026-04-04 11:45:13 +09:00
Karl Yang
6d33c67c01 fix: enable groq and deepgram bundled media providers by default (#59982) (thanks @yxjsxy)
* fix: add enabledByDefault to groq and deepgram media plugin manifests

The groq and deepgram plugin manifests were missing the
enabledByDefault: true flag. Without this flag, both plugins are
treated as bundled-but-disabled-by-default, so resolveRuntimePluginRegistry
loads without them. When buildProviderRegistry later needs to resolve
audio providers, the active registry is used first (short-circuits
the compat path in resolvePluginCapabilityProviders), leaving groq
and deepgram absent from the registry.

This caused 'Media provider not available: groq' errors when users
configured tools.media.audio.models with groq or deepgram, even
with GROQ_API_KEY / DEEPGRAM_API_KEY set correctly.

The fix mirrors the pattern used by other audio/media-only providers
such as mistral, which already has enabledByDefault: true.

Fixes #59875

* fix: enable groq and deepgram bundled media providers by default (#59982) (thanks @yxjsxy)

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-04-04 08:11:34 +05:30
Monty Taylor
d605cb08c5 matrix: force SSSS recreation on backup reset when SSSS key is broken (bad MAC) (#60599)
Merged via squash.

Prepared head SHA: 3b0a623407
Co-authored-by: emonty <95156+emonty@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
2026-04-03 22:34:23 -04:00
Vincent Koc
fb1cb99c88 fix(xai): narrow stream wrapper params 2026-04-04 11:31:27 +09:00
Vincent Koc
b5a849801c chore(plugin-sdk): refresh api baseline 2026-04-04 11:30:30 +09:00
Vincent Koc
e273753d45 refactor(providers): share anthropic tool payload helper 2026-04-04 11:30:30 +09:00
George Zhang
87885b948a fix: handle sensitive, number-clear, and array-clear edge cases in plugin config TUI (#60640) (#60640)
- Skip sensitive fields with a note directing users to openclaw config set
  or the Web UI (WizardPrompter has no masked input)
- Clear number fields to undefined when input is empty instead of storing 0
- Allow clearing array fields to undefined via empty input
2026-04-03 19:27:26 -07:00
Vincent Koc
761bd3bbd0 refactor(providers): share passthrough replay helpers 2026-04-04 11:22:41 +09:00
Ayaan Zaidi
6a3a0c405f fix: replay interrupted recurring jobs on first restart (#60583) (thanks @joelnishanth) 2026-04-04 07:51:04 +05:30
joelnishanth
7a16e14301 fix(cron): resume interrupted recurring jobs on first restart (#60495) 2026-04-04 07:51:04 +05:30
Vincent Koc
9e389cff3d fix(config): migrate legacy group allow aliases (#60597)
* fix(config): migrate legacy group allow aliases

* fix(config): inline legacy streaming migration helpers

* refactor(config): rename legacy account matcher helper

* chore(agents): codify config contract boundaries

* fix(config): keep legacy allow aliases writable

* Update AGENTS.md
2026-04-04 11:15:32 +09:00
Ayaan Zaidi
945b198c76 fix(android): allow cleartext LAN gateways 2026-04-04 07:36:18 +05:30
Vincent Koc
94adc24393 chore(plugin-sdk): refresh api baseline 2026-04-04 11:03:28 +09:00
Vincent Koc
30479b4ee0 refactor(providers): compose provider stream wrappers 2026-04-04 11:03:28 +09:00
Michael Faath
85c76e83b7 fix: restore android talk mode reply tts (#60306) (thanks @MKV21)
* Android: keep talk-mode session key synced for TTS replies

* Android: harden talk-mode reply playback state

* Android: harden talk-mode playback cancellation

* Android: avoid stale talk-mode playback preemption

* Android: tighten talk-mode playback claiming

* fix: distill android talk-mode playback ownership

* fix: restore android talk mode reply tts (#60306) (thanks @MKV21)

---------

Co-authored-by: Michael Faath <michaelfaath@macbookpro.speedport.ip>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-04-04 07:28:56 +05:30
Vincent Koc
858bf405f4 refactor(providers): share replay and tool compat helpers (#60637)
* refactor(providers): share replay and tool compat helpers

* chore(plugin-sdk): refresh api baseline
2026-04-04 10:55:36 +09:00
Vincent Koc
dd31ee1139 fix(cli): log pending control ui build 2026-04-04 10:47:38 +09:00
Peter Steinberger
b76ed0fadf fix: harden OpenAI websocket transport 2026-04-04 02:38:36 +01:00
Peter Steinberger
1e6e685347 fix: unblock cli startup metadata 2026-04-04 02:35:36 +01:00
Peter Steinberger
143d377c5a fix(cli): keep status json startup lean 2026-04-04 02:16:56 +01:00
Gustavo Madeira Santana
3713b0e506 vertex: read ADC files without exists preflight (#60592)
Merged via squash.

Prepared head SHA: 72f7372e97
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
2026-04-03 21:13:34 -04:00
Peter Steinberger
34cd49faa6 perf: route browser and line extension tests 2026-04-04 02:08:45 +01:00
Peter Steinberger
1e90b3afcd perf: split extension channel vitest lane 2026-04-04 02:08:45 +01:00
Peter Steinberger
e941d425ac perf: split acp and ui vitest lanes 2026-04-04 02:08:45 +01:00
Peter Steinberger
fb0ff6896a perf: route contract test targets 2026-04-04 02:08:45 +01:00
Peter Steinberger
b04c4e599c perf: route bundled and extension helper tests 2026-04-04 02:08:44 +01:00
Peter Steinberger
ac11e02518 perf: route bundled and extension helper tests 2026-04-04 02:08:44 +01:00
Peter Steinberger
269771a4b6 perf: route targeted tests to scoped vitest configs 2026-04-04 02:08:44 +01:00
Peter Steinberger
37ee19521f fix(status): keep empty status path lightweight 2026-04-04 10:02:42 +09:00
Peter Steinberger
f8a3840a42 fix(ci): restore contextTokens runtime typing 2026-04-04 02:00:19 +01:00
Gustavo Madeira Santana
931ddd96f0 fix(cache): preserve full 3-turn history image cache window (#60603)
Merged via squash.

Prepared head SHA: 58d06ea372
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
2026-04-03 20:48:56 -04:00
Peter Steinberger
b8021d6709 docs: add prompt cache stability rules 2026-04-04 01:47:00 +01:00
Peter Steinberger
58d2b9dd46 fix: add runtime model contextTokens caps 2026-04-04 09:36:53 +09:00
Peter Steinberger
45675c1698 docs: update Anthropic subscription billing guidance 2026-04-04 09:32:13 +09:00
Peter Steinberger
b2fb1210e1 fix: normalize openai websocket errors 2026-04-04 01:31:49 +01:00
Peter Steinberger
a38cb20177 feat(openai): add default prompt overlay 2026-04-04 09:27:07 +09:00
Gustavo Madeira Santana
f6f7609b66 matrix: retry credentials after legacy migration race (#60591)
Merged via squash.

Prepared head SHA: e050b39de0
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
2026-04-03 20:25:49 -04:00
Boris Cherny
af81c437fa fix(cache): delay history image pruning to preserve prompt cache prefix (#58038)
pruneProcessedHistoryImages was stripping image blocks from every
already-answered user turn on each run. Turn N sends image bytes → provider
caches the prefix. Turn N+1 replaces image with text marker → bytes diverge
at that message → cache miss from there onward.

Now only prune images older than 3 assistant turns. Recent history stays
byte-identical so the cached prefix survives, while legacy sessions with
persisted image payloads still get cleaned up.
2026-04-03 17:22:58 -07:00
Gustavo Madeira Santana
300fb36879 infra: atomically replace sync JSON writes (#60589)
Merged via squash.

Prepared head SHA: cb8ed77049
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
2026-04-03 20:21:44 -04:00
Peter Steinberger
628c71103e fix: align native openai transport defaults 2026-04-04 01:20:34 +01:00
Boris Cherny
bc16b9dccf fix(cache): sort MCP tools deterministically to stabilize prompt cache (#58037)
Co-authored-by: George Zhang <georgezhangtj97@gmail.com>
2026-04-03 17:19:53 -07:00
George Zhang
881f7dc82f Plugin SDK: add plugin config TUI prompts to onboard and configure wizards (#60590) (#60590)
Wire uiHints from plugin manifests into the TUI wizard so sandbox/tool
plugins get interactive config prompts during openclaw onboard (manual
flow) and openclaw configure --section plugins.

- Add setup.plugin-config.ts: discovers plugins with non-advanced uiHints,
  generates type-aware prompts (enum→select, boolean→confirm, array→csv,
  string/number→text) from jsonSchema + uiHints metadata.
- Onboard: new step after Skills, before Hooks (skipped in QuickStart).
  Only shows plugins with unconfigured fields.
- Configure: new 'plugins' section in the section menu. Shows all
  configurable plugins with configured/total field counts.

Closes #60030
2026-04-03 17:19:19 -07:00
Boris Cherny
f6380ae4b7 fix(cache): compact newest tool results first to preserve prompt cache prefix (#58036)
* fix(cache): compact newest tool results first to preserve prompt cache prefix

compactExistingToolResultsInPlace iterated front-to-back, replacing the
oldest tool results with placeholders when context exceeded 75%. This
rewrote messages[k] for small k, invalidating the provider prompt cache
from that point onward on every subsequent turn.

Reverse the loop to compact newest-first. The cached prefix stays intact;
the tradeoff is the model loses recent tool output instead of old, which
is acceptable since this guard only fires as an emergency measure past
the 75% threshold.

* fix(cache): compact newest tool results first to preserve prompt cache prefix (#58036) Thanks @bcherny

---------

Co-authored-by: George Zhang <georgezhangtj97@gmail.com>
2026-04-03 17:19:15 -07:00
1609 changed files with 48641 additions and 166335 deletions

View File

@@ -55,6 +55,8 @@ 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.
@@ -104,5 +106,6 @@ 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.

View File

@@ -14,7 +14,7 @@ inputs:
pnpm-version:
description: pnpm version for corepack.
required: false
default: "10.23.0"
default: "10.32.1"
install-bun:
description: Whether to install Bun alongside Node.
required: false

View File

@@ -4,7 +4,7 @@ inputs:
pnpm-version:
description: pnpm version to activate via corepack.
required: false
default: "10.23.0"
default: "10.32.1"
cache-key-suffix:
description: Suffix appended to the cache key.
required: false

View File

@@ -545,7 +545,6 @@ 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
@@ -950,7 +949,7 @@ jobs:
- name: Setup pnpm + cache store
uses: ./.github/actions/setup-pnpm-store-cache
with:
pnpm-version: "10.23.0"
pnpm-version: "10.32.1"
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.

View File

@@ -20,7 +20,7 @@ concurrency:
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
NODE_VERSION: "24.x"
PNPM_VERSION: "10.23.0"
PNPM_VERSION: "10.32.1"
jobs:
validate_macos_release_request:

View File

@@ -37,7 +37,7 @@ concurrency:
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
NODE_VERSION: "24.x"
PNPM_VERSION: "10.23.0"
PNPM_VERSION: "10.32.1"
jobs:
preflight_openclaw_npm:
@@ -129,6 +129,31 @@ 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:

View File

@@ -23,7 +23,7 @@ concurrency:
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
NODE_VERSION: "24.x"
PNPM_VERSION: "10.23.0"
PNPM_VERSION: "10.32.1"
CLAWHUB_REGISTRY: "https://clawhub.ai"
CLAWHUB_REPOSITORY: "openclaw/clawhub"
# Pinned to a reviewed ClawHub commit so release behavior stays reproducible.

View File

@@ -38,7 +38,7 @@ concurrency:
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
NODE_VERSION: "24.x"
PNPM_VERSION: "10.23.0"
PNPM_VERSION: "10.32.1"
jobs:
preview_plugins_npm:

6
.gitignore vendored
View File

@@ -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,6 +136,10 @@ 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/

View File

@@ -55,6 +55,11 @@
- 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`
@@ -125,10 +130,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 hooks 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 artifacts live together under `docs/.generated/`.
- 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).
- 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, update the matching baseline artifact and 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, 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.
- 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.
@@ -140,6 +145,14 @@
- 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`.
@@ -191,10 +204,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, 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.
- 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.
- Do not set test workers above 16; tried already.
- 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`.
- 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`.
- 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 + whats covered: `docs/help/testing.md`.
@@ -252,6 +265,8 @@
- "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).

View File

@@ -4,104 +4,74 @@ 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 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.
- 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.
- Control UI/skills: add ClawHub search, detail, and install flows directly in the Skills panel. (#60134) Thanks @samzong.
- 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.
- 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)
- 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/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.
- 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.
### Fixes
- 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.
- 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.
## 2026.4.2
@@ -132,6 +102,7 @@ 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
@@ -187,6 +158,7 @@ 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
@@ -216,6 +188,12 @@ 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
@@ -589,6 +567,9 @@ 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

View File

@@ -34,7 +34,7 @@ New install? Start here: [Getting started](https://docs.openclaw.ai/start/gettin
<table>
<tr>
<td align="center" width="20%">
<td align="center" width="16.66%">
<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,7 +42,15 @@ New install? Start here: [Getting started](https://docs.openclaw.ai/start/gettin
</picture>
</a>
</td>
<td align="center" width="20%">
<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%">
<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">
@@ -50,7 +58,7 @@ New install? Start here: [Getting started](https://docs.openclaw.ai/start/gettin
</picture>
</a>
</td>
<td align="center" width="20%">
<td align="center" width="16.66%">
<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">
@@ -58,7 +66,7 @@ New install? Start here: [Getting started](https://docs.openclaw.ai/start/gettin
</picture>
</a>
</td>
<td align="center" width="20%">
<td align="center" width="16.66%">
<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">
@@ -66,7 +74,7 @@ New install? Start here: [Getting started](https://docs.openclaw.ai/start/gettin
</picture>
</a>
</td>
<td align="center" width="20%">
<td align="center" width="16.66%">
<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">

View File

@@ -65,8 +65,8 @@ android {
applicationId = "ai.openclaw.app"
minSdk = 31
targetSdk = 36
versionCode = 2026040301
versionName = "2026.4.3"
versionCode = 2026040401
versionName = "2026.4.4"
ndk {
// Support all major ABIs — native libs are tiny (~47 KB per ABI)
abiFilters += listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")

View File

@@ -71,6 +71,7 @@ class NodeRuntime(
private val identityStore = DeviceIdentityStore(appContext)
private var connectedEndpoint: GatewayEndpoint? = null
private var activeGatewayAuth: GatewayConnectAuth? = null
private val cameraHandler: CameraHandler = CameraHandler(
appContext = appContext,
@@ -299,6 +300,11 @@ 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
@@ -345,6 +351,8 @@ class NodeRuntime(
session = operatorSession,
supportsChatSubscribe = false,
isConnected = { operatorConnected },
onBeforeSpeak = { micCapture.pauseForTts() },
onAfterSpeak = { micCapture.resumeAfterTts() },
).also { speaker ->
speaker.setPlaybackEnabled(prefs.speakerEnabled.value)
}
@@ -416,14 +424,19 @@ 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()
}
@@ -793,15 +806,14 @@ class NodeRuntime(
auth: GatewayConnectAuth,
reconnect: Boolean = false,
) {
activeGatewayAuth = auth
val tls = connectionManager.resolveTlsParams(endpoint)
val connectOperator =
shouldConnectOperatorSession(
auth.token,
auth.bootstrapToken,
auth.password,
loadStoredRoleDeviceToken("operator"),
val operatorAuth =
resolveOperatorSessionConnectAuth(
auth = auth,
storedOperatorToken = loadStoredRoleDeviceToken("operator"),
)
if (!connectOperator) {
if (operatorAuth == null) {
operatorConnected = false
operatorStatusText = "Offline"
operatorSession.disconnect()
@@ -809,9 +821,9 @@ class NodeRuntime(
} else {
operatorSession.connect(
endpoint,
auth.token,
auth.bootstrapToken,
auth.password,
operatorAuth.token,
operatorAuth.bootstrapToken,
operatorAuth.password,
connectionManager.buildOperatorConnectOptions(),
tls,
)
@@ -824,7 +836,7 @@ class NodeRuntime(
connectionManager.buildNodeConnectOptions(),
tls,
)
if (reconnect && connectOperator) {
if (reconnect && operatorAuth != null) {
operatorSession.reconnect()
}
if (reconnect) {
@@ -922,8 +934,33 @@ 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()
@@ -1259,18 +1296,47 @@ 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(
token: String?,
bootstrapToken: String?,
password: String?,
auth: NodeRuntime.GatewayConnectAuth,
storedOperatorToken: String?,
): Boolean {
return (
!token.isNullOrBlank() ||
!bootstrapToken.isNullOrBlank() ||
!password.isNullOrBlank() ||
!storedOperatorToken.isNullOrBlank()
)
return resolveOperatorSessionConnectAuth(auth, storedOperatorToken) != null
}
private enum class HomeCanvasGatewayState {

View File

@@ -1,32 +1,92 @@
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 loadToken(deviceId: String, role: String): String?
fun saveToken(deviceId: String, role: String, token: String)
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 clearToken(deviceId: String, role: String)
}
class DeviceAuthStore(private val prefs: SecurePrefs) : DeviceAuthTokenStore {
override fun loadToken(deviceId: String, role: String): String? {
private val json = Json { ignoreUnknownKeys = true }
override fun loadEntry(deviceId: String, role: String): DeviceAuthEntry? {
val key = tokenKey(deviceId, role)
return prefs.getString(key)?.trim()?.takeIf { it.isNotEmpty() }
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,
)
}
override fun saveToken(deviceId: String, role: String, token: String) {
override fun saveToken(deviceId: String, role: String, token: String, scopes: List<String>) {
val normalizedScopes = normalizeScopes(scopes)
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 = deviceId.trim().lowercase()
val normalizedRole = role.trim().lowercase()
val normalizedDevice = normalizeDeviceId(deviceId)
val normalizedRole = normalizeRole(role)
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()
}
}

View File

@@ -418,11 +418,63 @@ class GatewaySession(
}
throw GatewayConnectFailure(error)
}
handleConnectSuccess(res, identity.deviceId)
handleConnectSuccess(res, identity.deviceId, selectedAuth.authSource)
connectDeferred.complete(Unit)
}
private fun handleConnectSuccess(res: RpcResponse, deviceId: String) {
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,
) {
val payloadJson = res.payloadJson ?: throw IllegalStateException("connect failed: missing payload")
val obj = json.parseToJsonElement(payloadJson).asObjectOrNull() ?: throw IllegalStateException("connect failed")
pendingDeviceTokenRetry = false
@@ -432,8 +484,27 @@ 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()) {
deviceAuthStore.saveToken(deviceId, authRole, deviceToken)
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)
}
}
}
val rawCanvas = obj["canvasHostUrl"].asStringOrNull()
canvasHostUrl = normalizeCanvasHostUrl(rawCanvas, endpoint, isTlsConnection = tls != null)
@@ -899,6 +970,8 @@ 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

View File

@@ -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.isLoopbackGatewayHost
import ai.openclaw.app.gateway.isPrivateLanGatewayHost
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 = isLoopbackGatewayHost(endpoint.host)
val cleartextAllowedHost = isPrivateLanGatewayHost(endpoint.host)
if (isManual) {
if (!manualTlsEnabled && cleartextAllowedHost) return null

View File

@@ -1,6 +1,6 @@
package ai.openclaw.app.ui
import ai.openclaw.app.gateway.isLoopbackGatewayHost
import ai.openclaw.app.gateway.isPrivateLanGatewayHost
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 =
"Non-loopback mobile nodes require wss:// or Tailscale Serve. ws:// is allowed only for localhost and the Android emulator."
"Tailscale and public mobile nodes require wss:// or Tailscale Serve. ws:// is allowed for private LAN, 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 && !isLoopbackGatewayHost(host)) {
if (!tls && !isPrivateLanGatewayHost(host)) {
return GatewayEndpointParseResult(error = GatewayEndpointValidationError.INSECURE_REMOTE_URL)
}
val defaultPort =

View File

@@ -14,11 +14,13 @@ 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
@@ -99,11 +101,27 @@ 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 {
@@ -126,6 +144,58 @@ 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) {

View File

@@ -22,11 +22,13 @@ 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
@@ -46,6 +48,8 @@ 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"
@@ -101,6 +105,7 @@ 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
@@ -163,8 +168,11 @@ class TalkModeManager(
?: waitForAssistantText(session, startedAt, if (ok) 12_000 else 25_000)
if (!assistant.isNullOrBlank()) {
val playbackToken = playbackGeneration.incrementAndGet()
cancelActivePlayback()
_statusText.value = "Speaking…"
playAssistant(assistant, playbackToken)
runPlaybackSession(playbackToken) {
playAssistant(assistant, playbackToken)
}
} else {
_statusText.value = "No reply"
}
@@ -180,14 +188,12 @@ class TalkModeManager(
fun playTtsForText(text: String) {
val playbackToken = playbackGeneration.incrementAndGet()
ttsJob?.cancel()
ttsJob = scope.launch {
cancelActivePlayback()
scope.launch {
reloadConfig()
ensurePlaybackActive(playbackToken)
_isSpeaking.value = true
_statusText.value = "Speaking…"
playAssistant(text, playbackToken)
ttsJob = null
runPlaybackSession(playbackToken) {
playAssistant(text, playbackToken)
}
}
}
@@ -270,10 +276,11 @@ class TalkModeManager(
suspend fun speakAssistantReply(text: String) {
if (!playbackEnabled) return
val playbackToken = playbackGeneration.incrementAndGet()
stopSpeaking(resetInterrupt = false)
cancelActivePlayback()
ensureConfigLoaded()
ensurePlaybackActive(playbackToken)
playAssistant(text, playbackToken)
runPlaybackSession(playbackToken) {
playAssistant(text, playbackToken)
}
}
private fun start() {
@@ -483,9 +490,10 @@ class TalkModeManager(
}
Log.d(tag, "assistant text ok chars=${assistant.length}")
val playbackToken = playbackGeneration.incrementAndGet()
stopSpeaking(resetInterrupt = false)
ensurePlaybackActive(playbackToken)
playAssistant(assistant, playbackToken)
cancelActivePlayback()
runPlaybackSession(playbackToken) {
playAssistant(assistant, playbackToken)
}
} catch (err: Throwable) {
if (err is CancellationException) {
Log.d(tag, "finalize speech cancelled")
@@ -665,12 +673,60 @@ class TalkModeManager(
}
_statusText.value = "Speak failed: ${err.message ?: err::class.simpleName}"
Log.w(tag, "system tts failed: ${err.message ?: err::class.simpleName}")
} finally {
_isSpeaking.value = false
}
}
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()
}
private suspend fun speakWithSystemTts(text: String, directive: TalkDirective?, playbackToken: Long) {
ensurePlaybackActive(playbackToken)
val engine = ensureTextToSpeech()

View File

@@ -21,17 +21,72 @@ import java.util.UUID
@Config(sdk = [34])
class GatewayBootstrapAuthTest {
@Test
fun connectsOperatorSessionWhenBootstrapAuthExists() {
assertTrue(shouldConnectOperatorSession(token = "", bootstrapToken = "bootstrap-1", password = "", storedOperatorToken = ""))
assertTrue(shouldConnectOperatorSession(token = null, bootstrapToken = "bootstrap-1", password = null, storedOperatorToken = null))
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,
),
)
}
@Test
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))
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,
)
}
@Test
@@ -97,7 +152,7 @@ class GatewayBootstrapAuthTest {
assertEquals("fp-1", prefs.loadGatewayTlsFingerprint(endpoint.stableId))
assertEquals("setup-bootstrap-token", desiredBootstrapToken(runtime, "nodeSession"))
assertEquals("setup-bootstrap-token", desiredBootstrapToken(runtime, "operatorSession"))
assertNull(desiredBootstrapToken(runtime, "operatorSession"))
}
@Test

View File

@@ -0,0 +1,63 @@
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)
}
}

View File

@@ -35,12 +35,18 @@ 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, String>()
private val tokens = mutableMapOf<String, DeviceAuthEntry>()
override fun loadToken(deviceId: String, role: String): String? = tokens["${deviceId.trim()}|${role.trim()}"]?.trim()?.takeIf { it.isNotEmpty() }
override fun loadEntry(deviceId: String, role: String): DeviceAuthEntry? = tokens["${deviceId.trim()}|${role.trim()}"]
override fun saveToken(deviceId: String, role: String, token: String) {
tokens["${deviceId.trim()}|${role.trim()}"] = token.trim()
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 clearToken(deviceId: String, role: String) {
@@ -213,6 +219,144 @@ 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)
@@ -470,9 +614,14 @@ class GatewaySessionInvokeTest {
}
}
private fun connectResponseFrame(id: String, canvasHostUrl: String? = null): String {
private fun connectResponseFrame(
id: String,
canvasHostUrl: String? = null,
authJson: String? = null,
): String {
val canvas = canvasHostUrl?.let { "\"canvasHostUrl\":\"$it\"," } ?: ""
return """{"type":"res","id":"$id","ok":true,"payload":{$canvas"snapshot":{"sessionDefaults":{"mainSessionKey":"main"}}}}"""
val auth = authJson?.let { "\"auth\":$it," } ?: ""
return """{"type":"res","id":"$id","ok":true,"payload":{$canvas$auth"snapshot":{"sessionDefaults":{"mainSessionKey":"main"}}}}"""
}
private fun startGatewayServer(

View File

@@ -108,7 +108,7 @@ class ConnectionManagerTest {
}
@Test
fun resolveTlsParamsForEndpoint_manualPrivateLanRequiresTlsWhenToggleIsOff() {
fun resolveTlsParamsForEndpoint_manualPrivateLanCanStayCleartextWhenToggleIsOff() {
val endpoint = GatewayEndpoint.manual(host = "192.168.1.20", port = 18789)
val params =
@@ -118,9 +118,7 @@ class ConnectionManagerTest {
manualTlsEnabled = false,
)
assertEquals(true, params?.required)
assertNull(params?.expectedFingerprint)
assertEquals(false, params?.allowTOFU)
assertNull(params)
}
@Test
@@ -148,7 +146,7 @@ class ConnectionManagerTest {
}
@Test
fun resolveTlsParamsForEndpoint_discoveryPrivateLanWithoutHintsRequiresTls() {
fun resolveTlsParamsForEndpoint_discoveryPrivateLanWithoutHintsCanStayCleartext() {
val endpoint =
GatewayEndpoint(
stableId = "_openclaw-gw._tcp.|local.|Test",
@@ -166,9 +164,7 @@ class ConnectionManagerTest {
manualTlsEnabled = false,
)
assertEquals(true, params?.required)
assertNull(params?.expectedFingerprint)
assertEquals(false, params?.allowTOFU)
assertNull(params)
}
@Test

View File

@@ -290,11 +290,19 @@ class GatewayConfigResolverTest {
}
@Test
fun parseGatewayEndpointResultFlagsInsecureLanCleartextGateway() {
fun parseGatewayEndpointResultAcceptsLanCleartextGateway() {
val parsed = parseGatewayEndpointResult("ws://192.168.1.20:18789")
assertNull(parsed.config)
assertEquals(GatewayEndpointValidationError.INSECURE_REMOTE_URL, parsed.error)
assertEquals(
GatewayEndpointConfig(
host = "192.168.1.20",
port = 18789,
tls = false,
displayUrl = "http://192.168.1.20:18789",
),
parsed.config,
)
assertNull(parsed.error)
}
@Test

View File

@@ -1,8 +1,8 @@
// Shared iOS version defaults.
// Generated overrides live in build/Version.xcconfig (git-ignored).
OPENCLAW_GATEWAY_VERSION = 2026.4.3
OPENCLAW_MARKETING_VERSION = 2026.4.3
OPENCLAW_BUILD_VERSION = 2026040301
OPENCLAW_GATEWAY_VERSION = 2026.4.4
OPENCLAW_MARKETING_VERSION = 2026.4.4
OPENCLAW_BUILD_VERSION = 2026040401
#include? "../build/Version.xcconfig"

View File

@@ -1816,7 +1816,7 @@ private extension NodeAppModel {
return DeviceAuthStore.loadToken(deviceId: identity.deviceId, role: role) != nil
}
static func shouldStartOperatorGatewayLoop(
nonisolated static func shouldStartOperatorGatewayLoop(
token: String?,
bootstrapToken: String?,
password: String?,
@@ -1837,7 +1837,7 @@ private extension NodeAppModel {
return hasStoredOperatorToken
}
static func clearingBootstrapToken(in config: GatewayConnectConfig?) -> GatewayConnectConfig? {
nonisolated static func clearingBootstrapToken(in config: GatewayConnectConfig?) -> GatewayConnectConfig? {
guard let config else { return nil }
let trimmedBootstrapToken = config.bootstrapToken?
.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
@@ -1878,6 +1878,36 @@ 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 }
@@ -1927,17 +1957,20 @@ 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)
displayName: nodeOptions.clientDisplayName,
includeApprovalScope: self.shouldRequestOperatorApprovalScope(
token: reconnectAuth.token,
password: reconnectAuth.password))
do {
let reconnectAuth = self.currentGatewayReconnectAuth(
fallbackToken: token,
fallbackBootstrapToken: bootstrapToken,
fallbackPassword: password)
try await self.operatorGateway.connect(
url: url,
token: reconnectAuth.token,
@@ -2049,13 +2082,14 @@ 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: currentOptions,
connectOptions: connectedOptions,
sessionBox: sessionBox,
onConnected: { [weak self] in
guard let self else { return }
@@ -2071,24 +2105,13 @@ private extension NodeAppModel {
reconnectAuth.bootstrapToken?.trimmingCharacters(in: .whitespacesAndNewlines)
.isEmpty == false
if usedBootstrapToken {
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)
}
}
await self.handleSuccessfulBootstrapGatewayOnboarding(
url: url,
stableID: stableID,
token: reconnectAuth.token,
password: reconnectAuth.password,
nodeOptions: connectedOptions,
sessionBox: sessionBox)
}
let relayData = await MainActor.run {
(
@@ -2246,10 +2269,47 @@ private extension NodeAppModel {
}
}
func makeOperatorConnectOptions(clientId: String, displayName: String?) -> GatewayConnectOptions {
GatewayConnectOptions(
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(
role: "operator",
scopes: ["operator.read", "operator.write", "operator.talk.secrets"],
scopes: scopes,
caps: [],
commands: [],
permissions: [:],
@@ -3137,11 +3197,22 @@ 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()
}
static func _test_shouldStartOperatorGatewayLoop(
nonisolated static func _test_shouldStartOperatorGatewayLoop(
token: String?,
bootstrapToken: String?,
password: String?,
@@ -3154,6 +3225,41 @@ 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

View File

@@ -70,6 +70,52 @@ 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 {

View File

@@ -2,6 +2,7 @@ import OpenClawKit
import Foundation
import Testing
import UIKit
import UserNotifications
@testable import OpenClaw
private func makeAgentDeepLinkURL(
@@ -68,6 +69,28 @@ 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) {
@@ -127,6 +150,15 @@ private final class MockWatchMessagingService: @preconcurrency WatchMessagingSer
)
}
@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")!,
@@ -145,7 +177,7 @@ private final class MockWatchMessagingService: @preconcurrency WatchMessagingSer
clientMode: "node",
clientDisplayName: nil))
let cleared = NodeAppModel.clearingBootstrapToken(in: config)
let cleared = NodeAppModel._test_clearingBootstrapToken(in: config)
#expect(cleared?.bootstrapToken == nil)
#expect(cleared?.url == config.url)
#expect(cleared?.stableID == config.stableID)

View File

@@ -15,9 +15,9 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>2026.4.3</string>
<string>2026.4.4</string>
<key>CFBundleVersion</key>
<string>2026040301</string>
<string>2026040401</string>
<key>CFBundleIconFile</key>
<string>OpenClaw</string>
<key>CFBundleURLTypes</key>

View File

@@ -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

View File

@@ -542,6 +542,77 @@ 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?,
@@ -572,18 +643,37 @@ public actor GatewayChannelActor {
} else if let tick = ok.policy["tickIntervalMs"]?.value as? Int {
self.tickIntervalMs = Double(tick)
}
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(
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,
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()

View File

@@ -13,6 +13,7 @@ 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]?
@@ -20,6 +21,10 @@ 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 } }
@@ -79,11 +84,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))
return .data(Self.connectOkData(id: id, auth: self.helloAuth))
}
try await Task.sleep(nanoseconds: 1_000_000)
}
return .data(Self.connectOkData(id: "connect"))
return .data(Self.connectOkData(id: "connect", auth: self.helloAuth))
}
func receive(
@@ -110,8 +115,8 @@ private final class FakeGatewayWebSocketTask: WebSocketTasking, @unchecked Senda
return (try? JSONSerialization.data(withJSONObject: frame)) ?? Data()
}
private static func connectOkData(id: String) -> Data {
let payload: [String: Any] = [
private static func connectOkData(id: String, auth: [String: Any]? = nil) -> Data {
var payload: [String: Any] = [
"type": "hello-ok",
"protocol": 2,
"server": [
@@ -137,6 +142,9 @@ private final class FakeGatewayWebSocketTask: WebSocketTasking, @unchecked Senda
"tickIntervalMs": 30_000,
],
]
if let auth {
payload["auth"] = auth
}
let frame: [String: Any] = [
"type": "res",
"id": id,
@@ -149,9 +157,14 @@ 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 }
}
@@ -164,7 +177,7 @@ private final class FakeGatewayWebSocketSession: WebSocketSessioning, @unchecked
_ = url
return self.lock.withLock {
self.makeCount += 1
let task = FakeGatewayWebSocketTask()
let task = FakeGatewayWebSocketTask(helloAuth: self.helloAuth)
self.tasks.append(task)
return WebSocketTaskBox(task: task)
}
@@ -177,6 +190,7 @@ private actor SeqGapProbe {
func value() -> Bool { self.saw }
}
@Suite(.serialized)
struct GatewayNodeSessionTests {
@Test
func scannedSetupCodePrefersBootstrapAuthOverStoredDeviceToken() async throws {
@@ -234,6 +248,204 @@ 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(

View File

@@ -1,6 +1,7 @@
import XCTest
@testable import OpenClawKit
@MainActor
final class TalkSystemSpeechSynthesizerTests: XCTestCase {
func testWatchdogTimeoutDefaultsToLatinProfile() {
let timeout = TalkSystemSpeechSynthesizer.watchdogTimeoutSeconds(

View File

@@ -1,14 +1,21 @@
# Generated Docs Artifacts
These baseline artifacts are generated from the repo-owned OpenClaw config schema and bundled channel/plugin metadata.
SHA-256 hash files are the tracked drift-detection artifacts. The full JSON
baselines are generated locally (gitignored) for inspection only.
- 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`.
**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`

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,4 @@
ad87e3ff267b151ae163402f3cb52503e10641e332bcfbb6a574bbd7087a2484 config-baseline.json
03ff4a3e314f17dd8851aed3653269294bc62412bee05a6804dce840bd3d7551 config-baseline.core.json
73b57f395a2ad983f1660112d0b2b998342f1ddbe3089b440d7f73d0665de739 config-baseline.channel.json
9d5cb864e70768b66c1ecd881a9a584b7696ef2e5b32df686cfdc3fa21ddabbe config-baseline.plugin.json

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,2 @@
80a8238588cca3c6e0f89115f4271834b6e18f3497f70bc91dccc9203539cdd9 plugin-sdk-api-baseline.json
68ed97a285d82f995f377db7823104976e8ebab5c6d0750332acb1d3d79288ef plugin-sdk-api-baseline.jsonl

View File

@@ -47,6 +47,26 @@
"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"

View File

@@ -0,0 +1,3 @@
<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>

After

Width:  |  Height:  |  Size: 829 B

View File

@@ -0,0 +1,3 @@
<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>

After

Width:  |  Height:  |  Size: 826 B

View File

@@ -37,6 +37,11 @@ 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 |
@@ -163,7 +168,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.2-mini"}'
-d '{"message":"Summarize inbox","name":"Email","model":"openai/gpt-5.4-mini"}'
```
Fields: `message` (required), `name`, `agentId`, `wakeMode`, `deliver`, `channel`, `to`, `model`, `thinking`, `timeoutSeconds`.
@@ -313,7 +318,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 called without `--force`.
- `reason: not-due` in run output means manual run was checked with `openclaw cron run <jobId> --due` and the job was not due yet.
### Cron fired but no delivery

View File

@@ -25,6 +25,7 @@ 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.
@@ -50,6 +51,15 @@ 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
@@ -91,10 +101,17 @@ stateDiagram-v2
| `failed` | Completed with an error |
| `timed_out` | Exceeded the configured timeout |
| `cancelled` | Stopped by the operator via `openclaw tasks cancel` |
| `lost` | Backing child session disappeared (detected after a 5-minute grace period) |
| `lost` | The runtime lost authoritative backing state 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:
@@ -167,11 +184,38 @@ 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 | Backing session is gone |
| `lost` | error | Runtime-backed task ownership disappeared |
| `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
@@ -216,7 +260,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 if active tasks' backing sessions still exist. If a child session has been gone for more than 5 minutes, the task is marked `lost`.
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`.
2. **Cleanup stamping** — sets a `cleanupAfter` timestamp on terminal tasks (endedAt + 7 days).
3. **Pruning** — deletes records past their `cleanupAfter` date.

View File

@@ -319,7 +319,9 @@ Verbose restore diagnostics:
openclaw matrix verify backup restore --verbose
```
Delete the current server backup and create a fresh backup baseline:
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:
```bash
openclaw matrix verify backup reset --yes
@@ -366,8 +368,11 @@ 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.
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.
### Fresh backup baseline

View File

@@ -100,7 +100,7 @@ Stored under `~/.openclaw/devices/`:
### Notes
- The legacy `node.pair.*` API (CLI: `openclaw nodes pending/approve`) is a
- The legacy `node.pair.*` API (CLI: `openclaw nodes pending|approve|reject|rename`) is a
separate gateway-owned pairing store. WS nodes still require device pairing.
## Related docs

View File

@@ -218,6 +218,19 @@ 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.
@@ -267,19 +280,6 @@ 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`.

View File

@@ -21,6 +21,24 @@ 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 |
@@ -275,6 +293,7 @@ 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:

View File

@@ -10,20 +10,48 @@ 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.

View File

@@ -19,7 +19,9 @@ 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
@@ -53,6 +55,8 @@ 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.
@@ -78,6 +82,75 @@ 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:
@@ -96,6 +169,24 @@ 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

View File

@@ -11,6 +11,8 @@ 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)
@@ -41,10 +43,15 @@ 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`:
@@ -103,6 +110,24 @@ 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).

View File

@@ -1,5 +1,5 @@
---
summary: "CLI reference for `openclaw browser` (profiles, tabs, actions, Chrome MCP, and CDP)"
summary: "CLI reference for `openclaw browser` (lifecycle, profiles, tabs, actions, state, and debugging)"
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 OpenClaws browser control server and run browser actions (tabs, snapshots, screenshots, navigation, clicks, typing).
Manage OpenClaw's browser control surface and run browser actions (lifecycle, profiles, tabs, snapshots, screenshots, navigation, input, state emulation, and debugging).
Related:
@@ -20,6 +20,7 @@ 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).
@@ -32,6 +33,15 @@ 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
@@ -65,6 +75,7 @@ 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
```
@@ -78,6 +89,9 @@ 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>
@@ -103,6 +117,64 @@ 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

View File

@@ -26,6 +26,13 @@ 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
@@ -36,6 +43,16 @@ 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
@@ -63,6 +80,11 @@ 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.
@@ -82,6 +104,7 @@ 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`.

View File

@@ -11,10 +11,28 @@ 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"
@@ -69,6 +87,8 @@ 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:

View File

@@ -16,15 +16,35 @@ 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. 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.
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.
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.
@@ -39,4 +59,5 @@ Notes:
openclaw configure
openclaw configure --section web
openclaw configure --section model --section channels
openclaw configure --section gateway --section daemon
```

View File

@@ -21,6 +21,10 @@ 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.
@@ -28,6 +32,9 @@ 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.
@@ -78,3 +85,31 @@ 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
```

View File

@@ -75,6 +75,8 @@ 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.
@@ -83,6 +85,8 @@ 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).
@@ -100,6 +104,7 @@ 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

View File

@@ -19,5 +19,30 @@ 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.

View File

@@ -9,7 +9,20 @@ 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.

View File

@@ -21,8 +21,21 @@ 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.

View File

@@ -91,6 +91,20 @@ 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.
@@ -113,10 +127,12 @@ 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.
@@ -182,6 +198,21 @@ 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
@@ -192,6 +223,12 @@ 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`.

View File

@@ -9,10 +9,21 @@ 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:

View File

@@ -10,6 +10,8 @@ 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)
@@ -67,7 +69,7 @@ Show detailed information about a specific hook.
**Arguments:**
- `<name>`: Hook name (e.g., `session-memory`)
- `<name>`: Hook name or hook key (e.g., `session-memory`)
**Options:**
@@ -125,7 +127,7 @@ Not ready: 0
openclaw hooks enable <name>
```
Enable a specific hook by adding it to your config (`~/.openclaw/config.json`).
Enable a specific hook by adding it to your config (`~/.openclaw/openclaw.json` by default).
**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 cant be enabled/disabled here. Enable/disable the plugin instead.
@@ -186,6 +188,11 @@ 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

View File

@@ -46,6 +46,7 @@ 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)
@@ -105,6 +106,7 @@ openclaw [--dev] [--profile <name>] <command>
set
unset
file
schema
validate
completion
doctor
@@ -122,16 +124,26 @@ 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
@@ -152,6 +164,28 @@ 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
@@ -163,18 +197,26 @@ 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
@@ -223,13 +265,34 @@ 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
@@ -297,10 +360,49 @@ Note: plugins can add additional top-level commands (for example `openclaw voice
## Secrets
- `openclaw secrets reload` — re-resolve refs and atomically swap the runtime snapshot.
- `openclaw secrets audit` — scan for plaintext residues, unresolved refs, and precedence drift (`--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).
### `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.
## Plugins
@@ -319,9 +421,25 @@ Most plugin changes require a gateway restart. See [/plugin](/tools/plugin).
Vector search over `MEMORY.md` + `memory/*.md`:
- `openclaw memory status` — show index stats.
- `openclaw memory status` — show index stats; use `--deep` for provider probes or `--fix` to repair stale recall/promotion artifacts.
- `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
@@ -335,6 +453,22 @@ 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.
@@ -363,7 +497,7 @@ Options:
- `--mode <local|remote>`
- `--flow <quickstart|advanced|manual>` (manual is an alias for advanced)
- `--auth-choice <choice>` where `<choice>` is one of:
`setup-token`, `token`, `chutes`, `deepseek-api-key`, `openai-codex`, `openai-api-key`,
`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`,
@@ -374,10 +508,6 @@ 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>`
@@ -423,6 +553,10 @@ 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
@@ -460,6 +594,72 @@ 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`
@@ -501,12 +701,39 @@ 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:
@@ -519,6 +746,23 @@ 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.
@@ -550,6 +794,11 @@ 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.
@@ -564,6 +813,69 @@ 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).
@@ -573,14 +885,31 @@ 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`
@@ -610,6 +939,8 @@ 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>`
@@ -625,15 +956,23 @@ 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`
- `--local` (embedded run; plugin registry still preloads first)
- `--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.
@@ -657,6 +996,7 @@ 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`
@@ -673,7 +1013,7 @@ Add routing bindings for an agent.
Options:
- `--agent <id>`
- `--agent <id>` (defaults to the current default agent)
- `--bind <channel[:accountId]>` (repeatable)
- `--json`
@@ -683,11 +1023,13 @@ Remove routing bindings for an agent.
Options:
- `--agent <id>`
- `--agent <id>` (defaults to the current default agent)
- `--bind <channel[:accountId]>` (repeatable)
- `--all`
- `--json`
Use either `--all` or `--bind`, not both.
#### `agents delete <id>`
Delete an agent and prune its workspace + state.
@@ -697,11 +1039,146 @@ 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.
See [`acp`](/cli/acp) for full options and examples.
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`
### `status`
@@ -747,6 +1224,7 @@ Options:
- `--json`
- `--timeout <ms>`
- `--verbose`
- `--debug` (alias for `--verbose`)
### `sessions`
@@ -765,6 +1243,10 @@ 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`
@@ -800,6 +1282,7 @@ Options:
Notes:
- `--non-interactive` requires `--yes` and explicit scopes (or `--all`).
- `--all` removes service, state, workspace, and app together.
### `tasks`
@@ -810,10 +1293,19 @@ 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`
@@ -860,13 +1352,33 @@ Notes:
- `gateway status` probes the Gateway RPC by default using the services 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` prints which config path the CLI uses vs which config the service likely uses (service env), plus the resolved probe target URL.
- `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.
- 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.
@@ -881,6 +1393,10 @@ 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:
@@ -892,6 +1408,11 @@ 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).
@@ -900,7 +1421,7 @@ Include `--token` or `--password` explicitly. Missing explicit credentials is an
Subcommands:
- `gateway call <method> [--params <json>]`
- `gateway call <method> [--params <json>] [--url <url>] [--token <token>] [--password <password>] [--timeout <ms>] [--expect-final] [--json]`
- `gateway health`
- `gateway status`
- `gateway probe`
@@ -923,17 +1444,13 @@ Tip: these config write RPCs preflight active SecretRef resolution for refs in t
See [/concepts/models](/concepts/models) for fallback behavior and scanning strategy.
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.
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 Claude CLI migration:
@@ -941,7 +1458,13 @@ Anthropic Claude CLI migration:
openclaw models auth login --provider anthropic --method cli --set-default
```
Note: `--auth-choice anthropic-cli` is a deprecated legacy alias. Use `models auth login` instead.
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.
### `models` (root)
@@ -1034,12 +1557,17 @@ Options:
Options:
- `add`: interactive auth helper
- `add`: interactive auth helper (provider auth flow or token paste)
- `login`: `--provider <name>`, `--method <method>`, `--set-default`
- `login-github-copilot`: GitHub Copilot OAuth login flow
- `setup-token`: `--provider <name>` (default `anthropic`), `--yes`
- `login-github-copilot`: GitHub Copilot OAuth login flow (`--yes`)
- `setup-token`: `--provider <name>`, `--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:
@@ -1096,12 +1624,14 @@ Subcommands:
- `cron enable <id>`
- `cron disable <id>`
- `cron runs --id <id> [--limit <n>]`
- `cron run <id> [--force]`
- `cron run <id> [--due]`
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).
@@ -1166,7 +1696,7 @@ Browser control CLI (dedicated Chrome/Brave/Edge/Chromium). See [`openclaw brows
Common options:
- `--url`, `--token`, `--timeout`, `--json`
- `--url`, `--token`, `--timeout`, `--expect-final`, `--json`
- `--browser-profile <name>`
Manage:
@@ -1180,7 +1710,7 @@ Manage:
- `browser focus <targetId>`
- `browser close [targetId]`
- `browser profiles`
- `browser create-profile --name <name> [--color <hex>] [--cdp-url <url>]`
- `browser create-profile --name <name> [--color <hex>] [--cdp-url <url>] [--driver existing-session] [--user-data-dir <path>]`
- `browser delete-profile --name <name>`
Inspect:
@@ -1206,8 +1736,30 @@ 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.

View File

@@ -13,16 +13,47 @@ 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"
```
Use `--local-time` to render timestamps in your local timezone.
## 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.

View File

@@ -382,6 +382,13 @@ 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

View File

@@ -1,8 +1,9 @@
---
summary: "CLI reference for `openclaw memory` (status/index/search)"
summary: "CLI reference for `openclaw memory` (status/index/search/promote)"
read_when:
- You want to index or search semantic memory
- Youre debugging memory availability or indexing
- You want to promote recalled short-term memory into `MEMORY.md`
title: "memory"
---
@@ -21,9 +22,13 @@ 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
@@ -42,6 +47,7 @@ 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`:
@@ -58,9 +64,72 @@ 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`.

View File

@@ -68,11 +68,15 @@ 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`, `--reply-to`, `--thread-id`, `--gif-playback`
- 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
- 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`
@@ -192,7 +196,7 @@ Name lookup:
- `broadcast`
- Channels: any configured channel; use `--channel all` to target all providers
- Required: `--targets` (repeat)
- Required: `--targets <target...>`
- Optional: `--message`, `--media`, `--dry-run`
## Examples
@@ -214,6 +218,14 @@ 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:
```
@@ -272,6 +284,14 @@ 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

View File

@@ -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 headers.
provider usage windows and quota snapshots.
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 agents model/auth state. When omitted,
@@ -67,10 +67,14 @@ openclaw models fallbacks list
```bash
openclaw models auth add
openclaw models auth login --provider <id>
openclaw models auth setup-token
openclaw models auth setup-token --provider <id>
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 plugins auth flow (OAuth/API key). Use
`openclaw plugins list` to see which providers are installed.
@@ -85,6 +89,8 @@ 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` prompts for a setup-token value (generate it with `claude setup-token` on any machine).
- `setup-token` and `paste-token` remain generic token commands for providers
that expose token auth methods.
- `paste-token` accepts a token string generated elsewhere or from automation.
- 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.
- 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.

View File

@@ -1,5 +1,5 @@
---
summary: "CLI reference for `openclaw nodes` (list/status/approve/invoke, camera/canvas/screen)"
summary: "CLI reference for `openclaw nodes` (status, pairing, invoke, camera/canvas/screen)"
read_when:
- Youre managing paired nodes (cameras, screen, canvas)
- You need to approve requests or invoke node commands
@@ -28,6 +28,8 @@ 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

View File

@@ -142,9 +142,12 @@ 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, 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.
- 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.
- 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,

View File

@@ -20,10 +20,43 @@ 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>`.

View File

@@ -32,8 +32,9 @@ openclaw plugins update --all
openclaw plugins marketplace list <marketplace>
```
Bundled plugins ship with OpenClaw but start disabled. Use `plugins enable` to
activate them.
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`.
Native OpenClaw plugins must ship `openclaw.plugin.json` with an inline JSON
Schema (`configSchema`, even if empty). Compatible bundles use their own bundle

View File

@@ -10,11 +10,26 @@ 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
```
Run `openclaw backup create` first if you want a restorable snapshot before removing local state.
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.

View File

@@ -49,6 +49,7 @@ 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:
@@ -57,6 +58,13 @@ 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:
@@ -134,6 +142,7 @@ 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:

View File

@@ -14,12 +14,14 @@ 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`)
@@ -71,6 +73,7 @@ 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.

View File

@@ -20,10 +20,26 @@ 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`).

View File

@@ -19,7 +19,7 @@ openclaw status --usage
Notes:
- `--deep` runs live probes (WhatsApp Web + Telegram + Discord + Google Chat + Slack + Signal).
- `--deep` runs live probes (WhatsApp Web + Telegram + Discord + 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).

View File

@@ -12,10 +12,18 @@ 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
@@ -32,6 +40,7 @@ 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`
@@ -44,6 +53,7 @@ Heartbeat controls:
Flags:
- `--json`: machine-readable output.
- `--url`, `--token`, `--timeout`, `--expect-final`: shared Gateway RPC flags.
## `system presence`
@@ -53,6 +63,7 @@ instances, and similar status lines).
Flags:
- `--json`: machine-readable output.
- `--url`, `--token`, `--timeout`, `--expect-final`: shared Gateway RPC flags.
## Notes

View File

@@ -10,11 +10,30 @@ 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
```
Run `openclaw backup create` first if you want a restorable snapshot before removing state or workspaces.
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`.

View File

@@ -24,6 +24,7 @@ openclaw update --tag beta
openclaw update --tag main
openclaw update --dry-run
openclaw update --no-restart
openclaw update --yes
openclaw update --json
openclaw --update
```
@@ -36,6 +37,7 @@ 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.
@@ -60,6 +62,10 @@ 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

View File

@@ -22,4 +22,70 @@ openclaw webhooks gmail setup --account you@example.com
openclaw webhooks gmail run
```
See [Gmail Pub/Sub documentation](/automation/cron-jobs#gmail-pubsub-integration) for details.
### `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.

View File

@@ -73,6 +73,7 @@ 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.

View File

@@ -11,10 +11,10 @@ title: "Features"
<Columns>
<Card title="Channels" icon="message-square">
WhatsApp, Telegram, Discord, and iMessage with a single Gateway.
Discord, iMessage, Signal, Slack, Telegram, WhatsApp, WebChat, and more with a single Gateway.
</Card>
<Card title="Plugins" icon="plug">
Add Mattermost and more with extensions.
Add Matrix, Microsoft Teams, Nextcloud Talk, Nostr, Twitch, Zalo, and more with plugins.
</Card>
<Card title="Routing" icon="route">
Multi-agent routing with isolated sessions.
@@ -34,8 +34,9 @@ title: "Features"
**Channels:**
- WhatsApp, Telegram, Discord, iMessage (built-in)
- Mattermost, Matrix, Microsoft Teams, Nostr, and more (plugins)
- 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
- Group chat support with mention-based activation
- DM safety with allowlists and pairing

View File

@@ -0,0 +1,151 @@
---
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)

View File

@@ -83,6 +83,23 @@ 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
@@ -98,5 +115,7 @@ 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

View File

@@ -18,6 +18,8 @@ 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`.
@@ -26,14 +28,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
`resolveDynamicModel`, `prepareDynamicModel`, `normalizeResolvedModel`,
`capabilities`, `prepareExtraParams`, `wrapStreamFn`, `formatApiKey`,
`refreshOAuth`, `buildAuthDoctorHint`,
`isCacheTtlEligible`, `buildMissingAuthMessage`,
`suppressBuiltInModel`, `augmentModelCatalog`, `isBinaryThinking`,
`supportsXHighThinking`, `resolveDefaultThinkingLevel`,
`isModernModelRef`, `prepareRuntimeAuth`, `resolveUsageAuth`, and
`fetchUsageSnapshot`.
`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`.
- 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)
@@ -51,6 +53,7 @@ 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
@@ -65,6 +68,10 @@ 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
@@ -76,6 +83,8 @@ 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
@@ -87,7 +96,10 @@ Typical split:
Current bundled examples:
- `anthropic`: Claude 4.6 forward-compat fallback, auth repair hints, usage
endpoint fetching, and cache-TTL/provider-family metadata
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
- `openrouter`: pass-through model ids, request wrappers, provider capability
hints, and cache-TTL policy
- `github-copilot`: onboarding/device login, forward-compat model fallback,
@@ -107,7 +119,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-coding`,
- `byteplus`, `cloudflare-ai-gateway`, `huggingface`, `kimi`,
`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
@@ -162,13 +174,13 @@ OpenClaw ships with the piai catalog. These providers require **no**
### Anthropic
- Provider: `anthropic`
- Auth: `ANTHROPIC_API_KEY` or `claude setup-token`
- Auth: `ANTHROPIC_API_KEY`
- 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 token` (paste setup-token) or `openclaw models auth paste-token --provider anthropic`
- CLI: `openclaw onboard --auth-choice apiKey` or `openclaw onboard --auth-choice anthropic-cli`
- 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`)
- 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.
- 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.
```json5
{
@@ -187,6 +199,7 @@ OpenClaw ships with the piai 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
@@ -195,6 +208,24 @@ OpenClaw ships with the piai 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`)
@@ -249,7 +280,7 @@ OpenClaw ships with the piai catalog. These providers require **no**
- Provider: `kilocode`
- Auth: `KILOCODE_API_KEY`
- Example model: `kilocode/anthropic/claude-opus-4.6`
- CLI: `openclaw onboard --kilocode-api-key <key>`
- CLI: `openclaw onboard --auth-choice kilocode-api-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.
@@ -258,12 +289,12 @@ See [/providers/kilocode](/providers/kilocode) for setup details.
### Other bundled provider plugins
- OpenRouter: `openrouter` (`OPENROUTER_API_KEY`)
- Example model: `openrouter/anthropic/claude-sonnet-4-6`
- Example model: `openrouter/auto`
- 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-coding` (`KIMI_API_KEY` or `KIMICODE_API_KEY`)
- Kimi Coding: `kimi` (`KIMI_API_KEY` or `KIMICODE_API_KEY`)
- Qianfan: `qianfan` (`QIANFAN_API_KEY`)
- Model Studio: `modelstudio` (`MODELSTUDIO_API_KEY`)
- NVIDIA: `nvidia` (`NVIDIA_API_KEY`)
@@ -340,19 +371,21 @@ Kimi K2 model IDs:
Kimi Coding uses Moonshot AI's Anthropic-compatible endpoint:
- Provider: `kimi-coding`
- Provider: `kimi`
- Auth: `KIMI_API_KEY`
- Example model: `kimi-coding/k2p5`
- Example model: `kimi/kimi-code`
```json5
{
env: { KIMI_API_KEY: "sk-..." },
agents: {
defaults: { model: { primary: "kimi-coding/k2p5" } },
defaults: { model: { primary: "kimi/kimi-code" } },
},
}
```
Legacy `kimi/k2p5` remains accepted as a compatibility model id.
### Volcano Engine (Doubao)
Volcano Engine (火山引擎) provides access to Doubao and other models in China.

View File

@@ -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 setup-token`).
subscription** (OAuth) and **Anthropic** (API key or Claude CLI).
## 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.2
/model openai/gpt-5.4
/model status
```
@@ -163,12 +163,14 @@ 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; subscription token flows are also supported.
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.
Example (Anthropic setup-token):
Example (Claude CLI):
```bash
claude setup-token
claude auth login
openclaw models status
```

View File

@@ -3,16 +3,23 @@ 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 setup-token or OAuth auth flows
- You want Claude CLI 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, 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:
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:
For Anthropic in production, API key auth is the safer recommended path over subscription setup-token auth.
For Anthropic in production, API key auth is the safer recommended path.
- how the OAuth **token exchange** works (PKCE)
- where tokens are **stored** (and why)
@@ -37,6 +44,9 @@ 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)
@@ -54,36 +64,39 @@ 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 setup-token (subscription auth)
## Anthropic legacy token compatibility
<Warning>
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.
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).
</Warning>
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
```
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.
## 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:
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:
```bash
openclaw models auth login --provider anthropic --method cli --set-default
@@ -96,32 +109,34 @@ 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/...`.
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
```
## OAuth exchange (how login works)
OpenClaws interactive login flows are implemented in `@mariozechner/pi-ai` and wired into the wizards/commands.
### Anthropic setup-token / Claude CLI
### Anthropic 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
Wizard paths:
Interactive assistant path:
- `openclaw onboard` → auth choice `anthropic-cli`
- `openclaw onboard` → auth choice `setup-token` (Anthropic)
- `openclaw onboard` / `openclaw configure` → auth choice `anthropic-cli`
### OpenAI Codex (ChatGPT OAuth)
@@ -146,6 +161,8 @@ 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.

View File

@@ -39,10 +39,10 @@ cache-write size, directly lowering cost.
OpenClaw auto-enables pruning for Anthropic profiles:
| Profile type | Pruning enabled | Heartbeat |
| -------------------- | --------------- | --------- |
| OAuth or setup-token | Yes | 1 hour |
| API key | Yes | 30 min |
| Profile type | Pruning enabled | Heartbeat |
| ------------------------------- | --------------- | --------- |
| Claude CLI or legacy token auth | Yes | 1 hour |
| API key | Yes | 30 min |
If you set explicit values, OpenClaw does not override them.

110
docs/concepts/soul.md Normal file
View File

@@ -0,0 +1,110 @@
---
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)

View File

@@ -84,6 +84,9 @@ 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

View File

@@ -1,7 +1,7 @@
{
"$schema": "https://mintlify.com/docs.json",
"name": "OpenClaw",
"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.",
"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.",
"theme": "mint",
"icons": {
"library": "lucide"
@@ -1051,6 +1051,7 @@
"concepts/context",
"concepts/context-engine",
"concepts/agent-workspace",
"concepts/soul",
"concepts/oauth",
"start/bootstrapping"
]
@@ -1068,7 +1069,8 @@
"concepts/memory-builtin",
"concepts/memory-qmd",
"concepts/memory-honcho",
"concepts/memory-search"
"concepts/memory-search",
"concepts/memory-dreaming"
]
},
"concepts/compaction"
@@ -1225,6 +1227,7 @@
"pages": [
"providers/anthropic",
"providers/bedrock",
"providers/chutes",
"providers/claude-max-api-proxy",
"providers/cloudflare-ai-gateway",
"providers/deepgram",

View File

@@ -1,5 +1,5 @@
---
summary: "Model authentication: OAuth, API keys, and setup-token"
summary: "Model authentication: OAuth, API keys, and Claude CLI reuse"
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, setup tokens). 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, Claude CLI reuse). 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 youre running a long-lived gateway, start with an API key for your chosen
provider.
For Anthropic specifically, API key auth is the safe path and is recommended
over subscription setup-token auth.
For Anthropic specifically, API key auth is the safe path. Claude CLI reuse is
the other supported subscription-style setup path.
1. Create an API key in your provider console.
2. Put it on the **gateway host** (the machine running `openclaw gateway`).
@@ -59,45 +59,18 @@ API keys for daemon use: `openclaw onboard`.
See [Help](/help) for details on env inheritance (`env.shellEnv`,
`~/.openclaw/.env`, systemd/launchd).
## Anthropic: setup-token (subscription auth)
## Anthropic: legacy token compatibility
If youre using a Claude subscription, the setup-token flow is supported. Run
it on the **gateway host**:
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.
```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>
For new setup, use an Anthropic API key or migrate to Claude CLI on the gateway
host.
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
```
@@ -116,13 +89,17 @@ 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 instead of pasting a
setup-token:
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`
```bash
openclaw models auth login --provider anthropic --method cli --set-default
@@ -132,12 +109,21 @@ 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
@@ -186,8 +172,8 @@ Use `--agent <id>` to target a specific agent; omit it to use the configured def
### "No credentials found"
If the Anthropic token profile is missing, run `claude setup-token` on the
**gateway host**, then re-check:
If the Anthropic profile is missing, migrate that setup to Claude CLI or an API
key on the **gateway host**, then re-check:
```bash
openclaw models status
@@ -195,10 +181,12 @@ openclaw models status
### Token expiring/expired
Run `openclaw models status` to confirm which profile is expiring. If the profile
is missing, rerun `claude setup-token` and paste the token again.
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.
## Requirements
## Claude CLI requirements
Only needed for the Anthropic Claude CLI reuse path:
- Anthropic subscription account (for `claude setup-token`)
- Claude Code CLI installed (`claude` command available)

View File

@@ -20,6 +20,10 @@ 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
@@ -158,6 +162,12 @@ 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`:

View File

@@ -67,19 +67,15 @@ 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:me@example.com": {
provider: "anthropic",
mode: "oauth",
email: "me@example.com",
},
"anthropic:default": { provider: "anthropic", mode: "api_key" },
"anthropic:work": { provider: "anthropic", mode: "api_key" },
"openai:default": { provider: "openai", mode: "api_key" },
"openai-codex:default": { provider: "openai-codex", mode: "oauth" },
"openai-codex:personal": { provider: "openai-codex", mode: "oauth" },
},
order: {
anthropic: ["anthropic:me@example.com", "anthropic:work"],
anthropic: ["anthropic:default", "anthropic:work"],
openai: ["openai:default"],
"openai-codex": ["openai-codex:default"],
"openai-codex": ["openai-codex:personal"],
},
},
@@ -240,7 +236,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.2"],
fallbacks: ["anthropic/claude-opus-4-6", "openai/gpt-5.4"],
},
imageModel: {
primary: "openrouter/anthropic/claude-sonnet-4-6",
@@ -248,7 +244,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.2": { alias: "gpt" },
"openai/gpt-5.4": { alias: "gpt" },
},
skills: ["github", "weather"], // inherited by agents that omit list[].skills
thinkingDefault: "low",
@@ -536,60 +532,19 @@ 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.
### OAuth with API key failover
### Anthropic API key + MiniMax fallback
```json5
{
auth: {
profiles: {
"anthropic:subscription": {
provider: "anthropic",
mode: "oauth",
email: "me@example.com",
},
"anthropic:api": {
provider: "anthropic",
mode: "api_key",
},
},
order: {
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"],
anthropic: ["anthropic:api"],
},
},
models: {

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