Compare commits

..

1748 Commits

Author SHA1 Message Date
huntharo
a17a418c3c Plugins: fix rebase integration on main 2026-04-03 14:56:20 -04:00
huntharo
e8a3938ffc Docs: mark lane-oriented channel plan complete 2026-04-03 14:50:04 -04:00
huntharo
73bdf20abe Plugins: finish host-owned channel interaction flow 2026-04-03 14:50:04 -04:00
huntharo
3366681e8c Plugins: share rich reply projection across channels 2026-04-03 14:50:04 -04:00
huntharo
e8d6e0e4c7 Docs: add lane-oriented channel interface plan 2026-04-03 14:49:52 -04:00
huntharo
2a8fabb91b Docs: document lane-oriented plugin channels 2026-04-03 14:49:52 -04:00
huntharo
2678771b2b Plugins: add lane-oriented channel runtime 2026-04-03 14:49:52 -04:00
Tak Hoffman
d2ca915a7f fix: honor telegram default action account 2026-04-03 13:48:45 -05:00
Peter Steinberger
88ab29f492 fix(ci): relax discord runtime mock module constraint 2026-04-03 19:46:59 +01:00
Tak Hoffman
534b0c663e fix: honor zalouser default runtime account 2026-04-03 13:46:36 -05:00
Tak Hoffman
f66c9b829e fix: honor slack default runtime account 2026-04-03 13:45:28 -05:00
Tak Hoffman
4f5f1fa724 fix: honor imessage default runtime account 2026-04-03 13:44:15 -05:00
Peter Steinberger
b8af2c65e5 fix(ci): bind full discord conversation runtime mock type 2026-04-03 19:44:05 +01:00
Tak Hoffman
4ca1ae8046 fix: honor signal default runtime account 2026-04-03 13:43:09 -05:00
Tak Hoffman
c7875f193b fix: honor discord default runtime account 2026-04-03 13:41:55 -05:00
Peter Steinberger
e3f410efb5 fix(ci): widen discord binding runtime mock type 2026-04-03 19:40:47 +01:00
Peter Steinberger
3fb6e3e91f test: trim more extension importOriginal usage 2026-04-03 19:40:20 +01:00
Tak Hoffman
17c0026c04 fix: honor bluebubbles default runtime account 2026-04-03 13:39:22 -05:00
Tak Hoffman
9289f967df fix: honor mattermost default runtime account 2026-04-03 13:38:03 -05:00
Peter Steinberger
e76a16dfa5 fix(ci): preserve omitted feishu finalize account context 2026-04-03 19:38:00 +01:00
Vincent Koc
e697fa5e75 feat(providers): add google transport runtime 2026-04-04 03:35:58 +09:00
Peter Steinberger
2156bf0210 test: fix setup wizard and execFile test drift 2026-04-03 19:35:38 +01:00
Peter Steinberger
0ad2da060e test: route openclaw root through boundary config 2026-04-03 19:35:27 +01:00
Peter Steinberger
cc62fd38f6 test: trim more extension mock imports 2026-04-03 19:34:55 +01:00
Tak Hoffman
a8302e8eab fix: honor mattermost default reply account 2026-04-03 13:34:53 -05:00
Peter Steinberger
323ad51eb8 fix(ci): align execFile mock typings 2026-04-03 19:31:41 +01:00
Peter Steinberger
8be2dea382 test: trim more extension partial mocks 2026-04-03 19:31:32 +01:00
Peter Steinberger
b27fd7cc49 fix(setup): narrow default account id typing 2026-04-03 19:30:35 +01:00
Peter Steinberger
0c95e3f073 refactor(plugins): move command ui policy into extensions 2026-04-03 19:30:35 +01:00
Peter Steinberger
e5d2181403 fix(ci): repair discord interactive test seams 2026-04-03 19:29:14 +01:00
Peter Steinberger
45a6f769bb test: trim core partial mocks 2026-04-03 19:28:19 +01:00
Peter Steinberger
6eca4e0136 test: trim extension partial mocks 2026-04-03 19:28:19 +01:00
Tak Hoffman
24a4ed1013 fix: honor matrix default runtime account 2026-04-03 13:26:34 -05:00
Peter Steinberger
eea069bdc3 fix(ci): repair bundled and extension test drift 2026-04-03 19:25:23 +01:00
Tak Hoffman
e063f67ac0 fix: honor nextcloud default runtime account 2026-04-03 13:24:58 -05:00
Vincent Koc
26b7260bf4 refactor(signal): narrow channel runtime imports 2026-04-04 03:24:21 +09:00
Vincent Koc
e9cbdc7439 fix(plugins): narrow top-level allowFrom account resolver 2026-04-04 03:24:21 +09:00
Tak Hoffman
d5c6e7af0f fix: honor whatsapp default heartbeat account 2026-04-03 13:23:29 -05:00
Gustavo Madeira Santana
1f660bf930 Docs: document agent skill allowlists 2026-04-03 14:23:05 -04:00
Tak Hoffman
5c3dc40794 fix: honor googlechat default runtime account 2026-04-03 13:22:11 -05:00
Peter Steinberger
28b8e019f7 test(setup): fix latest type regressions 2026-04-03 19:21:46 +01:00
Peter Steinberger
df18f4c517 refactor(matrix): move legacy migrations behind doctor 2026-04-03 19:21:24 +01:00
Tak Hoffman
5eb3341db1 fix: honor zalo default runtime account 2026-04-03 13:19:50 -05:00
Gustavo Madeira Santana
5e365a8ec4 agents: preserve remote skill sync eligibility 2026-04-03 14:19:43 -04:00
Tak Hoffman
045010a2a5 fix: honor zalouser default runtime account 2026-04-03 13:18:11 -05:00
Peter Steinberger
72b8025107 fix: align feishu and matrix type guards 2026-04-03 19:17:14 +01:00
Peter Steinberger
4c5c361db7 test: stub gateway speech providers 2026-04-03 19:16:56 +01:00
Vincent Koc
956e746da1 fix(plugins): narrow nested allowFrom account resolver 2026-04-04 03:16:14 +09:00
Vincent Koc
7d691a3ce3 refactor(whatsapp): narrow channel runtime imports 2026-04-04 03:16:14 +09:00
Peter Steinberger
5c6dca78d9 fix(discord): avoid bundled sibling requires 2026-04-03 19:15:21 +01:00
Peter Steinberger
53f8c2047a fix(ci): restore channel approval and lifecycle harnesses 2026-04-03 19:14:42 +01:00
Tak Hoffman
d20e3d5691 fix: honor feishu setup adapter default 2026-04-03 13:14:10 -05:00
Tak Hoffman
a89cb679a2 fix: honor nostr setup default account 2026-04-03 13:12:49 -05:00
Peter Steinberger
13bc70397a test: trim test partial mocks 2026-04-03 19:10:56 +01:00
Tak Hoffman
5c4551458f fix: honor qqbot setup default account 2026-04-03 13:10:49 -05:00
Peter Steinberger
181bd6327f test(plugins): fix rebase fallout 2026-04-03 19:10:00 +01:00
Peter Steinberger
42ffe86fc7 test(cron): stabilize regression harness 2026-04-03 19:09:21 +01:00
Peter Steinberger
03a43fe231 refactor(plugins): genericize core channel seams 2026-04-03 19:09:21 +01:00
Peter Steinberger
856592cf00 fix(outbound): restore generic delivery and security seams 2026-04-03 19:09:20 +01:00
Peter Steinberger
ab96520bba refactor(plugins): move channel behavior into plugins 2026-04-03 19:09:20 +01:00
Josh Lehman
c52df32878 refactor: move bundled replay policy ownership into plugins (#60452)
* refactor: move bundled replay policy ownership into plugins

* test: preserve replay fallback until providers adopt hooks

* test: cover response replay branches for ollama and zai

---------

Co-authored-by: Shakker <shakkerdroid@gmail.com>
2026-04-03 19:08:10 +01:00
Tak Hoffman
7fb58afb41 fix: honor googlechat default allowFrom account 2026-04-03 13:07:07 -05:00
Tak Hoffman
7be2d361de fix: honor feishu finalize default account 2026-04-03 13:04:12 -05:00
Vincent Koc
abc3f27ba9 refactor(zalo): narrow action runtime imports 2026-04-04 03:03:15 +09:00
Vincent Koc
0ba93afda9 fix(feishu): guard scoped setup config access 2026-04-04 03:03:15 +09:00
Tak Hoffman
b7b53b29e8 fix: honor discord setup default account 2026-04-03 13:01:28 -05:00
Peter Steinberger
d9e59f7329 fix(ci): align loader and channel test expectations 2026-04-03 19:00:23 +01:00
Vincent Koc
54479220f5 refactor(xai): share model hint helper 2026-04-04 02:58:58 +09:00
Vincent Koc
ea4265a820 feat(providers): add anthropic transport runtime 2026-04-04 02:58:58 +09:00
Tak Hoffman
8fc684cb55 fix: honor feishu default account setup policy 2026-04-03 12:58:50 -05:00
Peter Steinberger
5d20c73e05 fix: route Copilot Claude through Anthropic 2026-04-04 02:57:59 +09:00
Gustavo Madeira Santana
e588a363f9 fix: respect approval request filters in ambiguity checks 2026-04-03 13:57:18 -04:00
Tak Hoffman
4bbd67c21e fix: honor imessage default account setup policy 2026-04-03 12:56:42 -05:00
Peter Steinberger
de49b26bb1 test: trim acp spawn parent stream resets 2026-04-03 18:56:17 +01:00
Peter Steinberger
91a3554cd7 test: trim session status module resets 2026-04-03 18:55:23 +01:00
Peter Steinberger
3fd27211b1 fix(ci): stabilize channel approval and monitor tests 2026-04-03 18:54:48 +01:00
Peter Steinberger
7eed2e2911 fix: align cron and bluebubbles test drift 2026-04-03 18:53:58 +01:00
Peter Steinberger
7439479047 test: tighten runtime setup and boundary guards 2026-04-03 18:53:34 +01:00
Peter Steinberger
3edfc494df test: expand builtin mock helper usage 2026-04-03 18:53:34 +01:00
Peter Steinberger
613393621c test: reduce discord monitor partial mocks 2026-04-03 18:53:03 +01:00
Tak Hoffman
5888e44745 fix: honor signal default account setup policy 2026-04-03 12:52:14 -05:00
Peter Steinberger
6739c28718 refactor: clarify auth failover policy 2026-04-04 02:49:18 +09:00
Tak Hoffman
1d1a8264ec fix: honor zalo default account setup policy 2026-04-03 12:49:09 -05:00
Vincent Koc
50e1eb56d7 fix(security): harden discord proxy and bundled channel activation (#60455)
* fix(security): tighten discord proxy and mobile tls guards

* fix(plugins): enforce allowlists for bundled channels

* fix(types): align callers with removed legacy config aliases

* fix(security): preserve bundled channel opt-in and ipv6 proxies
2026-04-04 02:48:52 +09:00
Peter Steinberger
3ddf745f97 fix(ci): restore account setup typings 2026-04-03 18:48:44 +01:00
Gustavo Madeira Santana
dc306013e1 Approvals: scope foreign-channel account routing (#60417)
Merged via squash.

Prepared head SHA: 3ad6cae91f
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
2026-04-03 13:48:00 -04:00
Tak Hoffman
0769590f03 fix: honor zalouser default account setup policy 2026-04-03 12:47:22 -05:00
Tak Hoffman
f115aba582 fix: honor bluebubbles default account setup policy 2026-04-03 12:45:42 -05:00
Tak Hoffman
638e831bca fix: honor telegram default account setup policy 2026-04-03 12:43:51 -05:00
Peter Steinberger
379c329f81 test: trim dispatch and command partial mocks 2026-04-03 18:42:52 +01:00
Tak Hoffman
4edf6a2c7d fix: honor line default account setup policy 2026-04-03 12:42:18 -05:00
Tak Hoffman
4afa720a0c fix: honor nextcloud default account setup policy 2026-04-03 12:42:18 -05:00
Tak Hoffman
d0a43cf8c0 fix: honor googlechat default account setup policy 2026-04-03 12:42:18 -05:00
Tak Hoffman
acfa09679c fix: honor qqbot default account config 2026-04-03 12:42:18 -05:00
Tak Hoffman
b929a4c27d fix: honor imessage setup account status 2026-04-03 12:42:18 -05:00
Tak Hoffman
9f049cb1d8 fix: honor nostr default account routing 2026-04-03 12:42:18 -05:00
Tak Hoffman
f77054eaee fix: honor feishu setup account id 2026-04-03 12:42:18 -05:00
Tak Hoffman
35256d6f1d fix: honor nostr setup account label 2026-04-03 12:42:18 -05:00
Tak Hoffman
17060ca124 fix: honor feishu setup account writes 2026-04-03 12:42:17 -05:00
Tak Hoffman
53612fa128 fix: label whatsapp setup account status 2026-04-03 12:42:17 -05:00
Tak Hoffman
fc61e8d280 fix: honor feishu setup account status 2026-04-03 12:42:17 -05:00
Tak Hoffman
e37c0da23a fix: honor synology setup account status 2026-04-03 12:42:17 -05:00
Tak Hoffman
42ebc8c170 fix: honor irc setup account status 2026-04-03 12:42:17 -05:00
Tak Hoffman
e6a9408c3b fix: honor qqbot setup account status 2026-04-03 12:42:17 -05:00
Gustavo Madeira Santana
ddd250d130 feat(skills): add inherited agent skill allowlists (#59992)
Merged via squash.

Prepared head SHA: 6f60779a57
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 13:41:28 -04:00
Peter Steinberger
04f59a7227 fix: remove duplicate bluebubbles actions field 2026-04-03 18:40:28 +01:00
Peter Steinberger
636a23b73e test: extract node builtin mock helpers 2026-04-03 18:40:28 +01:00
Vincent Koc
47b8be7116 refactor(discord): lazy-load message actions 2026-04-04 02:38:43 +09:00
Vincent Koc
7e3b48c254 test(ci): align memory fallback reindex assertion 2026-04-04 02:37:38 +09:00
Peter Steinberger
1bee69f79b refactor: route direct extension test targets 2026-04-04 02:36:48 +09:00
Peter Steinberger
d0d5b34b44 fix(ci): repair extension test regressions 2026-04-03 18:36:35 +01:00
Peter Steinberger
2c5f244554 chore: update changelog for #60404 (thanks @extrasmall0) 2026-04-04 02:35:27 +09:00
Peter Steinberger
865fa2ba72 fix: narrow auth permanent lockouts 2026-04-04 02:35:27 +09:00
Extra Small
42e1d489fd fix(auth): use shorter backoff for auth_permanent failures
auth_permanent errors (e.g. API_KEY_INVALID) can be caused by transient
provider outages rather than genuinely revoked credentials. Previously
these used the same 5h-24h billing backoff, which left providers disabled
long after the upstream issue resolved.

Introduce separate authPermanentBackoffMinutes (default: 10) and
authPermanentMaxMinutes (default: 60) config options so auth_permanent
failures recover in minutes rather than hours.

Fixes #56838
2026-04-04 02:35:27 +09:00
Vincent Koc
022a24ec48 refactor(signal): split uuid helper from monitor 2026-04-04 02:34:14 +09:00
Vincent Koc
c4eaa30ee7 fix(bluebubbles): remove duplicate action config field 2026-04-04 02:34:14 +09:00
Vincent Koc
e88f4fe5f4 fix(ci): narrow matrix action test discovery 2026-04-04 02:33:59 +09:00
Peter Steinberger
a7dd6036a0 style: format doctor and gateway harness mocks 2026-04-03 18:33:47 +01:00
Peter Steinberger
68edc53090 test: trim doctor and gateway partial mocks 2026-04-03 18:33:47 +01:00
Peter Steinberger
f36ed7105f test: reduce extension runtime partial mocks 2026-04-03 18:33:47 +01:00
Peter Steinberger
14c863dc4a test: reduce telegram media harness imports 2026-04-03 18:33:47 +01:00
Peter Steinberger
fb9be1fcb6 test: trim models and cron partial mocks 2026-04-03 18:33:47 +01:00
Peter Steinberger
1c16c6a94a test: split inbound contract helpers 2026-04-03 18:33:46 +01:00
Peter Steinberger
d6e89f96d6 refactor: share gateway config auth helpers 2026-04-04 02:29:29 +09:00
Vincent Koc
646e271c72 test(contracts): split provider wizard lanes 2026-04-04 02:29:00 +09:00
Vincent Koc
71de4adcce test(contracts): split bundled web search lanes 2026-04-04 02:28:15 +09:00
Vincent Koc
f93f76dcc4 fix(ci): dedupe bluebubbles actions config type 2026-04-04 02:26:42 +09:00
Vincent Koc
22fd61e483 test(contracts): split plugin registration lanes 2026-04-04 02:26:39 +09:00
Peter Steinberger
ebdade0efc ci: shard extension fast checks 2026-04-03 18:26:26 +01:00
Vincent Koc
230c61885d refactor(slack): lazy-load webhook handler 2026-04-04 02:26:19 +09:00
Vincent Koc
7ad72281f7 refactor(providers): share pi openai reasoning compat gate 2026-04-04 02:25:10 +09:00
Vincent Koc
bee60a479b test(contracts): fix tts provider fixtures 2026-04-04 02:24:28 +09:00
Peter Steinberger
5fbef0f914 fix(ci): resolve tracked merge markers 2026-04-03 18:22:03 +01:00
Peter Steinberger
be9db66533 fix: split discord voice timeouts and restore gate on main (#60345) (thanks @geekhuashan) 2026-04-04 02:21:43 +09:00
geekhuashan
0c575f37fd fix(discord): add DiscordVoiceReadyListener fire-and-forget error-path test
Add test covering the DiscordVoiceReadyListener.handle() path where
autoJoin() rejects, confirming the error is caught and does not propagate.
2026-04-04 02:21:43 +09:00
geekhuashan
db593440c4 fix(discord voice): fire-and-forget autoJoin and increase playback timeout to 60s 2026-04-04 02:21:43 +09:00
Vincent Koc
136f177cb3 test(contracts): split provider contract lanes 2026-04-04 02:20:35 +09:00
Peter Steinberger
7fd9e40960 fix: tighten gateway shared-auth disconnects (#60387) (thanks @mappel-nv) 2026-04-04 02:20:22 +09:00
Michael Appel
c742963fd9 Gateway: avoid secret-ref auth disconnect churn 2026-04-04 02:20:22 +09:00
Michael Appel
97558f2325 Gateway: expand shared-auth rotation coverage 2026-04-04 02:20:22 +09:00
Michael Appel
54b269b2cb Gateway: disconnect shared-auth sessions on auth change 2026-04-04 02:20:22 +09:00
Peter Steinberger
e0580e6863 test: harden shared-worker runtime setup 2026-04-03 18:18:56 +01:00
Peter Steinberger
2981cce130 fix: align config and plugin test types 2026-04-03 18:18:56 +01:00
Vincent Koc
ff68fd3060 refactor(providers): share completions format defaults 2026-04-04 02:18:12 +09:00
Vincent Koc
39a16c600f test(contracts): localize provider contract suites 2026-04-04 02:17:15 +09:00
Vincent Koc
f881eb066c test(contracts): remove dead session binding helper 2026-04-04 02:16:04 +09:00
Vincent Koc
514b37e185 fix(providers): keep native modelstudio streaming usage compat 2026-04-04 02:15:46 +09:00
Vincent Koc
feed4007fe test(contracts): localize surface registry helpers 2026-04-04 02:15:01 +09:00
Vincent Koc
dd42154e45 fix(providers): stop forcing reasoning effort on proxy completions 2026-04-04 02:14:10 +09:00
Vincent Koc
dd35b97398 test(contracts): narrow session binding registry seeding 2026-04-04 02:13:31 +09:00
Vincent Koc
7836c9a6c2 fix(providers): stop forcing store on proxy completions 2026-04-04 02:12:59 +09:00
Peter Steinberger
ae359c0c8b fix: remove changelog conflict marker 2026-04-04 02:12:10 +09:00
Peter Steinberger
5e69d7e75b fix: land discord everyone mention gating 2026-04-04 02:12:10 +09:00
geekhuashan
6ba490ec7b fix(discord): guard @everyone shortcut against bot-authored messages
Preserve the !author.bot || sender.isPluralKit guard when short-circuiting
wasMentioned on mentionedEveryone, so bot relay messages don't spuriously
trigger mention-gate logic. Add test coverage for the wasMentioned path.
2026-04-04 02:12:10 +09:00
geekhuashan
0b69119f1b fix(discord): detect @everyone mentions in message preflight 2026-04-04 02:12:10 +09:00
Vincent Koc
f575bc2bfe test(ci): harden proxy-sensitive and timeout unit tests 2026-04-04 02:12:00 +09:00
Vincent Koc
b871707628 test(contracts): localize remaining suite helpers 2026-04-04 02:11:44 +09:00
Vincent Koc
50d85dcd59 refactor(providers): share openai compat defaults 2026-04-04 02:10:24 +09:00
Vincent Koc
c5a45eb274 test(contracts): localize registry-backed contract helpers 2026-04-04 02:09:25 +09:00
Peter Steinberger
3c07b126ed fix(ci): restore discord action loader 2026-04-03 18:07:31 +01:00
Vincent Koc
f1911274aa test(contracts): localize surface and session binding helpers 2026-04-04 02:06:38 +09:00
chziyue
d5c42a07ef fix(ui): Stop button shows Send during tool execution (#54528)
Merged via squash.

Prepared head SHA: f2d65a5c3d
Co-authored-by: chziyue <62380760+chziyue@users.noreply.github.com>
Co-authored-by: velvet-shark <126378+velvet-shark@users.noreply.github.com>
Reviewed-by: @velvet-shark
2026-04-03 18:59:47 +02:00
Peter Steinberger
54cd0859d3 test(ci): align discord ack removal expectation 2026-04-03 17:58:33 +01:00
Peter Steinberger
911e3974f7 refactor: clarify Discord classic fallback 2026-04-04 01:58:06 +09:00
Tak Hoffman
9e6de5ece4 fix: honor tlon setup account status 2026-04-03 11:57:50 -05:00
Peter Steinberger
f076e97a3c fix(ci): restore discord ack cleanup 2026-04-03 17:54:44 +01:00
Tak Hoffman
b2a9e0d7a7 fix: honor setup binary path account overrides 2026-04-03 11:54:21 -05:00
Shakker
2c0967150d test: preserve whatsapp account helpers in setup mock 2026-04-03 17:51:01 +01:00
Vincent Koc
eec6f59a77 fix(providers): disable z.ai strict tool shaping 2026-04-04 01:50:55 +09:00
Vincent Koc
745f1c9812 fix(types): align callers with removed legacy config aliases 2026-04-04 01:50:44 +09:00
Vincent Koc
ddaef48421 fix(ci): restore discord reaction account context 2026-04-04 01:50:34 +09:00
Tak Hoffman
51f6bc4940 fix: honor selected account in setup status 2026-04-03 11:50:09 -05:00
Peter Steinberger
5edefc4d5b fix: remove changelog conflict marker (#60066) (thanks @huntharo) 2026-04-04 01:49:35 +09:00
Peter Steinberger
ec01cd0ceb fix: tidy plugin update override docs (#60066) (thanks @huntharo) 2026-04-04 01:49:35 +09:00
huntharo
c4f40c3f7d Plugins: allow unsafe-force override on update 2026-04-04 01:49:35 +09:00
Vincent Koc
824ff335c6 fix(providers): align custom transport compat defaults 2026-04-04 01:48:00 +09:00
Peter Steinberger
958cebcc87 fix: preserve Discord component-only media behavior (#60361) (thanks @geekhuashan) 2026-04-04 01:46:32 +09:00
geekhuashan
0e07c8973e fix(discord): forward mediaReadFile and mediaAccess in component classic message path
Forward mediaReadFile and mediaAccess through the sendMessageDiscord shortcut
in sendDiscordComponentMessage, so local-file media works correctly when
falling back to classic Discord messages. Add test coverage.
2026-04-04 01:46:32 +09:00
geekhuashan
efbb9a1296 fix(discord): downgrade text-only component+media to classic message and auto-append file block 2026-04-04 01:46:32 +09:00
Vincent Koc
36ed66cce2 fix(providers): honor moonshot transport compat (#60411) 2026-04-04 01:45:45 +09:00
Vincent Koc
54e8790ad7 fix(providers): honor moonshot transport compat 2026-04-04 01:45:19 +09:00
Shakker
33d54e93b8 test: keep legacy tts contract fixtures typed 2026-04-03 17:43:55 +01:00
Shakker
21002a60be test: align legacy streaming config coverage 2026-04-03 17:43:55 +01:00
Shakker
21a87eaf2b fix: use safeExtend for bluebubbles config 2026-04-03 17:43:17 +01:00
Shakker
06b2e8b79a test: satisfy xai transport model typing 2026-04-03 17:43:17 +01:00
Shakker
91572df932 fix: restore bluebubbles action config typing 2026-04-03 17:43:17 +01:00
Peter Steinberger
a5e725a3b8 test: align vitest defaults with migrated config 2026-04-03 17:42:48 +01:00
Tak Hoffman
b63a175d3d fix: honor imessage probe account config 2026-04-03 11:40:50 -05:00
Marcus Castro
b473e056a0 fix(whatsapp): restore source login tool import 2026-04-03 13:40:20 -03:00
Shakker
408dac8d21 docs(changelog): note secrets runtime isolation cleanup 2026-04-03 17:38:05 +01:00
Shakker
bc46c1dacc test: restore secrets suite isolation cleanup 2026-04-03 17:38:05 +01:00
Tak Hoffman
7f4dd21227 fix: honor zalo action discovery account config 2026-04-03 11:37:22 -05:00
Tak Hoffman
eb497a89cd fix: honor zalouser action discovery account config 2026-04-03 11:35:55 -05:00
Peter Steinberger
b118efea80 test: split reply flow coverage by owner surface 2026-04-03 17:35:01 +01:00
Peter Steinberger
d74d47443e test: trim extension setup startup 2026-04-03 17:33:45 +01:00
Peter Steinberger
2e041c8b66 test: split slack monitor coverage 2026-04-03 17:33:45 +01:00
Peter Steinberger
1512fab782 test: remove discord channel partial mocks 2026-04-03 17:33:45 +01:00
Peter Steinberger
e263b5d7b6 test: split telegram channel coverage 2026-04-03 17:33:45 +01:00
Peter Steinberger
6686ef0b3a test: split whatsapp channel coverage 2026-04-03 17:33:45 +01:00
Tak Hoffman
d21ae7173f fix: honor slack action discovery account config 2026-04-03 11:33:23 -05:00
Vincent Koc
2a19771c3c test(contracts): split plugins-core lanes 2026-04-04 01:30:48 +09:00
Peter Steinberger
a1cad12139 fix(ci): restore compat config types 2026-04-03 17:29:43 +01:00
Tak Hoffman
c22f2a0cab fix: honor feishu action discovery account config 2026-04-03 11:28:51 -05:00
Peter Steinberger
570ed4285e refactor: extract Discord ack reaction helpers 2026-04-04 01:28:04 +09:00
Tak Hoffman
226e2389f6 fix: honor mattermost action discovery account config 2026-04-03 11:27:07 -05:00
Vincent Koc
09f66b0073 fix(build): align bluebubbles action gating 2026-04-04 01:25:02 +09:00
Vincent Koc
c7a947dc0a fix(config): remove legacy config aliases from public schema 2026-04-04 01:24:14 +09:00
Tak Hoffman
d59d236a90 fix: honor matrix action discovery account config 2026-04-03 11:23:13 -05:00
Vincent Koc
6ac5806a39 fix(providers): honor mistral transport compat (#60405) 2026-04-04 01:21:41 +09:00
Tak Hoffman
fb8048a188 fix: honor telegram action discovery account config 2026-04-03 11:20:49 -05:00
Peter Steinberger
2b900b576c refactor: modernize vitest projects config 2026-04-03 17:20:30 +01:00
Vincent Koc
9dba944c42 fix(build): restore current main type gates 2026-04-04 01:20:25 +09:00
Peter Steinberger
cf4d3c4daf refactor: share Discord ack reaction runtime context 2026-04-04 01:19:57 +09:00
Vincent Koc
4c969391fe test(contracts): split plugins-core extension lanes 2026-04-04 01:18:54 +09:00
Tak Hoffman
832810a5bb fix: honor discord action discovery account config 2026-04-03 11:18:26 -05:00
Vincent Koc
8ffd1c0973 test(contracts): split outbound payload contract lanes 2026-04-04 01:17:58 +09:00
Vincent Koc
1f509e0e04 test(contracts): split setup and status contract lanes 2026-04-04 01:16:38 +09:00
Vincent Koc
56f8de0cf9 test(contracts): split plugin and action contract lanes 2026-04-04 01:15:39 +09:00
Vincent Koc
6389498a7c test(contracts): split surface contract lanes 2026-04-04 01:13:53 +09:00
Tak Hoffman
632a10cddc fix: honor googlechat action discovery account config 2026-04-03 11:13:33 -05:00
Vincent Koc
a2836e6db6 fix(ci): narrow openai responses input literals 2026-04-04 01:13:09 +09:00
Tak Hoffman
da5c6ac2b6 fix: honor bluebubbles action discovery account config 2026-04-03 11:11:58 -05:00
Vincent Koc
0b4cdfc53e docs: fix changelog attribution gaps and remove duplicates 2026-04-04 01:11:03 +09:00
Vincent Koc
7ed789d67d fix(providers): centralize compat endpoint detection (#60399) 2026-04-04 01:10:50 +09:00
Tak Hoffman
fb4127082a fix: honor signal action discovery account config 2026-04-03 11:09:39 -05:00
Vincent Koc
9fbf501d5a fix(ci): align whatsapp and responses typing 2026-04-04 01:09:28 +09:00
Peter Steinberger
2766a3409c fix: resolve rebase type drift (#60249) (thanks @shakkernerd) 2026-04-04 01:07:28 +09:00
Peter Steinberger
664265fc66 test(heartbeat): pass reply spy into seeded override case 2026-04-04 01:07:28 +09:00
Peter Steinberger
2a1a7ea6f9 fix(browser): route test support through sdk testing 2026-04-04 01:07:28 +09:00
Peter Steinberger
7e2b26f77b fix(plugins): restore activation state wrapper 2026-04-04 01:07:28 +09:00
Peter Steinberger
0b74755894 test(utils): drop moved provider setup 2026-04-04 01:07:28 +09:00
Peter Steinberger
8541a5c3fa docs(changelog): note test-runtime perf work 2026-04-04 01:07:28 +09:00
Peter Steinberger
88dac32623 fix(reply-threading): align fallback test coverage 2026-04-04 01:07:28 +09:00
Peter Steinberger
0324055d09 test: align latest main runtime harnesses 2026-04-04 01:07:28 +09:00
Peter Steinberger
5bafa6edcf fix(auto-reply): align fallback model runtime state 2026-04-04 01:07:28 +09:00
Peter Steinberger
c563cdc901 fix(telegram): allow target approvals fallback 2026-04-04 01:07:28 +09:00
Peter Steinberger
6f5e71fdbc test: fix talk voice runtime type import 2026-04-04 01:07:28 +09:00
Shakker
2fa3a09137 test: harden command queue timer cleanup 2026-04-04 01:07:28 +09:00
Shakker
1877a2ea26 fix: stabilize rebased test surfaces 2026-04-04 01:07:28 +09:00
Shakker
d75fa152b9 test: trim secrets runtime snapshot setup 2026-04-04 01:07:28 +09:00
Shakker
38f76a1f8f perf: trim media secret setup and isolate heavy tests 2026-04-04 01:07:28 +09:00
Shakker
20250653ce fix: resolve rebased planner and loader regressions 2026-04-04 01:07:28 +09:00
Shakker
192f880a0b refactor: trim cron session cleanup imports 2026-04-04 01:07:28 +09:00
Shakker
998810a6a3 test: localize imessage alias channel coverage 2026-04-04 01:07:28 +09:00
Shakker
ce784b62f5 refactor: trim outbound direct-send runtimes 2026-04-04 01:07:28 +09:00
Shakker
5dd6189a2a refactor: split plugin interactive registration 2026-04-04 01:07:28 +09:00
Shakker
5b176c8cc5 test: split plugin install source coverage 2026-04-04 01:07:28 +09:00
Shakker
4919a8871b refactor: lazy load compaction store updates 2026-04-04 01:07:28 +09:00
Shakker
a3227e58d2 test: split secrets runtime integration coverage 2026-04-04 01:07:28 +09:00
Shakker
db76dbc546 test: split plugin loader coverage by concern 2026-04-04 01:07:28 +09:00
Shakker
b53dcb9380 test: fix bluebubbles monitor route typing 2026-04-04 01:07:28 +09:00
Shakker
da120962b9 test: fix image generation runtime test types 2026-04-04 01:07:28 +09:00
Shakker
383eea86dc test: move image generation runtime coverage to owner 2026-04-04 01:07:28 +09:00
Shakker
3c50139285 refactor: narrow heartbeat session imports 2026-04-04 01:07:28 +09:00
Shakker
590655472b perf: fast-path built-in reasoning provider checks 2026-04-04 01:07:28 +09:00
Shakker
9c4ea016d9 fix: use pnpm exec for scripted vitest runs 2026-04-04 01:07:28 +09:00
Shakker
e1143fb95f test: tighten bluebubbles monitor runtime typing 2026-04-04 01:07:28 +09:00
Shakker
24da2c39f3 refactor: isolate session transcript coverage 2026-04-04 01:07:28 +09:00
Shakker
27a8ef1284 refactor: narrow telegram message context runtime imports 2026-04-04 01:07:28 +09:00
Shakker
71b5a7c35b test: slim shared plugin runtime mock 2026-04-04 01:07:28 +09:00
Shakker
e9e7033ea1 test: trim embeddings provider import cost 2026-04-04 01:07:28 +09:00
Shakker
0af1d0ddb2 test: split security audit code safety coverage 2026-04-04 01:07:28 +09:00
Shakker
eb2cd09b72 test: trim facade runtime circular harness cost 2026-04-04 01:07:28 +09:00
Shakker
3cce39cae2 test: shrink media runtime facade coverage 2026-04-04 01:07:28 +09:00
Shakker
a65ff4de9f test: drain cron regression queue work before cleanup 2026-04-04 01:07:28 +09:00
Shakker
6299a5fbfe test: merge cron delivery-target thread coverage 2026-04-04 01:07:28 +09:00
Shakker
5ff72867bf test: type heartbeat reply mocks 2026-04-04 01:07:28 +09:00
Shakker
dcc3467a2b test: reset cron regression command queue state 2026-04-04 01:07:28 +09:00
Shakker
8bd3067e69 refactor: move built-in channel normalization to ids 2026-04-04 01:07:28 +09:00
Shakker
57ee3d9673 test: route config artifact checks through contracts 2026-04-04 01:07:28 +09:00
Shakker
33248980d9 test: split cron service regression ownership 2026-04-04 01:07:28 +09:00
Shakker
deb70e7e25 test: split cron isolated-agent turn coverage 2026-04-04 01:07:28 +09:00
Shakker
ebb4e9f0a6 test: route repo contract tests through contracts surface 2026-04-04 01:07:28 +09:00
Shakker
75b66403be test: route script suites through contracts surface 2026-04-04 01:07:28 +09:00
Shakker
335b472c37 test: merge subagent context-engine coverage into registry suite 2026-04-04 01:07:28 +09:00
Shakker
a78dba4396 refactor: lazy load heartbeat reply runtime 2026-04-04 01:07:28 +09:00
Shakker
bf3d1f85b8 test: avoid resetting cron issue regression modules 2026-04-04 01:07:28 +09:00
Shakker
5ae346427f test: fix stale typing in active suites 2026-04-04 01:07:28 +09:00
Shakker
ccdd33545b test: narrow qr dashboard integration surfaces 2026-04-04 01:07:28 +09:00
Shakker
652f273a0f refactor: lazy load approval forwarder defaults 2026-04-04 01:07:28 +09:00
Shakker
48fe2fd8be test: trim subagent context-engine harness cost 2026-04-04 01:07:28 +09:00
Shakker
9cf7b92e0d refactor: trim bundled capability metadata imports 2026-04-04 01:07:28 +09:00
Shakker
ac7e1f7c6c test: fix branch regression coverage 2026-04-04 01:07:28 +09:00
Shakker
6be5d34f2f test: avoid rebuilding openclaw tools in camera tests 2026-04-04 01:07:28 +09:00
Shakker
18891b1806 refactor: lazy load subagent registry runtime hooks 2026-04-04 01:07:28 +09:00
Shakker
08560c1f48 refactor: lazy load task cancellation control runtime 2026-04-04 01:07:28 +09:00
Shakker
192c02cd92 test: reuse subagent registry loop guard harness 2026-04-04 01:07:28 +09:00
Shakker
2d4428bcbb test: isolate outbound target registry boundaries 2026-04-04 01:07:28 +09:00
Shakker
2afa169250 test: isolate plugin dispatch runner boundaries 2026-04-04 01:07:28 +09:00
Shakker
0126653783 test: isolate message action runner test boundaries 2026-04-04 01:07:28 +09:00
Shakker
36c8282795 refactor: lazy load cli gateway helper runtimes 2026-04-04 01:07:28 +09:00
Shakker
58f1044ec0 test: drop duplicate outbound target coverage 2026-04-04 01:07:28 +09:00
Shakker
23422ccb68 refactor: lazy load cli gateway rpc runtime 2026-04-04 01:07:28 +09:00
Shakker
94340fdbae test: split message action runner boundaries 2026-04-04 01:07:28 +09:00
Shakker
42786afc64 refactor: trim image generation runtime imports 2026-04-04 01:07:28 +09:00
Shakker
768ec2a712 test: trim message action poll runner setup 2026-04-04 01:07:28 +09:00
Shakker
0875c2e370 test: trim message action media runner setup 2026-04-04 01:07:28 +09:00
Shakker
50069bcb59 fix: guard media image auto model resolution 2026-04-04 01:07:28 +09:00
Shakker
4b79ae7ad8 test: trim provider usage auth normalization setup 2026-04-04 01:07:28 +09:00
Shakker
14ff2c30d1 perf: prefer configured media auth providers 2026-04-04 01:07:28 +09:00
Shakker
25e9ff01cf refactor: trim media understanding runner test imports 2026-04-04 01:07:28 +09:00
Shakker
19493b681d test: harden channel metadata validation harness 2026-04-04 01:07:28 +09:00
Shakker
8d5c11d31b refactor: trim thinking helper import graph 2026-04-04 01:07:28 +09:00
Shakker
54af005f59 refactor: lazy load cron delivery outbound runtime 2026-04-04 01:07:28 +09:00
Shakker
9951f22766 refactor: split lightweight provider model id helpers 2026-04-04 01:07:28 +09:00
Shakker
9a6dda1b66 refactor: localize workspace skill prompt contract 2026-04-04 01:07:28 +09:00
Shakker
cc57bcfe2f refactor: lazy load cron subagent followup runtime 2026-04-04 01:07:28 +09:00
Shakker
9919e978ca refactor: lazy load cron auth and model runtime 2026-04-04 01:07:28 +09:00
Shakker
49563843dc refactor: trim remote skills startup imports 2026-04-04 01:07:28 +09:00
Shakker
c593ed0055 refactor: split lightweight plugin config policy 2026-04-04 01:07:28 +09:00
Shakker
4499d572fa refactor: split skill command specs from workspace snapshot 2026-04-04 01:07:28 +09:00
Shakker
24edb82ece refactor: split delivery target runtime seams 2026-04-04 01:07:28 +09:00
Shakker
d6ad92c1a0 fix: trim non-live test setup work 2026-04-04 01:07:28 +09:00
chi
33e6a6724d fix(telegram): enable HTML formatting for model switch messages (#60042)
* fix(telegram): enable HTML formatting for model switch messages

The model switch confirmation message was displaying raw Markdown
(**text**) instead of bold formatting because parse_mode was not set.

Changes:
- Add optional extra parameter to editMessageWithButtons for parse_mode
- Change format from Markdown ** to HTML <b> tags
- Pass parse_mode: 'HTML' when editing model switch message

Fixes the issue where model names appeared as **provider/model**
instead of bold text in Telegram.

* fix(telegram): escape HTML entities in model switch confirmation

Add defensive `escapeHtml` helper to sanitize `selection.provider`
and `selection.model` before interpolating them into the HTML
callback message. This prevents potential API rejection (HTTP 400)
if future provider or model names contain `<`, `>`, or `&`.

Addresses review feedback on unescaped HTML interpolation.

* test(telegram): cover HTML model switch confirmation

---------

Co-authored-by: Frank Yang <frank.ekn@gmail.com>
2026-04-04 00:05:09 +08:00
Tak Hoffman
7c738ad036 fix: honor whatsapp heartbeat account allowFrom 2026-04-03 11:04:00 -05:00
Vincent Koc
3d799ba004 fix(ci): tighten whatsapp and openai transport types 2026-04-04 01:02:41 +09:00
Vincent Koc
f49d8f665c test(providers): use preferred gpt-5.4 constant 2026-04-04 00:59:50 +09:00
Tak Hoffman
8805c7b55b fix: honor imessage setup dm policy accounts 2026-04-03 10:56:55 -05:00
Tak Hoffman
d114b4e033 fix: honor signal setup dm policy accounts 2026-04-03 10:54:40 -05:00
Peter Steinberger
b7f524abaa fix: resolve post-rebase gate follow-ups for #60081 2026-04-04 00:53:45 +09:00
Peter Steinberger
bf6bd7432a fix: harden discord ack auth and gate fallout (#60081) (thanks @FunJim) 2026-04-04 00:53:45 +09:00
FunJim
c1741abc3c test(discord): update ack reaction assertions to expect propagated cfg
The implementation fix propagates the hydrated cfg to reactMessageDiscord
and removeReactionDiscord. Update test assertions to expect the cfg
property in the options argument using expect.objectContaining to handle
the dynamic session store path.
2026-04-04 00:53:45 +09:00
FunJim
b51214ec3e fix(discord): pass hydrated config to ack reactions to fix SecretRef resolution
When extracting `reactMessageDiscord`, it defaulted to reading the raw config (which contains `SecretRef`s) if a hydrated `cfg` was omitted. We now pass the pre-resolved `cfg` context into the reaction options so the plugin SDK resolves the token via memory rather than the raw file.
2026-04-04 00:53:45 +09:00
Tak Hoffman
27ced5c1d3 fix: honor line setup dm policy accounts 2026-04-03 10:52:03 -05:00
Vincent Koc
f02f3b925d refactor(zalouser): lazy-load async runtime surfaces 2026-04-04 00:51:22 +09:00
Tak Hoffman
d1883470e7 fix: honor whatsapp setup dm policy accounts 2026-04-03 10:49:39 -05:00
Vincent Koc
bd4f745833 fix(providers): respect responses developer-role compat (#60385) 2026-04-04 00:49:16 +09:00
Vincent Koc
62b736a8c2 refactor(whatsapp): lazy-load login tool 2026-04-04 00:47:33 +09:00
Shakker
d9dbce4093 fix: restore missing contract registry config import 2026-04-03 16:45:32 +01:00
Tak Hoffman
69d018ce4f fix: honor zalouser setup dm policy accounts 2026-04-03 10:44:46 -05:00
Tak Hoffman
4107a5a4f0 fix: honor zalo setup dm policy accounts 2026-04-03 10:42:23 -05:00
Peter Steinberger
41ce3269f4 refactor(plugins): split activation snapshot and compat flow 2026-04-04 00:42:11 +09:00
Vincent Koc
eb3481fca9 refactor(discord): lazy-load actions and audit 2026-04-04 00:40:30 +09:00
Shakker
a6a200ebc2 docs(changelog): note browser and whatsapp seam split 2026-04-03 16:39:47 +01:00
Shakker
b1747d8b1c fix: remove unused sandbox browser type import 2026-04-03 16:39:47 +01:00
Shakker
846bfaa045 fix: align plugin sdk subpath expectations 2026-04-03 16:39:47 +01:00
Peter Steinberger
3aac90fc85 fix: restore browser-config sdk compatibility 2026-04-03 16:39:47 +01:00
Shakker
9a88a933cf refactor: narrow audit browser enablement check 2026-04-03 16:39:47 +01:00
Shakker
35541377d1 test: split whatsapp setup surface coverage 2026-04-03 16:39:47 +01:00
Shakker
9b8c892ff4 perf: direct export whatsapp target helpers 2026-04-03 16:39:47 +01:00
Shakker
5e7ebd098e fix: remove duplicate sandbox browser import 2026-04-03 16:39:47 +01:00
Shakker
e7cb9dec43 refactor: add approval auth runtime subpath 2026-04-03 16:39:47 +01:00
Shakker
6e3203a728 refactor: narrow whatsapp chunking imports 2026-04-03 16:39:47 +01:00
Shakker
4615ddf89b test: trim whatsapp channel test barrels 2026-04-03 16:39:47 +01:00
Shakker
6d6060d3ec perf: split whatsapp targets facade 2026-04-03 16:39:47 +01:00
Shakker
4528f8779e test: localize browser config env helper 2026-04-03 16:39:47 +01:00
Shakker
a5b23f17fb perf: split browser config sdk support 2026-04-03 16:39:47 +01:00
Shakker
557a07bd5b perf: skip browser runtime lookup for empty tab cleanup 2026-04-03 16:39:47 +01:00
Shakker
f41a67b118 fix: restore browser and whatsapp boundary contracts 2026-04-03 16:39:47 +01:00
Shakker
2e520d112d refactor: split browser sdk imports for sandbox and audit 2026-04-03 16:39:47 +01:00
Tak Hoffman
625201bddc fix: honor bluebubbles setup dm policy accounts 2026-04-03 10:39:32 -05:00
Vincent Koc
4b93000d11 test(contracts): isolate plugin registry 2026-04-04 00:38:27 +09:00
Tak Hoffman
6a28756e98 fix: honor nextcloud setup dm policy accounts 2026-04-03 10:37:07 -05:00
Vincent Koc
e67be773d6 test(contracts): isolate action registry 2026-04-04 00:35:35 +09:00
Tak Hoffman
3d8a039149 fix: honor legacy setup dm policy accounts 2026-04-03 10:34:18 -05:00
Peter Steinberger
904d9db132 fix(ci): repair whatsapp harness mocking 2026-04-03 16:32:47 +01:00
Vincent Koc
f2204cb35a test(contracts): isolate setup and status registries 2026-04-04 00:32:20 +09:00
Vincent Koc
d755709ddd refactor(discord): lazy-load cross-context ui 2026-04-04 00:31:29 +09:00
Tak Hoffman
b1026a0b28 fix: honor account-scoped setup dm policy 2026-04-03 10:31:00 -05:00
Peter Steinberger
c6b8109bd8 fix(ci): use sdk seams in whatsapp test harnesses 2026-04-03 16:29:53 +01:00
Peter Steinberger
8b6c224554 refactor(plugins): share activation context for provider runtimes 2026-04-04 00:29:09 +09:00
Vincent Koc
c8318754b5 test(contracts): lazily resolve session binding registry 2026-04-04 00:28:22 +09:00
Vincent Koc
7c6eba4634 test(config): cover thread binding legacy doctor paths 2026-04-04 00:26:41 +09:00
Vincent Koc
f1f6b98639 test(contracts): isolate slack outbound harness 2026-04-04 00:26:16 +09:00
Vincent Koc
79aa212789 refactor(whatsapp): lazy-load send and action runtimes 2026-04-04 00:25:02 +09:00
Tak Hoffman
dae0400a8f fix: honor discord account guild policy config 2026-04-03 10:24:42 -05:00
Vincent Koc
745aa26420 fix(ci): remove duplicate migrated test imports 2026-04-04 00:24:20 +09:00
Vincent Koc
ed297eb8b9 fix(providers): align cache-ttl anthropic semantics (#60375) 2026-04-04 00:22:32 +09:00
Vincent Koc
af835acd00 test(config): cover legacy tts doctor paths 2026-04-04 00:21:45 +09:00
Vincent Koc
ade6b61358 test(contracts): split registry-backed channel contract lanes 2026-04-04 00:21:30 +09:00
Vincent Koc
f71ef47288 fix(ci): disable automatic clawhub release workflow 2026-04-04 00:20:28 +09:00
Vincent Koc
a592cd67cb fix(ci): bump clawhub plugin versions for release gate 2026-04-04 00:20:27 +09:00
Peter Steinberger
1dfcdbdf91 fix(testing): repair bundled plugin helper imports 2026-04-03 16:19:39 +01:00
Tak Hoffman
e3fea41b59 fix: honor telegram account topic mention config 2026-04-03 10:19:11 -05:00
Vincent Koc
c71df2f4b0 test(commands): allow scoped channel test registries 2026-04-04 00:18:34 +09:00
Peter Steinberger
2e779a1b20 refactor(discord): share thread starter snapshot parsing 2026-04-04 00:17:57 +09:00
Vincent Koc
0f129c87ba test(config): cover telegram and x_search legacy doctor paths 2026-04-04 00:16:57 +09:00
Tak Hoffman
a3541a1cce fix: honor telegram account replyToMode 2026-04-03 10:16:05 -05:00
Tak Hoffman
30c0dc3d47 fix: honor discord account replyToMode 2026-04-03 10:14:42 -05:00
Vincent Koc
4e3b2781fb test(contracts): split session binding registry seams 2026-04-04 00:13:40 +09:00
Peter Steinberger
93f136cbed test: split inbound contract suites by channel 2026-04-03 16:13:09 +01:00
Peter Steinberger
2da3b45ce7 test: reduce discord component partial mocks 2026-04-03 16:13:09 +01:00
Tak Hoffman
8e9607c064 fix: honor googlechat account replyToMode 2026-04-03 10:12:47 -05:00
Vincent Koc
f08a1c34dd fix(providers): scope anthropic-family cache semantics (#60370) 2026-04-04 00:11:57 +09:00
Vincent Koc
b50b85a5db refactor(zalo): lazy-load setup wizard surface 2026-04-04 00:11:07 +09:00
Vincent Koc
702a200844 fix(ci): guard optional discord reaction cleanup 2026-04-04 00:09:32 +09:00
Tak Hoffman
78c390ea86 docs: align messages config support notes 2026-04-03 10:08:34 -05:00
Vincent Koc
5ad2c61c9a test(config): cover gateway bind legacy doctor flow 2026-04-04 00:07:19 +09:00
Peter Steinberger
cee5f960b5 fix(gateway): preserve raw activation source for startup plugin loads 2026-04-04 00:06:57 +09:00
Vincent Koc
93d514f816 fix(ci): correct zalo status helper imports 2026-04-04 00:06:47 +09:00
Vincent Koc
0eb9416d9c refactor(telegram): lazy-load send and action runtimes 2026-04-04 00:06:38 +09:00
Tak Hoffman
30fc29c9b0 fix: honor discord status reactions toggle 2026-04-03 10:05:17 -05:00
Vincent Koc
ca68d57dc2 test(config): cover legacy heartbeat and memorySearch doctor paths 2026-04-04 00:04:25 +09:00
Vincent Koc
9e6da1e70a fix(providers): pass anthropic cache retention through custom apis (#60359) 2026-04-04 00:04:09 +09:00
Vincent Koc
6366010884 fix(ci): route extension test helpers through public sdk seams 2026-04-04 00:03:48 +09:00
Peter Steinberger
ad8870ae28 test: narrow gateway and model status runtime seams 2026-04-03 16:03:32 +01:00
Peter Steinberger
a6816cb59c test: reduce subagent announce import overhead 2026-04-03 16:03:32 +01:00
Peter Steinberger
25a187568f test: trim whatsapp monitor import overhead 2026-04-03 16:03:32 +01:00
Shakker
b98ee01814 fix: restore cron context window priming 2026-04-03 16:03:10 +01:00
Shakker
f5276ed38b test: preserve cron model-selection helper exports 2026-04-03 16:03:10 +01:00
Shakker
de952c036a refactor: split cron delivery planning from sending 2026-04-03 16:03:10 +01:00
Shakker
bd8d29c2b1 fix: align cron test delivery result types 2026-04-03 16:03:10 +01:00
Shakker
6363094e93 refactor: trim cron session store startup imports 2026-04-03 16:03:10 +01:00
Shakker
1f0c4a624b refactor: route cron subagent reads through registry seam 2026-04-03 16:03:10 +01:00
Shakker
11dbcdc46d refactor: narrow model fallback auth imports 2026-04-03 16:03:10 +01:00
Shakker
b721f5e48a refactor: lazy load cron gateway cleanup 2026-04-03 16:03:10 +01:00
Shakker
a4efe7c028 refactor: narrow cron delivery session imports 2026-04-03 16:03:10 +01:00
Shakker
12fa700579 refactor: lazy load cron usage formatting 2026-04-03 16:03:10 +01:00
Shakker
fc8ab82aab refactor: trim cron session startup imports 2026-04-03 16:03:10 +01:00
Shakker
88b1c00b39 refactor: lazy load cron cli runtime 2026-04-03 16:03:10 +01:00
Shakker
7a9ad3820e refactor: localize cron channel test outbounds 2026-04-03 16:03:10 +01:00
Hiroshi Tanaka
e9a1f7818c fix(discord): extract forwarded message text in thread starter resolution (#60139)
resolveDiscordThreadStarter only checked content and embeds, returning
null for forwarded messages where the text lives in message_snapshots.

Add a local resolveStarterForwardedText helper that extracts text
directly from the message_snapshots array on the REST response object.
This avoids fragile type casts and keeps the change self-contained
within threading.ts.

Fixes #60129
2026-04-04 00:02:42 +09:00
Vincent Koc
fbd361d338 fix(config): surface legacy channel streaming aliases (#60358) 2026-04-04 00:00:38 +09:00
Vincent Koc
3257136160 refactor(zalo): narrow channel sdk imports 2026-04-04 00:00:34 +09:00
Vincent Koc
5ccf6d229b test(channels): remove unused group policy helper 2026-04-03 23:57:43 +09:00
Tak Hoffman
759c81ceb8 test: audit heartbeat config honor 2026-04-03 09:55:51 -05:00
Vincent Koc
35cf7d0340 fix(config): migrate legacy sandbox perSession alias (#60346)
* fix(config): migrate legacy sandbox perSession alias

* fix(config): preserve invalid sandbox persession values
2026-04-03 23:55:47 +09:00
Vincent Koc
b6dd7ac232 test(channels): remove unused dm policy helper 2026-04-03 23:54:07 +09:00
Vincent Koc
f9f0a593e4 test(helpers): remove unused plugin helper wrappers 2026-04-03 23:52:58 +09:00
Vincent Koc
a028e16eaa test(helpers): remove unused bundled plugin surface wrapper 2026-04-03 23:50:55 +09:00
Vincent Koc
35aa6c6126 test(channel): remove unused outbound helper 2026-04-03 23:50:06 +09:00
Vincent Koc
756cf847e0 refactor(telegram): lazy-load audit and monitor surfaces 2026-04-03 23:49:53 +09:00
Vincent Koc
279ee5e842 test(commands): lazy-load default channel registry plugins 2026-04-03 23:48:38 +09:00
Frank Yang
e9f82ac752 fix: clear stale ClawHub query results on input change (#60267)
* fix: clear stale ClawHub query results on input change

* docs: move ClawHub follow-up changelog entry to section tail
2026-04-03 22:48:14 +08:00
Vincent Koc
76ff144037 test(outbound): remove unused runner helper 2026-04-03 23:46:24 +09:00
Vincent Koc
66825c0969 refactor(providers): centralize native provider detection (#60341)
* refactor(providers): centralize native provider detection

* fix(providers): preserve openrouter thinking format

* fix(providers): preserve openrouter host thinking format
2026-04-03 23:46:21 +09:00
Vincent Koc
24e10e6e45 test(contracts): lazy-load slack outbound contract surface 2026-04-03 23:45:01 +09:00
Vincent Koc
38a4b2b14c refactor(signal): route target normalization through channel-targets 2026-04-03 23:44:50 +09:00
Vincent Koc
5ce7aee33b test(cron): localize core channel outbound test loads 2026-04-03 23:41:54 +09:00
Vincent Koc
e9acbdb111 fix(ci): share heavy check lock across test and lint 2026-04-03 23:41:34 +09:00
Vincent Koc
8ffeadd8f9 test(contracts): lazy-load outbound contract plugin helpers 2026-04-03 23:40:19 +09:00
Peter Steinberger
cd38eba316 refactor: unify plugin activation source plumbing 2026-04-03 23:39:36 +09:00
Vincent Koc
975d2ddce2 test(contracts): lazy-load inbound contract plugin helpers 2026-04-03 23:39:26 +09:00
Vincent Koc
3b69b8e3c4 fix(ci): route extension test helpers through sdk testing 2026-04-03 23:39:06 +09:00
Vincent Koc
c013b9cdf3 test(contracts): lazy-load session binding test facades 2026-04-03 23:37:59 +09:00
Vincent Koc
316978700e test(gateway): lazy-load speech provider test surfaces 2026-04-03 23:33:16 +09:00
Vincent Koc
316da43dd7 fix(config): migrate legacy talk config via doctor (#60333)
* fix(config): migrate legacy talk config via doctor

* fix(config): harden legacy talk provider migration
2026-04-03 23:32:36 +09:00
Vincent Koc
23d8a979b3 test(contracts): lazy-load discord thread binding test surface 2026-04-03 23:31:47 +09:00
Vincent Koc
1556490ee7 fix(ci): collapse duplicate provider request union 2026-04-03 23:30:25 +09:00
Vincent Koc
cddb34ba6a refactor(line): lazy-load card command 2026-04-03 23:29:34 +09:00
Vincent Koc
0d7d573cd6 test(commands): split default channel test registry helper 2026-04-03 23:29:24 +09:00
Vincent Koc
06ed0eaad5 fix(ci): narrow discord subagent hook types 2026-04-03 23:28:03 +09:00
Peter Steinberger
b40d4b63f6 refactor: centralize update targets and extension guardrails 2026-04-03 23:26:31 +09:00
Vincent Koc
8f5f78bbe8 feat(providers): reopen model request transport config (#60327)
* feat(providers): reopen model request transport config

* chore(config): refresh request override baselines
2026-04-03 23:25:11 +09:00
Vincent Koc
cedc8bdebb refactor(signal): lazy-load monitor surfaces 2026-04-03 23:24:42 +09:00
Vincent Koc
4e9629d60c fix(ci): route bluebubbles helper through local barrel 2026-04-03 23:24:17 +09:00
Peter Steinberger
d375cd727e fix: migrate legacy web search config on startup 2026-04-03 23:24:02 +09:00
Shakker
549e0bb268 test: keep imessage test plugin facade-free by default 2026-04-03 15:23:50 +01:00
Vincent Koc
690c58baa2 refactor(discord): lazy-load subagent hooks 2026-04-03 23:22:34 +09:00
Vincent Koc
0ecf84524d fix(ci): restore line runtime seams 2026-04-03 23:19:39 +09:00
Vincent Koc
78fb352506 refactor(feishu): lazy-load subagent hooks 2026-04-03 23:19:11 +09:00
Vincent Koc
52008e2e60 fix(doctor): clarify legacy config migration guidance (#60326) 2026-04-03 23:16:11 +09:00
Vincent Koc
a9a057d1eb fix(ci): normalize extension harness imports 2026-04-03 23:15:57 +09:00
Vincent Koc
ac20eed335 fix(ci): route extension tests through sdk seams 2026-04-03 23:15:57 +09:00
Vincent Koc
ed166ba338 test(contracts): extract narrow channel contract helpers 2026-04-03 23:14:45 +09:00
Josh Lehman
799c6f40aa refactor: move provider replay runtime ownership into plugins (#60126)
* refactor: move provider replay runtime ownership into plugins

* fix(provider-runtime): address review followups

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-04-03 23:14:37 +09:00
Shakker
f328e7f4a6 docs: add changelog for outbound seam refactor 2026-04-03 15:10:48 +01:00
Shakker
6395336454 fix: resolve outbound seam follow-ups 2026-04-03 15:10:48 +01:00
Shakker
1a23627e32 refactor: split delivery target runtime seams 2026-04-03 15:10:48 +01:00
Shakker
c2e93c76bd refactor: split session store loader from maintenance 2026-04-03 15:10:48 +01:00
Shakker
883a35a38c refactor: narrow cron delivery target session imports 2026-04-03 15:10:48 +01:00
Shakker
cef0f36931 refactor: split chat history text helpers 2026-04-03 15:10:48 +01:00
Shakker
4a0905b94b refactor: lazy load outbound channel bootstrap 2026-04-03 15:10:48 +01:00
Shakker
4a81771290 refactor: lazy load outbound transcript mirroring 2026-04-03 15:10:48 +01:00
Shakker
e26a590f7a refactor: drop heavy channel outbound test imports 2026-04-03 15:10:48 +01:00
Shakker
a61408737f refactor: localize deliver test outbounds 2026-04-03 15:10:48 +01:00
Shakker
d338299dc7 refactor: keep deliver tests channel-generic 2026-04-03 15:10:48 +01:00
Shakker
909895c471 refactor: narrow deliver test channel boundaries 2026-04-03 15:10:48 +01:00
Shakker
d7e4fad872 refactor: trim outbound delivery test imports 2026-04-03 15:10:48 +01:00
Shakker
52717ee399 refactor: trim outbound target test imports 2026-04-03 15:10:48 +01:00
Agustin Rivera
3cd9aac6bb Require owner access for /allowlist writes (#59836)
* fix(allowlist): require owner access for writes

* docs(changelog): note allowlist owner gate fix

---------

Co-authored-by: Jacob Tomlinson <jtomlinson@nvidia.com>
2026-04-03 07:07:36 -07:00
Vincent Koc
62b1fe0b85 fix(ci): correct browser live test export 2026-04-03 23:07:20 +09:00
Peter Steinberger
8d0bed458e refactor: simplify reply-threading and test helpers 2026-04-03 23:06:22 +09:00
Vincent Koc
1125cd3b97 refactor(xai): lazy-load heavy tool modules 2026-04-03 23:03:33 +09:00
Vincent Koc
efeee6f921 fix(ci): use plugin registry test bridges 2026-04-03 23:03:15 +09:00
Vincent Koc
4b2c7404e5 test(types): remove remaining testing barrel references 2026-04-03 23:03:02 +09:00
Vincent Koc
5341d483d1 test(acpx): use direct ACP adapter contract seam 2026-04-03 23:01:51 +09:00
Peter Steinberger
0dad4072b4 fix: keep extension helper imports behind local runtime barrels (#60153) 2026-04-03 23:01:43 +09:00
Peter Steinberger
96e8352bda fix: align extension boundary guardrails for landing (#60153) 2026-04-03 23:01:43 +09:00
Peter Steinberger
0c0d84fbd9 fix: route pnpm test wrappers through the active runner (#60153) 2026-04-03 23:01:43 +09:00
Peter Steinberger
ab57d24f79 fix: stabilize npm owner update test on Windows (#60153) (thanks @jayeshp19) 2026-04-03 23:01:43 +09:00
jayeshp19
b9ede82cc2 greptile fix 2026-04-03 23:01:43 +09:00
jayeshp19
eb4b6f7024 Use owning npm prefix for global updates 2026-04-03 23:01:43 +09:00
Vincent Koc
c1d68f213d test(helpers): use direct internal seams 2026-04-03 23:00:28 +09:00
Vincent Koc
d2427c19e0 fix(ci): restore extension runtime seams 2026-04-03 22:57:28 +09:00
Vincent Koc
9b83e462cf test(channels): use narrow active registries in sticky tests 2026-04-03 22:57:16 +09:00
Vincent Koc
9aab672a69 refactor(bluebubbles): narrow monitor processing exports 2026-04-03 22:56:26 +09:00
Peter Steinberger
bc137951e9 fix: preserve allowlist guard for auto-enabled bundled channels (#60233) (thanks @dorukardahan) 2026-04-03 22:55:31 +09:00
Doruk Ardahan
cd08facd7a fix(plugins): keep auto-enabled channels behind allowlists 2026-04-03 22:55:30 +09:00
Doruk Ardahan
f7d24c1ed5 fix(plugins): allow configured bundled channels past allowlists 2026-04-03 22:55:30 +09:00
Vincent Koc
2ad69083d2 refactor(feishu): narrow reply dispatcher exports 2026-04-03 22:54:29 +09:00
Vincent Koc
1ad9fe0b52 test(discord): use direct channel test helper 2026-04-03 22:51:37 +09:00
Vincent Koc
f6e99bd514 refactor(msteams): narrow messenger sdk imports 2026-04-03 22:50:54 +09:00
pgondhi987
b48b528b02 fix(skills): block UV_PYTHON in workspace dotenv and harden uv installer env [AI] (#59178)
* fix: address issue

* fix: finalize issue changes

* fix: address PR review feedback

* Changelog: note uv installer env hardening

---------

Co-authored-by: Jacob Tomlinson <jtomlinson@nvidia.com>
2026-04-03 06:50:43 -07:00
Vincent Koc
8b5e80fcaa refactor(msteams): narrow store sdk imports 2026-04-03 22:49:27 +09:00
Vincent Koc
6f9b4b52f8 refactor(msteams): narrow send sdk imports 2026-04-03 22:47:07 +09:00
Vincent Koc
3b358414d3 test(channels): use direct contract helper imports 2026-04-03 22:46:58 +09:00
Vincent Koc
4798e125f4 feat(providers): add llm transport adapter seam (#60264)
* feat(providers): add llm transport adapter seam

* fix(providers): harden openai transport adapter

* fix(providers): correct transport usage accounting
2026-04-03 22:45:47 +09:00
Vincent Koc
875c3813aa refactor(msteams): narrow outbound sdk imports 2026-04-03 22:45:09 +09:00
Vincent Koc
f904a49568 refactor(feishu): narrow channel runtime exports 2026-04-03 22:43:09 +09:00
Vincent Koc
020093bf4b test(slack): use direct outbound payload harness 2026-04-03 22:43:06 +09:00
Vincent Koc
597416fdd9 refactor(feishu): narrow outbound runtime exports 2026-04-03 22:41:46 +09:00
Peter Steinberger
173bb0aea0 test: remove update-cli shared partial mock 2026-04-03 14:40:11 +01:00
Onur
fa9e1e3d8e CI: add ClawHub plugin release workflow (#59179)
* CI: add ClawHub plugin release workflow

* CI: harden ClawHub plugin release workflow

* CI: finish ClawHub plugin release hardening

* CI: watch shared ClawHub release inputs

* CI: harden ClawHub publish workflow

* CI: watch more ClawHub release deps

* CI: match shared release inputs by prefix

* CI: pin ClawHub publish source commit

* CI: refresh pinned ClawHub release commit

* CI: rename ClawHub plugin release environment

---------

Co-authored-by: Onur Solmaz <onur@solmaz.io>
2026-04-03 15:40:07 +02:00
Shakker
e0e5df25e6 test: unstick context lookup fake-timer warmup 2026-04-03 14:38:30 +01:00
Peter Steinberger
8962a8fb73 test: reduce status and update-cli partial mocks 2026-04-03 14:37:47 +01:00
Peter Steinberger
bab14fb8f1 test: lazy-load config doc baseline runtime 2026-04-03 14:29:17 +01:00
Peter Steinberger
bb25a8050c test: baseline bundled runtime sidecar paths 2026-04-03 14:26:01 +01:00
Shakker
8fabfa5d1c fix: rehydrate context token cache after module reload 2026-04-03 14:23:44 +01:00
Peter Steinberger
ba7297ff21 test: narrow media fs-safe test seams 2026-04-03 14:22:12 +01:00
Shakker
3b727d2433 test: harden package-root mocks after setup slimming 2026-04-03 14:17:16 +01:00
Shakker
a898cd464a fix: use runtime plugin state in test setup 2026-04-03 14:17:16 +01:00
Shakker
a764b8f8cd refactor: trim test setup agent imports 2026-04-03 14:17:16 +01:00
Shakker
73073a91bb refactor: isolate session store test cleanup state 2026-04-03 14:17:16 +01:00
Shakker
7bd6bb91c4 fix: trim non-live test setup work 2026-04-03 14:17:16 +01:00
Peter Steinberger
6510ecafb0 test: narrow bluebubbles reply-cache imports 2026-04-03 14:16:29 +01:00
Peter Steinberger
85bd5b3ce7 fix(ci): refresh protocol models and align channel tests 2026-04-03 14:13:32 +01:00
Peter Steinberger
e43a362ad9 test: trim update-cli metadata fixture import 2026-04-03 14:09:28 +01:00
Peter Steinberger
3a2667b5fc test: fix status and update-cli mock drift 2026-04-03 14:06:32 +01:00
Peter Steinberger
bbbfbd2db4 test: import slack monitor helpers directly 2026-04-03 14:03:05 +01:00
Peter Steinberger
91618438bc test: narrow googlechat channel test deps 2026-04-03 14:03:05 +01:00
Peter Steinberger
6d05607cd9 test: trim nextcloud send config import cost 2026-04-03 14:03:05 +01:00
Peter Steinberger
a884ad3cf2 fix(ci): route extension test helpers through sdk seams 2026-04-03 13:58:21 +01:00
Peter Steinberger
35d890b5ef test: narrow subagent spawn test seams 2026-04-03 13:53:11 +01:00
Peter Steinberger
88bc6d852f fix(ci): route remaining feishu runtime seams locally 2026-04-03 13:52:40 +01:00
Peter Steinberger
1a75fc9e05 fix: align latest-main gate drift on #60221 2026-04-03 21:52:35 +09:00
Ayaan Zaidi
39361d13be fix: restore bootstrap tokens after send failure (#60221) 2026-04-03 21:52:35 +09:00
Ayaan Zaidi
5e3a3c42ca fix(gateway): revoke bootstrap tokens after handshake commit 2026-04-03 21:52:35 +09:00
Ayaan Zaidi
b08d58c917 fix(gateway): track bootstrap profile redemption 2026-04-03 21:52:35 +09:00
Ayaan Zaidi
0891253012 fix(pairing): preserve mixed-role node scopes 2026-04-03 21:52:35 +09:00
Ayaan Zaidi
a42f000b53 fix(gateway): defer bootstrap token revocation 2026-04-03 21:52:35 +09:00
Peter Steinberger
df115822b9 test: reduce non-telegram import overhead 2026-04-03 13:49:51 +01:00
Peter Steinberger
4f4aa46d00 test: split telegram bot command menu coverage 2026-04-03 13:49:51 +01:00
Peter Steinberger
da1980b923 fix(ci): route final feishu helper barrels locally 2026-04-03 13:46:06 +01:00
Vincent Koc
a3b9ae8fab refactor(feishu): narrow bot runtime exports 2026-04-03 21:44:51 +09:00
Vincent Koc
8735dd7d19 fix(ci): restore channel helper seams 2026-04-03 21:43:32 +09:00
Vincent Koc
7ede711cae fix(test): use shell spawn for windows test runner 2026-04-03 21:43:32 +09:00
Vincent Koc
01a163c7f3 fix(discord): restore runtime action seam 2026-04-03 21:43:32 +09:00
Vincent Koc
851de3554e refactor(feishu): split comment dispatcher seam 2026-04-03 21:43:29 +09:00
Vincent Koc
349d3e8289 test(plugin-sdk): extract direct helper seams 2026-04-03 21:42:04 +09:00
Vincent Koc
2fff6be4c3 refactor(feishu): split monitor state seam 2026-04-03 21:41:47 +09:00
Peter Steinberger
2a54a7f7cd fix(ci): route remaining feishu helper barrels locally 2026-04-03 13:41:32 +01:00
Vincent Koc
77c04ec29c test(whatsapp): use direct outbound helper fixtures 2026-04-03 21:40:12 +09:00
Vincent Koc
56ead96f48 test(discord): use direct system event helpers 2026-04-03 21:39:30 +09:00
Vincent Koc
027a544d8f refactor(feishu): split dedup runtime seam 2026-04-03 21:38:51 +09:00
Vincent Koc
710c63edad test(extensions): use direct runtime capture helpers 2026-04-03 21:37:41 +09:00
Vincent Koc
319aa2f1fe refactor(feishu): split runtime helper seams 2026-04-03 21:37:32 +09:00
Peter Steinberger
899fe6ffa5 fix(ci): route feishu bot helpers through local barrel 2026-04-03 13:36:55 +01:00
Vincent Koc
0ca2a91213 test(extensions): use direct helper seams in provider tests 2026-04-03 21:36:13 +09:00
Vincent Koc
a05daaa832 refactor(feishu): split channel runtime seam 2026-04-03 21:34:08 +09:00
Vincent Koc
0a61138e77 test(deepgram): use direct audio test helpers 2026-04-03 21:32:47 +09:00
Vincent Koc
e4cc8cd975 refactor(feishu): split outbound runtime seam 2026-04-03 21:32:25 +09:00
Peter Steinberger
ee39ec29d1 fix(ci): restore talk-voice plugin runtime export 2026-04-03 13:32:16 +01:00
Vincent Koc
344717a2d5 refactor(feishu): split comment handler seam 2026-04-03 21:30:16 +09:00
Peter Steinberger
65cddd79eb fix(ci): align discord actions contract with config discovery 2026-04-03 13:29:17 +01:00
Vincent Koc
beb108cfaa refactor(feishu): split bot runtime seam 2026-04-03 21:28:15 +09:00
Peter Steinberger
49936f6066 refactor: move ollama synthetic auth precedence into extension 2026-04-03 21:25:02 +09:00
Vincent Koc
a0dbba1626 test(extensions): narrow provider registration test helpers 2026-04-03 21:24:43 +09:00
Vincent Koc
568859e1fb test(extensions): avoid barrel testing helpers in media tests 2026-04-03 21:23:47 +09:00
Vincent Koc
2a04d5c16f test(extensions): narrow utility test helper imports 2026-04-03 21:23:47 +09:00
Vincent Koc
a3cadfd51d test(talk-voice): slim command runtime fixture 2026-04-03 21:22:00 +09:00
Peter Steinberger
7c41b9fca9 fix(ci): route telegram test harness through reply runtime 2026-04-03 13:21:38 +01:00
Vincent Koc
7fa0c76ffc test(mattermost): slim leaf runtime fixtures 2026-04-03 21:21:24 +09:00
Vincent Koc
f0a4423271 fix(tui): tolerate clock skew in pending-history reconciliation 2026-04-03 21:21:09 +09:00
Peter Steinberger
a3f34a8f77 test: reduce telegram context partial mocks 2026-04-03 13:19:50 +01:00
Vincent Koc
c186644662 test(googlechat): use narrow registry helpers in webhook routing tests 2026-04-03 21:18:51 +09:00
Vincent Koc
e4abd34466 test(feishu): drop unused client runtime imports 2026-04-03 21:18:17 +09:00
Vincent Koc
4e60653959 fix(test): default local Vitest to one worker (#60281) 2026-04-03 21:18:12 +09:00
Vincent Koc
69da71c0ce test(feishu): slim tool runtime fixtures 2026-04-03 21:17:11 +09:00
Vincent Koc
cd81e0a07b test(zalo): replace heavy testing helpers in monitor tests 2026-04-03 21:17:02 +09:00
Peter Steinberger
5184522f2f refactor: trim extension test runner surface 2026-04-03 13:15:43 +01:00
Vincent Koc
3a68414569 test(feishu): slim bot runtime fixtures 2026-04-03 21:14:08 +09:00
Peter Steinberger
99397254a1 fix(ci): relax feishu runtime test casts 2026-04-03 13:12:23 +01:00
Peter Steinberger
d2dae50a75 test: trim telegram bot import graph 2026-04-03 13:10:43 +01:00
Vincent Koc
9245b9e2f4 test(zalo): use narrow registry helpers in lifecycle tests 2026-04-03 21:10:33 +09:00
Peter Steinberger
f59d0eac68 refactor(plugin-runtime): remove plugin-specific core seams 2026-04-03 13:08:39 +01:00
Vincent Koc
4846ebce12 fix(test): serialize local heavy checks (#60273) 2026-04-03 21:07:56 +09:00
Vincent Koc
feca4aa49e test(feishu): replace heavy runtime mock helper in bot tests 2026-04-03 21:07:43 +09:00
Peter Steinberger
fbb2537774 refactor: clarify tool schema normalization 2026-04-03 21:07:19 +09:00
Peter Steinberger
1118d032ca refactor: split extension test helpers 2026-04-03 13:06:11 +01:00
Peter Steinberger
87abcfd6a6 fix: harden ollama tool-call replay (#52253) (thanks @Adam-Researchh) 2026-04-03 21:06:06 +09:00
Vincent Koc
e116e7d584 test(feishu): slim comment monitor runtime fixtures 2026-04-03 21:05:54 +09:00
Vincent Koc
dc312a4c76 test(feishu): slim reaction monitor runtime fixtures 2026-04-03 21:02:26 +09:00
Peter Steinberger
685ef52284 refactor: simplify test workflow helpers 2026-04-03 13:00:00 +01:00
Peter Steinberger
71a54d0c95 fix(ci): forward bluebubbles barrel and node env fixes 2026-04-03 12:58:10 +01:00
Vincent Koc
688eb8435b test(bluebubbles): split webhook ingress seam 2026-04-03 20:58:03 +09:00
Vincent Koc
2734d06ade test(matrix): avoid loading send module in thread binding tests 2026-04-03 20:56:58 +09:00
Vincent Koc
a8040ab9d9 test(matrix): avoid loading action modules in tool tests 2026-04-03 20:55:47 +09:00
Vincent Koc
b925f6d46c test(zalo): avoid loading monitor and probe modules in startup test 2026-04-03 20:55:09 +09:00
Vincent Koc
fbc4fa6ac3 test(feishu): avoid loading streaming card module in dispatcher tests 2026-04-03 20:54:24 +09:00
Vincent Koc
d888ce242b test(bluebubbles): split monitor processing seam 2026-04-03 20:54:08 +09:00
Peter Steinberger
0d938748a5 refactor(sessions): clarify duplicate session resolution 2026-04-03 20:53:17 +09:00
Vincent Koc
a0c6ea5aba test(feishu): avoid loading bot and send modules in menu tests 2026-04-03 20:52:26 +09:00
Peter Steinberger
a2077b28ef refactor: trim vitest wrapper layers 2026-04-03 12:52:14 +01:00
Vincent Koc
bd1e78ea34 test(msteams): avoid loading graph upload module in messenger tests 2026-04-03 20:50:00 +09:00
Vincent Koc
82fca281b6 test(msteams): avoid loading graph module in message tests 2026-04-03 20:50:00 +09:00
Vincent Koc
b410c5434c test(msteams): avoid loading graph module in member tests 2026-04-03 20:50:00 +09:00
Vincent Koc
d9aa88dd6c test(bluebubbles): split channel status seam 2026-04-03 20:46:42 +09:00
Vincent Koc
9bd05d3841 test(browser): stop reloading auth server module 2026-04-03 20:45:45 +09:00
Peter Steinberger
57999f9965 fix: narrow empty MCP tool schema normalization (#60176) (thanks @Bartok9) 2026-04-03 20:45:33 +09:00
Bartok Moltbot
19dbe00763 fix(tools): normalize truly empty MCP tool schemas for OpenAI
Fixes #60158

MCP tools with parameter-free schemas may return truly empty objects
`{}` without a `type` field. The existing normalization handled
`{ type: "object" }` → `{ type: "object", properties: {} }` but
missed the truly empty case.

OpenAI gpt-5.4 rejects tool schemas without `type: "object"` and
`properties`, causing HTTP 400 errors:

```
Invalid schema for function 'flux-mcp__get_flux_instance':
In context=(), object schema missing properties.
```

This change catches empty schemas (no type, no properties, no unions)
before the final pass-through and converts them to the required format.

Added test case for parameter-free MCP tool schemas.
2026-04-03 20:45:33 +09:00
Peter Steinberger
6845b8061c docs: simplify vitest workflow guidance 2026-04-03 12:45:13 +01:00
Peter Steinberger
9ef5d85e40 refactor: remove custom test planner runtime 2026-04-03 12:45:13 +01:00
Peter Steinberger
c80c1cf56f test: drop planner fixtures and coverage 2026-04-03 12:45:13 +01:00
Vincent Koc
d21d859ded test(browser): stop reloading cdp screenshot module 2026-04-03 20:44:53 +09:00
Vincent Koc
11c6202ec0 test(bluebubbles): split action metadata seam 2026-04-03 20:44:23 +09:00
Vincent Koc
9a53c3d772 test(browser): drop redundant module resets 2026-04-03 20:43:49 +09:00
Vincent Koc
6e3eb34a90 test(bluebubbles): narrow action helper imports 2026-04-03 20:42:29 +09:00
Peter Steinberger
36a233ff98 fix(config): honor isolated state-dir config writes 2026-04-03 12:42:08 +01:00
Vincent Koc
51d6d7013f fix(tui): preserve pending sends and busy-state visibility (#59800)
* fix(tui): preserve pending messages across refreshes

* fix(tui): keep fallback runs visibly active

* fix(tui): expose full verbose mode and reclaim width

* refactor(tui): drop stale optimistic-send state

* test(tui): drop unused state binding

* docs(changelog): add tui beta note

* fix(tui): bound fallback wait and dedupe pending restore

* fix(tui): preserve queued sends and busy-state visibility

* chore(changelog): align tui pending-send note

* chore(changelog): refine tui release note
2026-04-03 20:39:55 +09:00
Vincent Koc
79da4a46b4 fix(feishu): annotate send target return 2026-04-03 20:36:24 +09:00
Vincent Koc
dc6e041cfe test(bluebubbles): narrow monitor normalize number parsing 2026-04-03 20:36:24 +09:00
Vincent Koc
2ec9d3d58b test(bluebubbles): narrow request-url export 2026-04-03 20:36:24 +09:00
Vincent Koc
69e0fbd95e test(bluebubbles): narrow media-send limit import 2026-04-03 20:36:24 +09:00
Vincent Koc
13412c0c50 test(bluebubbles): narrow send markdown import 2026-04-03 20:36:24 +09:00
Peter Steinberger
afa78a5b13 test: trim telegram testing barrel imports 2026-04-03 12:36:07 +01:00
Peter Steinberger
de1d0f4fae fix(ci): restore telegram real registry test support 2026-04-03 12:31:28 +01:00
samzong
37ab4b7fdc [Feat] Add ClawHub skill search and detail in Control UI (#60134)
* feat(gateway): add skills.search and skills.detail RPC methods

Expose ClawHub search and detail capabilities through the Gateway protocol,
enabling desktop/web clients to browse and inspect skills from the registry.

New RPCs:
- skills.search: search ClawHub skills by query with optional limit
- skills.detail: fetch full detail for a single skill by slug

Both methods delegate to existing agent-layer functions
(searchSkillsFromClawHub, fetchSkillDetailFromClawHub) which wrap
the ClawHub HTTP client. No new external dependencies.

Signed-off-by: samzong <samzong.lu@gmail.com>

* feat(skills): add ClawHub skill search and detail in Control UI

Add skills.search and skills.detail Gateway RPC methods with typed
protocol schemas, AJV validators, and handler implementations. Wire
the new RPCs into the Control UI Skills panel with a debounced search
input, results list, detail dialog, and one-click install from ClawHub.

Gateway:
- SkillsSearchParams/ResultSchema and SkillsDetailParams/ResultSchema
- Handler calls searchClawHubSkills and fetchClawHubSkillDetail directly
- Remove zero-logic fetchSkillDetailFromClawHub wrapper
- 9 handler tests including boundary validation

Control UI:
- searchClawHub, loadClawHubDetail, installFromClawHub controllers
- 300ms debounced search input to avoid 429 rate limits
- Dedicated install busy state (clawhubInstallSlug) with success/error feedback
- Install buttons disabled during install with progress text
- Detail dialog with owner, version, changelog, platform metadata

Part of #43301

Signed-off-by: samzong <samzong.lu@gmail.com>

* fix(skills): guard search and detail responses against stale writes

Signed-off-by: samzong <samzong.lu@gmail.com>

* fix(skills): reset loading flags on query clear and detail close

Signed-off-by: samzong <samzong.lu@gmail.com>

* fix(gateway): register skills.search/detail in read scope and method list

Add skills.search and skills.detail to the operator READ scope group
and the server methods list. Without this, unclassified methods default
to operator.admin, blocking read-only operator sessions.

Also guard the detail loading reset in the finally block by the active
slug to prevent a transient flash when rapidly switching skills.

Signed-off-by: samzong <samzong.lu@gmail.com>

* fix(skills): guard search loading reset by active query

Signed-off-by: samzong <samzong.lu@gmail.com>

* test: cover ClawHub skills UI flow

* fix: clear stale ClawHub search results

---------

Signed-off-by: samzong <samzong.lu@gmail.com>
Co-authored-by: Frank Yang <frank.ekn@gmail.com>
2026-04-03 19:30:44 +08:00
Peter Steinberger
d39e4dff6a test: make planner lanes explicit 2026-04-03 12:29:29 +01:00
Peter Steinberger
f4393791eb test: split vitest setup for projects 2026-04-03 12:29:29 +01:00
Peter Steinberger
1dd88c6288 fix(sessions): harden session id resolution 2026-04-03 20:29:20 +09:00
Peter Steinberger
1337be3063 refactor: narrow telegram native command test seams 2026-04-03 12:25:47 +01:00
Peter Steinberger
db6d149f75 test: route telegram plugin tests through extensions 2026-04-03 12:25:47 +01:00
Peter Steinberger
0a2a1ff778 fix(ci): make gateway audit path test platform-safe 2026-04-03 12:22:29 +01:00
Vincent Koc
5021b12ac1 perf(browser): trim invoke-browser test imports 2026-04-03 20:12:40 +09:00
Vincent Koc
045d590542 perf(matrix): isolate probe runtime deps 2026-04-03 20:05:50 +09:00
Vincent Koc
fac89d403b perf(browser): split remote profile tab op tests 2026-04-03 20:03:48 +09:00
Peter Steinberger
d3310f4837 fix(ci): resolve changelog conflict markers 2026-04-03 12:03:22 +01:00
Peter Steinberger
e2e1197fa9 refactor(gateway): clarify local mode guardrails 2026-04-03 20:02:32 +09:00
Peter Steinberger
4e22e75697 test: reduce telegram broad partial mocks 2026-04-03 12:01:10 +01:00
Peter Steinberger
225431665a test: trim telegram media retry import cost 2026-04-03 12:01:10 +01:00
Peter Steinberger
05df7f802b docs: add test cost guardrails 2026-04-03 12:01:10 +01:00
Peter Steinberger
566fc72106 refactor(discord): share proxy resolution helpers 2026-04-03 19:58:23 +09:00
Vincent Koc
53504b3662 fix(agents): suppress profile allowlist warnings 2026-04-03 19:55:05 +09:00
Peter Steinberger
2c7eea8f10 fix(gateway): fail closed on missing mode 2026-04-03 19:50:45 +09:00
Peter Steinberger
a6649201b7 docs: clarify default subagent allowlists 2026-04-03 19:45:05 +09:00
Peter Steinberger
d921784718 fix: support default subagent allowlists (#59944) (thanks @hclsys) 2026-04-03 19:43:17 +09:00
HCL
a57766bad0 fix(agents): fall back to defaults for subagents.allowAgents
resolveAgentConfig().subagents.allowAgents reads only the per-agent
entry, never falling back to agents.defaults.subagents.allowAgents.
Other subagent defaults like runTimeoutSeconds correctly read from
cfg.agents.defaults.subagents — allowAgents was missed.

Root cause: subagent-spawn.ts:463 and agents-list-tool.ts:49 both
use resolveAgentConfig() which returns only per-agent config without
defaults merging. The same pattern is already established at
subagent-spawn.ts:403 for runTimeoutSeconds.

Fix: add cfg.agents.defaults.subagents.allowAgents as fallback when
per-agent entry doesn't specify allowAgents. Both call sites fixed.

Closes #59938

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: HCL <chenglunhu@gmail.com>
2026-04-03 19:42:24 +09:00
Peter Steinberger
50f4bffbb6 fix: unblock Discord land by breaking import cycles (#57465) 2026-04-03 19:38:59 +09:00
Peter Steinberger
32ebaa3757 refactor: share session model resolution helpers 2026-04-03 19:37:56 +09:00
Peter Steinberger
67d87abf7c style: normalize telegram fetch test formatting 2026-04-03 11:37:41 +01:00
Peter Steinberger
bb3ea2137b test: move telegram fetch coverage into extensions 2026-04-03 11:37:41 +01:00
Peter Steinberger
e0c4458a2f test: add lighter extensions vitest setup 2026-04-03 11:37:41 +01:00
Peter Steinberger
52225db134 fix(ci): reuse sdk tool auth error for whatsapp 2026-04-03 11:35:45 +01:00
Peter Steinberger
5400980305 test(plugin-sdk): tighten boundary guardrails 2026-04-03 11:35:37 +01:00
Peter Steinberger
1c26e806ff refactor: simplify gateway startup logs 2026-04-03 11:31:34 +01:00
Peter Steinberger
16ca1f4d74 test: route extension boundary inventory off unit 2026-04-03 11:30:45 +01:00
Peter Steinberger
b406b7d2e4 refactor: extract embedded runner failover helpers 2026-04-03 19:28:39 +09:00
Peter Steinberger
71f8c0344a fix(ci): refresh schema and boundary expectations 2026-04-03 11:26:06 +01:00
Vincent Koc
1a5787137f style(msteams): format split graph message import 2026-04-03 19:23:26 +09:00
Vincent Koc
b53ab34d04 perf(msteams): split graph message tests 2026-04-03 19:23:26 +09:00
Peter Steinberger
a5eb8e08ad test: reroute telegram fetch network policy suite 2026-04-03 11:19:29 +01:00
Vincent Koc
c0a8d07fce test(browser): collapse wrapper suite files 2026-04-03 19:18:49 +09:00
Peter Steinberger
fb0d82ba9f fix(ci): refresh guardrails and config baselines 2026-04-03 11:18:40 +01:00
Peter Steinberger
2766c27b2a refactor(plugin-sdk): genericize web channel runtime seams 2026-04-03 11:17:28 +01:00
Peter Steinberger
182bec5091 test: run boundary inventory suites without global setup 2026-04-03 11:17:00 +01:00
Vincent Koc
d55e580307 perf(acpx): clean up runtime fixtures per test 2026-04-03 19:15:26 +09:00
Vincent Koc
e18611188d test(discord): defer provider runtime mocks 2026-04-03 19:13:10 +09:00
Peter Steinberger
d68840ef40 fix(ci): handle bundled fast-check task 2026-04-03 11:10:50 +01:00
Vincent Koc
e414e51761 perf(nextcloud-talk): split setup test hotspots 2026-04-03 19:09:49 +09:00
Peter Steinberger
80c5764482 refactor(telegram): streamline media runtime options 2026-04-03 19:09:13 +09:00
Peter Steinberger
122e6f0f79 fix(plugins): route runtime imports through sdk facades 2026-04-03 11:07:31 +01:00
Vincent Koc
ddd1c77b49 perf(feishu): narrow hotspot runtime seams 2026-04-03 19:06:49 +09:00
Ayaan Zaidi
1fabc96acc fix: keep device approval scopes aligned (#60208) 2026-04-03 15:34:05 +05:30
Ayaan Zaidi
403e0e6521 fix(pairing): mint tokens for merged device roles 2026-04-03 15:34:05 +05:30
Vincent Koc
61f13173c2 feat(providers): add model request transport overrides (#60200)
* feat(providers): add model request transport overrides

* chore(providers): finalize request override follow-ups

* fix(providers): narrow model request overrides
2026-04-03 19:00:06 +09:00
Peter Steinberger
55e43cbc7f test: isolate bundled plugin coverage from unit 2026-04-03 10:58:44 +01:00
Peter Steinberger
64755c52f2 test: move extension-owned coverage out of core 2026-04-03 10:58:44 +01:00
Vincent Koc
2bfbddb81f perf(browser): remove duplicate heavy test wrappers 2026-04-03 18:57:05 +09:00
Peter Steinberger
355dc7f3a8 fix(msteams): avoid discord approval auth import cycle 2026-04-03 10:55:47 +01:00
Vincent Koc
0657cfbb34 test(feishu): slim bot menu runtime fixtures 2026-04-03 18:54:42 +09:00
Peter Steinberger
6e2b46d666 docs: clarify DM pairing vs group auth 2026-04-03 18:51:51 +09:00
Peter Steinberger
0710cfaa71 chore: ignore local vitest timing artifact 2026-04-03 10:51:26 +01:00
Vincent Koc
294d425ae4 test(feishu): slim comment handler runtime fixtures 2026-04-03 18:49:43 +09:00
Peter Steinberger
86ff57518f fix: keep Discord proxy fallback local (#57465) (thanks @geekhuashan) 2026-04-03 18:49:14 +09:00
geekhuashan
3a4fd62135 test(discord): proxy fetch regression coverage for REST, webhook, and stagger 2026-04-03 18:49:14 +09:00
geekhuashan
c8223606ca fix(discord): proxy Carbon REST, webhook and monitor fetch paths; stagger multi-bot startup 2026-04-03 18:49:14 +09:00
Peter Steinberger
dfb423532b docs(telegram): clarify RFC2544 vs fake-IP SSRF guidance 2026-04-03 18:48:14 +09:00
Vincent Koc
726bfd3434 fix(plugin-sdk): export ssrf policy type 2026-04-03 18:47:31 +09:00
Vincent Koc
1bba19decb perf(msteams): narrow secret and ssrf runtime seams 2026-04-03 18:47:31 +09:00
Vincent Koc
8a58a18a0a test(feishu): slim broadcast runtime fixtures 2026-04-03 18:47:08 +09:00
Peter Steinberger
2ca97a7d48 docs(plugin-sdk): refresh seam cleanup docs 2026-04-03 10:45:11 +01:00
Peter Steinberger
f2d7a825b1 refactor(plugin-sdk): remove channel-specific sdk seams 2026-04-03 10:45:10 +01:00
Peter Steinberger
ad6fdf1e3c test: suppress expected nodes run stderr noise 2026-04-03 10:44:20 +01:00
Peter Steinberger
1a68e55f47 test: stabilize Windows startup fallback daemon tests 2026-04-03 10:43:42 +01:00
Vincent Koc
5e0decd9b5 test(msteams): slim messenger runtime fixtures 2026-04-03 18:42:59 +09:00
Vincent Koc
b55ac9e64d test(msteams): trim attachment test runtime footprint 2026-04-03 18:39:50 +09:00
Vincent Koc
e1093a3177 test(diffs): split render coverage from config tests 2026-04-03 18:39:50 +09:00
Peter Steinberger
4bfa9260ce fix(telegram): add dangerous private-network media opt-in 2026-04-03 18:39:17 +09:00
Peter Steinberger
f29c139a7a test: deduplicate provider discovery contract suite 2026-04-03 18:32:15 +09:00
Peter Steinberger
7bf0496dd8 feat: add qwen3.6-plus to modelstudio catalog 2026-04-03 18:32:14 +09:00
Vincent Koc
ddb7e4cc34 perf(test): add gh run ingestion for memory hotspots (#60187)
* perf(test): add gh run ingestion for memory hotspots

* perf(test): harden gh run hotspot ingestion
2026-04-03 18:30:51 +09:00
Peter Steinberger
87b7bb1d14 fix(agents): harden rate-limit fallback handoff
Co-authored-by: TechFath3r <thetechfath3r@gmail.com>
2026-04-03 18:28:56 +09:00
Vincent Koc
f5c3b409ea Config: separate core/plugin baseline entries (#60162)
* Config: separate core/plugin baseline entries

* Config: split config baseline by kind

* Config: split generated baselines by kind

* chore(build): skip generated baseline shards in local tooling

* chore(build): forbid generated docs in npm pack
2026-04-03 18:26:23 +09:00
Ayaan Zaidi
9e58a0892b fix: align mobile pairing secure endpoint UX (#60128) 2026-04-03 14:51:24 +05:30
Ayaan Zaidi
b9897eec7c fix: align mobile pairing secure endpoint UX (#60128) 2026-04-03 14:51:24 +05:30
Ayaan Zaidi
fadd627467 fix: align mobile pairing secure endpoint UX (#60128) 2026-04-03 14:51:24 +05:30
Ayaan Zaidi
a1d07796fc fix(pairing): honor operator scopes for mixed bootstrap approvals 2026-04-03 14:51:24 +05:30
Ayaan Zaidi
a2b53522eb fix(pairing): allow private lan mobile ws 2026-04-03 14:51:24 +05:30
Ayaan Zaidi
84add47525 fix(pairing): allow emulator ws setup urls 2026-04-03 14:51:24 +05:30
Ayaan Zaidi
acd5734aa9 fix(pairing): align mobile setup with secure endpoints 2026-04-03 14:51:24 +05:30
Vincent Koc
c6f95a0c37 test: summarize diagnostic report memory growth 2026-04-03 18:19:47 +09:00
@zimeg
f9785c63e7 docs(slack): add groups:history scope to app manifest 2026-04-03 02:15:53 -07:00
samzong
61fef8c689 [Fix] Isolate teardown steps so session lock release is unconditional (#59194)
Merged via squash.

Prepared head SHA: 52b3bb46bb
Co-authored-by: samzong <13782141+samzong@users.noreply.github.com>
Co-authored-by: frankekn <4488090+frankekn@users.noreply.github.com>
Reviewed-by: @frankekn
2026-04-03 17:07:24 +08:00
Vincent Koc
44f1887f25 test(diffs): dispose highlighter after each render 2026-04-03 17:49:37 +09:00
Vincent Koc
9771d84c56 test: add report-on-signal diagnostics 2026-04-03 17:47:08 +09:00
Vincent Koc
97c542a67b test(memory-lancedb): avoid repeated dynamic imports 2026-04-03 17:47:08 +09:00
Vincent Koc
de2d4ecd9e test(nextcloud-talk): avoid per-test module resets 2026-04-03 17:47:08 +09:00
Vincent Koc
cb7f74b5eb perf(test): refresh extension memory hotspots from gh logs (#60159) 2026-04-03 17:43:44 +09:00
Vincent Koc
84970d325e docs(changelog): add missing media transport PR number 2026-04-03 17:41:48 +09:00
Vincent Koc
23719dd513 feat(media): add request transport overrides (#59848)
* style(providers): normalize request policy formatting

* style(providers): normalize request policy formatting

* feat(media): add request transport overrides

* fix(secrets): resolve media request secret refs

* fix(secrets): cover shared media request refs

* fix(secrets): scope media request ref activity

* fix(media): align request ref gating
2026-04-03 17:35:26 +09:00
Peter Steinberger
9e47c1a2c5 test: avoid windows task-owner tempdir hangs 2026-04-03 09:26:04 +01:00
Peter Steinberger
1849cf71c2 fix(image): skip inferred resolution for openai edits 2026-04-03 09:20:08 +01:00
@zimeg
dc45faaf4e docs(slack): order recommended scopes and events 2026-04-03 01:10:42 -07:00
@zimeg
6f23e513cc docs(slack): match recommended bot scopes in onboarding 2026-04-03 01:01:25 -07:00
Josh Lehman
2b28e75822 fix: enrich session_end lifecycle hooks (#59715)
Merged via squash.

Prepared head SHA: b3ef62b973
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-04-03 00:16:14 -07:00
hengm3467
52d8dc5b56 feat: add bundled StepFun provider plugin (#60032)
Co-authored-by: George Zhang <georgezhangtj97@gmail.com>
2026-04-02 23:53:50 -07:00
Peter Steinberger
a064da3bef docs(changelog): align 2026.4.x release notes 2026-04-03 15:23:22 +09:00
Peter Steinberger
051b5ddafe test: dedupe core test teardown paths 2026-04-03 07:14:58 +01:00
Peter Steinberger
b66197f932 test: reduce import wrapper reload churn 2026-04-03 07:14:58 +01:00
Peter Steinberger
9bba2ec0ad test: trim extension teardown churn 2026-04-03 07:14:58 +01:00
Peter Steinberger
376a042ba1 test: isolate runtime state in memory tests 2026-04-03 07:14:58 +01:00
Marcus Castro
d2d9a928b1 WhatsApp: honor block streaming config (#60069) 2026-04-03 03:12:37 -03:00
Brad Groux
dda53a2ff8 test: update gateway.mode test to match default-to-local behavior (#54801) (#60094)
The test previously asserted that a valid snapshot without gateway.mode
blocks startup. After defaulting gateway.mode to 'local' when unset,
the gateway should start successfully in this scenario — update the
test to verify the new expected behavior.

Co-authored-by: Brad Groux <bradgroux@users.noreply.github.com>
2026-04-03 00:59:51 -05:00
Brad Groux
9978d2276b fix: default gateway.mode to 'local' when unset (#54801) (#60085)
After v2026.3.24 introduced a gateway.mode guard, startup fails on
Windows (and other platforms) when the config file exists but doesn't
contain an explicit gateway.mode value. This happens after 'openclaw
onboard' writes a minimal config without gateway settings.

Default to 'local' when the mode is unset, restoring pre-3.24 behavior
where the gateway started without requiring an explicit mode.

Fixes #54801

Co-authored-by: Brad Groux <bradgroux@users.noreply.github.com>
2026-04-03 00:23:06 -05:00
Brad Groux
6e94b047e2 fix: improve WS handshake reliability on slow-startup environments (#60075)
* fix: import CHANNEL_IDS from leaf module to avoid TDZ on init (#48832)

schema.ts and validation.ts imported CHANNEL_IDS from channels/registry.js,
which re-exports from channels/ids.js but also imports plugins/runtime.js.
When the bundler resolves this dependency graph, the re-exported CHANNEL_IDS
can be undefined at the point config/validation.ts evaluates (temporal dead
zone), causing 'CHANNEL_IDS is not iterable' on startup.

Fix: import CHANNEL_IDS directly from channels/ids.js (the leaf module with
zero heavy dependencies) and normalizeChatChannelId from channels/chat-meta.js.

Fixes #48832

* fix: improve WS handshake reliability on slow-startup environments (#48736)

On Windows with large dist bundles (46MB/639 files), heavy synchronous
module loading blocks the event loop during CLI startup, preventing
timely processing of the connect.challenge frame and causing ~80%
handshake timeout failures.

Changes:
- Yield event loop (setImmediate) before starting WS connection in
  callGateway to let pending I/O drain after heavy module loading
- Add OPENCLAW_CONNECT_CHALLENGE_TIMEOUT_MS env var override for
  client-side connect challenge timeout (server already has
  OPENCLAW_HANDSHAKE_TIMEOUT_MS)
- Include diagnostic timing in challenge timeout error messages
  (elapsed vs limit) for easier debugging
- Add tests for env var override and resolution logic

---------

Co-authored-by: Brad Groux <bradgroux@users.noreply.github.com>
2026-04-03 00:21:14 -05:00
Brad Groux
0aa98a8e3b fix: import CHANNEL_IDS from leaf module to avoid TDZ on init (#48832) (#60061)
schema.ts and validation.ts imported CHANNEL_IDS from channels/registry.js,
which re-exports from channels/ids.js but also imports plugins/runtime.js.
When the bundler resolves this dependency graph, the re-exported CHANNEL_IDS
can be undefined at the point config/validation.ts evaluates (temporal dead
zone), causing 'CHANNEL_IDS is not iterable' on startup.

Fix: import CHANNEL_IDS directly from channels/ids.js (the leaf module with
zero heavy dependencies) and normalizeChatChannelId from channels/chat-meta.js.

Fixes #48832

Co-authored-by: Brad Groux <bradgroux@users.noreply.github.com>
2026-04-03 00:20:17 -05:00
samzong
2b9981bd06 [Fix] Remove spurious plugin id mismatch warning (#59185)
Merged via squash.

Prepared head SHA: 82859a0f92
Co-authored-by: samzong <13782141+samzong@users.noreply.github.com>
Co-authored-by: frankekn <4488090+frankekn@users.noreply.github.com>
Reviewed-by: @frankekn
2026-04-03 12:58:56 +08:00
Ayaan Zaidi
251fa72798 fix: emit passive hooks for mention-skipped group messages (#60018)
* fix(channels): emit passive hooks for mention-skipped group messages

* fix(channels): honor signal ingest overrides

* fix(channels): honor telegram ingest fallback

* fix: emit passive hooks for mention-skipped group messages (#60018)
2026-04-03 10:14:48 +05:30
Vincent Koc
9b80344e58 docs: rewrite automation decision guide with current nomenclature 2026-04-03 13:26:32 +09:00
Vincent Koc
a2b6fdc3df docs: rebuild automation section for coherence and readability 2026-04-03 13:11:23 +09:00
Ayaan Zaidi
d5ea5f27ac fix: parse kimi tagged tool calls (#60051)
* fix: parse kimi tagged tool calls

* fix: parse kimi tagged tool calls (#60051)

* fix: parse kimi tagged tool calls (#60051)
2026-04-03 09:39:19 +05:30
Vincent Koc
98137f7f80 perf(test): isolate memory-heavy extension hotspots (#59879)
* perf(test): isolate memory-heavy extension hotspots

* fix(test): pin extension memory hotspot threshold
2026-04-03 13:01:09 +09:00
Peter Steinberger
e3674bcc04 test: streamline runtime wrapper test reloads 2026-04-03 04:41:38 +01:00
Peter Steinberger
ffd34f8896 test: reduce agent test import churn 2026-04-03 04:41:09 +01:00
Peter Steinberger
847faa3d04 test: trim extension test import churn 2026-04-03 04:41:08 +01:00
Vincent Koc
2f013b68f8 docs: add missing changelog entries and update context visibility security docs 2026-04-03 12:39:45 +09:00
v1p0r
3a7555a27b "fix(telegram): surface media placeholder and file_id when download f… (#59948)
* "fix(telegram): surface media placeholder and file_id when download fails"

* fix: unify telegram media placeholder selection

* fix: preserve telegram media context on captioned download failures (#59948) (thanks @v1p0r)

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-04-03 09:08:05 +05:30
samzong
0e0ad692b5 fix: persist Telegram reaction ownership across restart (#59207) (thanks @samzong) 2026-04-03 08:57:06 +05:30
Ayaan Zaidi
a5d6e519eb fix: preserve explicit Telegram topic targets on replies (#59634) (thanks @dashhuang) 2026-04-03 08:50:02 +05:30
Cindy
c001c090b9 fix(telegram): keep topic thread when replying with message tool 2026-04-03 08:50:02 +05:30
Ted-developer
dd080b6fb0 fix(msteams): download DM inline images via Graph API (#52212)
Fix three bugs preventing inline image downloads in Teams 1:1 DM chats: wrong conversation ID format for Graph API, missing media URL extraction, and incorrect content type detection.

Fixes #24797

Thanks @Ted-developer
2026-04-02 22:14:02 -05:00
saram ali
985533efbc fix: cover buffered Telegram apiRoot downloads (#59544) (thanks @SARAMALI15792)
* test(telegram): add URL construction tests for custom apiRoot

Add comprehensive test cases to verify that file download URLs are correctly
constructed when using a custom apiRoot configuration for local Bot API servers.

Tests validate:
- Document downloads use the custom apiRoot in the constructed URL
- Sticker downloads use the custom apiRoot in the constructed URL
- SSRF policy correctly includes the custom hostname

This ensures issue #59512 (Telegram file downloads with local Bot API) is
properly covered by regression tests.

* refactor(telegram): improve media resolution code quality

Apply KISS and YAGNI principles to reduce code duplication and improve maintainability:

1. Extract media metadata resolution
   - Consolidate resolveMediaFileRef(), resolveTelegramFileName(), and
     resolveTelegramMimeType() into single resolveMediaMetadata() function
   - Returns typed MediaMetadata object with fileRef, fileName, mimeType
   - Reduces duplication and improves readability

2. Add logging for apiRoot parsing failures
   - Log when custom apiRoot URL parsing fails in buildTelegramMediaSsrfPolicy()
   - Helps debug configuration issues with local Bot API servers

3. Fix missing apiRoot in buffered messages
   - Add telegramCfg.apiRoot parameter to resolveMedia() calls in
     bot-handlers.buffers.ts (lines 150-159, 189)
   - Ensures reply media in buffered contexts respects custom apiRoot config
   - Fixes inconsistency where runtime handler passed apiRoot but buffers didn't

These changes improve code quality while maintaining backward compatibility and
ensuring issue #59512 (Telegram file downloads with local Bot API) works correctly
in all contexts.

* fix(telegram): resolve bot review issues

Address critical issues identified by Greptile code review:

1. Define telegramCfg in bot-handlers.buffers.ts
   - Extract telegramCfg from cfg.channels?.telegram
   - Fixes ReferenceError when accessing telegramCfg.apiRoot
   - Ensures buffered message handlers can access apiRoot configuration

2. Restore type safety for MediaMetadata.fileRef
   - Change from 'unknown' to proper union type
   - Preserves type information for downstream file_id access
   - Prevents TypeScript strict mode compilation errors

These fixes ensure the PR compiles correctly and handles buffered
media downloads with custom apiRoot configuration.

* fix(telegram): use optional chaining for telegramCfg.apiRoot

TypeScript strict mode requires optional chaining when accessing
properties on potentially undefined objects. Changed telegramCfg.apiRoot
to telegramCfg?.apiRoot to handle cases where telegramCfg is undefined.

Fixes TypeScript errors:
- TS18048: 'telegramCfg' is possibly 'undefined' (line 160)
- TS18048: 'telegramCfg' is possibly 'undefined' (line 191)

* fix(telegram): add missing optional chaining on line 191

Complete the fix for telegramCfg optional chaining.
Previous commit only fixed line 160, but line 191 also needs
the same fix to prevent TS18048 error.

* fix: cover buffered Telegram apiRoot downloads (#59544) (thanks @SARAMALI15792)

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-04-03 08:41:41 +05:30
Derek YU
5f6e3499f3 fix: detect PID recycling in gateway lock on Windows/macOS + startup progress (#59843)
Fix stale lock files from crashed gateway processes blocking new invocations on Windows/macOS. Detect PID recycling to avoid false positive lock conflicts, and add startup progress indicator.

Thanks @TonyDerek-dot
2026-04-02 22:07:35 -05:00
Neerav Makwana
7c7098fd1d fix: keep inbound images readable on upgraded installs (#59971) (thanks @neeravmakwana)
* fix(telegram): allow inbound media from config dir

Made-with: Cursor

* test: simplify local roots regression

* fix: keep inbound images readable on upgraded installs (#59971) (thanks @neeravmakwana)

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-04-03 08:24:29 +05:30
Gustavo Madeira Santana
0fdb55c4e4 Docs: refresh diffs plugin docs 2026-04-02 21:47:55 -04:00
Gustavo Madeira Santana
ebc9784f26 docs: fix Matrix plugin docs 2026-04-02 21:47:34 -04:00
Gustavo Madeira Santana
78a842d055 fix(matrix): align IDB snapshot lock timing 2026-04-02 21:44:08 -04:00
Gustavo Madeira Santana
1efa923ab8 Matrix: add native exec approvals (#58635)
Merged via squash.

Prepared head SHA: d9f048e827
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-02 21:08:54 -04:00
Gustavo Madeira Santana
9e2cbf9a30 chore: tighten pr push script 2026-04-02 20:56:51 -04:00
Alejandro Martinez
3a91a4f8d4 fix(matrix): add advisory file locking to IDB crypto persistence (#59851)
Merged via squash.

Prepared head SHA: 392e411ffd
Co-authored-by: al3mart <11448715+al3mart@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
2026-04-02 20:19:40 -04:00
Alejandro Martinez
b894ca6702 fix(matrix): allow secret storage recreation during repair bootstrap (#59846)
Merged via squash.

Prepared head SHA: 06b10f61eb
Co-authored-by: al3mart <11448715+al3mart@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
2026-04-02 20:11:58 -04:00
Hyojin Kwak
739ed1bf29 fix(msteams): preserve channel reply threading in proactive fallback (#55198)
When a thread reply's turn context is revoked and falls back to proactive messaging, the normalized conversation ID lost the thread suffix, causing replies to land in the channel root instead of the original thread.

Reconstructs the threaded conversation ID (`;messageid=<activityId>`) for channel conversations in the proactive fallback path, while correctly leaving group chat conversations flat.

Fixes #27189

Thanks @hyojin
2026-04-02 18:27:13 -05:00
Josh Lehman
ed8d5b3797 fix: add Telegram native progress placeholder opt-in for plugin commands (#59300)
Merged via squash.

Prepared head SHA: 4f5bc22a89
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-04-02 15:55:46 -07:00
Bruce MacDonald
5f4077cc7d fix(ollama): prefer real cloud auth over local marker 2026-04-02 15:51:57 -07:00
Peter Steinberger
64581c655d test: make plugin activation boundary env agnostic 2026-04-03 05:02:58 +09:00
Peter Steinberger
bff6025bde test: refresh generated baselines 2026-04-03 04:54:59 +09:00
Peter Steinberger
15b005489d chore: bump version to 2026.4.3 2026-04-03 04:54:59 +09:00
Peter Steinberger
49f67f54f4 docs: fix task flow release note command 2026-04-03 04:39:13 +09:00
Peter Steinberger
6f67347e00 ci: restore npm token auth for dist-tag promotion 2026-04-02 20:37:49 +01:00
Peter Steinberger
04cf29f613 refactor: speed up whatsapp inbound dispatch tests 2026-04-03 04:34:58 +09:00
Peter Steinberger
694d12a90b refactor: apply context visibility across channels 2026-04-03 04:34:57 +09:00
Peter Steinberger
35e1605147 feat: add configurable context visibility 2026-04-03 04:34:57 +09:00
Peter Steinberger
d4d2d9e479 ci: move npm promotion into trusted workflow 2026-04-02 20:29:57 +01:00
Peter Steinberger
658f0c5d2d ci: use oidc token for npm promotion 2026-04-02 20:23:56 +01:00
Peter Steinberger
dbfb13b93a build: update appcast for 2026.4.2 2026-04-02 20:08:40 +01:00
Vincent Koc
883df8c6a8 fix(plugins): reuse runtime registries for web provider snapshots (#59865)
* fix(plugins): reuse runtime registries for web providers

* test(plugins): clarify runtime reuse intent

* chore(changelog): note web provider runtime reuse
2026-04-03 04:07:43 +09:00
Agustin Rivera
193fdd6e3b fix(policy): preserve restrictive tool allowlists (#58476)
* fix(policy): preserve restrictive tool allowlists

Co-authored-by: David Silva <david.silva@gendigital.com>

* fix(policy): address review follow-ups

* fix(policy): restore additive alsoAllow semantics

* fix(policy): preserve optional tool opt-ins for allow-all configs

* fix(policy): narrow plugin-only allowlist warnings

* fix(policy): add changelog entry

* Revert "fix(policy): add changelog entry"

This reverts commit 4a996bf4ca.

* chore: add changelog for restrictive tool allowlists

---------

Co-authored-by: David Silva <david.silva@gendigital.com>
Co-authored-by: Devin Robison <drobison@nvidia.com>
2026-04-02 12:55:36 -06:00
Peter Steinberger
9f85595d80 fix: pin anthropic sdk to patched version 2026-04-02 19:50:05 +01:00
Vincent Koc
d34bca3ce6 fix(plugins): reuse runtime registry for provider resolution (#59856)
* fix(plugins): reuse runtime registry for provider resolution

* test(plugins): align provider runtime helper names
2026-04-03 03:40:24 +09:00
Peter Steinberger
be4be5e783 fix: improve parallels smoke progress 2026-04-02 19:39:23 +01:00
Agustin Rivera
d631326c5e fix(tailscale): gate test binary override (#58468)
* fix(tailscale): gate test binary override

* fix(changelog): note tailscale override hardening

* fix(changelog): drop tailscale note from pr

* chore: add changelog for tailscale test binary gating

---------

Co-authored-by: Devin Robison <drobison@nvidia.com>
2026-04-02 12:39:10 -06:00
Vincent Koc
f32a5b30db chore(skills): align taskflow skill with runtime 2026-04-03 03:38:02 +09:00
Peter Steinberger
d74a12264a fix: mirror bedrock runtime dep in root package 2026-04-02 19:26:56 +01:00
Vincent Koc
f911bbc353 refactor(plugins): separate activation from enablement (#59844)
* refactor(plugins): separate activation from enablement

* fix(cli): sanitize verbose plugin activation reasons
2026-04-03 03:22:37 +09:00
Vincent Koc
4aeb0255f3 docs: rename TaskFlow to Task Flow in prose 2026-04-03 03:22:01 +09:00
Vincent Koc
d9c662dc69 docs: restructure automation section as Automation & Tasks 2026-04-03 03:16:51 +09:00
Peter Steinberger
3bd2bbea34 docs: clarify npm release workflow inputs 2026-04-02 19:11:01 +01:00
Peter Steinberger
0ebb69b882 build: set release version to 2026.4.2 2026-04-02 19:09:58 +01:00
Peter Steinberger
38bd525888 test: align strict inline-eval awk denial expectation 2026-04-02 19:09:39 +01:00
Peter Steinberger
209535b7c7 build: make npm release tag configurable 2026-04-02 19:06:37 +01:00
Vincent Koc
bcd61e54e1 docs: fix TaskFlow CLI command path and CLI task notify policy 2026-04-03 03:03:00 +09:00
Peter Steinberger
9f3a26caa6 fix(discord): quiet Carbon reconcile log 2026-04-02 18:55:34 +01:00
Agustin Rivera
49d08382a9 iOS: restrict A2UI action dispatch to trusted canvas URLs (#58471)
* fix(ios): restrict a2ui bridge trust

* test(ios): cover fragment-strip trust and document raw-string equality

* fix(ios): normalize capability URL before trust comparison in canvas commands

* fix(ios): trim canvas.navigate url before trust comparison

* chore: add changelog for iOS A2UI trust boundary

---------

Co-authored-by: Devin Robison <drobison@nvidia.com>
2026-04-02 11:51:09 -06:00
Peter Steinberger
00aa31a30c docs(changelog): remove duplicate entries 2026-04-02 18:48:27 +01:00
Vincent Koc
7aa22959e4 refactor(tasks): rename registry hooks to observers (#59829) 2026-04-03 02:42:59 +09:00
Agustin Rivera
676b748056 Limit connect snapshot metadata to admin-scoped clients (#58469)
* fix(gateway): gate connect snapshot metadata by scope

* fix(gateway): clarify connect snapshot trust boundary

* fix(gateway): note connect snapshot change in changelog

* fix(gateway): remove changelog changes from PR

* chore: add changelog for scoped gateway snapshot metadata

---------

Co-authored-by: Devin Robison <drobison@nvidia.com>
2026-04-02 11:41:47 -06:00
Peter Steinberger
a4a372825e docs(changelog): reorder unreleased fixes 2026-04-02 18:39:31 +01:00
Peter Steinberger
45c8207ef2 fix(exec): clarify auto routing semantics (#58897) (thanks @vincentkoc) 2026-04-03 02:37:12 +09:00
Vincent Koc
938541999e Delete docs/internal/codex/2026-03-29-exec-target-override-fix.md 2026-04-03 02:37:12 +09:00
Vincent Koc
5dca81271c fix(exec): clarify and cover auto host override guard 2026-04-03 02:37:12 +09:00
Vincent Koc
dae6632da1 Security: block exec host overrides under auto target 2026-04-03 02:37:12 +09:00
Agustin Rivera
5874a387ae fix(windows): reject unresolved cmd wrappers (#58436)
* fix(windows): reject unresolved cmd wrappers

* fix(windows): add wrapper policy coverage

* fix(windows): document wrapper fallback migration

* fix(windows): drop changelog entry from pr

* chore: add changelog for Windows wrapper fail-closed behavior

---------

Co-authored-by: Devin Robison <drobison@nvidia.com>
Co-authored-by: Devin Robison <drobison00@users.noreply.github.com>
2026-04-02 11:35:50 -06:00
Peter Steinberger
3e452f2671 fix: preserve strict inline-eval approval boundaries (#59780) (thanks @luoyanglang) 2026-04-02 18:30:29 +01:00
Peter Steinberger
f03d7c5a4c refactor: centralize Windows exec invocation 2026-04-02 18:27:53 +01:00
Peter Steinberger
d56415e353 fix(openai): support reference-image edits 2026-04-03 02:26:33 +09:00
luoyanglang
f0a4bbba33 test(tasks): close flow registry before temp-dir cleanup 2026-04-03 02:25:48 +09:00
luoyanglang
68d8e15a2e fix(exec): satisfy allowlist predicate type checks 2026-04-03 02:25:48 +09:00
luoyanglang
7c83cae425 fix(exec): keep strict inline-eval interpreter approvals reusable 2026-04-03 02:25:48 +09:00
Agustin Rivera
a941a4fef9 fix(android): require TLS for remote gateway endpoints (#58475)
* fix(android): require tls for remote gateway endpoints

* fix(android): expand loopback gateway coverage

* fix(android): validate scanned gateway endpoints

* fix(android): handle mapped loopback literals

* fix(android): allow emulator bridge host

* fix(changelog): note android gateway tls hardening

* fix(android): preserve first-time tls trust prompts

* fix(changelog): drop android gateway entry from pr

* fix(android): scope emulator bridge tls bypass

* fix(android): normalize ipv6 gateway hosts

* fix(android): preserve ipv6 gateway url brackets

* fix(android): preserve auth across tls trust prompt

* fix(android): normalize bracketed ipv6 gateway hosts

* chore: add changelog for Android remote gateway TLS

---------

Co-authored-by: Devin Robison <drobison@nvidia.com>
Co-authored-by: Devin Robison <drobison00@users.noreply.github.com>
2026-04-02 11:23:51 -06:00
Peter Steinberger
2ea0ca08f6 test: add cross-provider approval availability coverage (#59776) (thanks @joelnishanth) 2026-04-03 02:21:17 +09:00
joelnishanth
d5865bbcc2 fix: decouple approval availability from native delivery enablement (#59620)
getActionAvailabilityState in createApproverRestrictedNativeApprovalAdapter
was gating on both hasApprovers AND isNativeDeliveryEnabled, causing
Telegram exec approvals to report "not allowed" when
channels.telegram.execApprovals.target was configured but
execApprovals.enabled was not explicitly true. The availability check
should only depend on whether approvers exist; native delivery mode is
a routing concern handled downstream.
2026-04-03 02:21:17 +09:00
Peter Steinberger
9b48a4d90a docs: fix changelog conflict markers (#59466) 2026-04-03 02:19:32 +09:00
Peter Steinberger
bacc938c2a docs: note windows exec landing (#59466) (thanks @lawrence3699) 2026-04-03 02:19:32 +09:00
lawrence3699
2fd7f7ca52 fix(exec): hide windows console windows 2026-04-03 02:19:32 +09:00
pgondhi987
7eb094a00d fix(infra): align env key normalization in approval binding path (#59182)
* fix: address issue

* fix: address PR review feedback

* fix: address review feedback

* fix: address review feedback

* chore: add changelog for Windows env approval binding

---------

Co-authored-by: Devin Robison <drobison@nvidia.com>
2026-04-02 11:14:33 -06:00
Vincent Koc
774beb8e5c refactor(plugin-sdk): add task domain runtime surfaces (#59805)
* refactor(plugin-sdk): add task domain runtime views

* chore(plugin-sdk): refresh api baseline

* fix(plugin-sdk): preserve task runtime owner isolation
2026-04-03 02:11:21 +09:00
Peter Steinberger
f30b4bc717 fix: remove leaked changelog conflict marker 2026-04-02 18:07:39 +01:00
Peter Steinberger
fc76f667c2 test: isolate task flow link validation stores 2026-04-03 02:04:26 +09:00
Peter Steinberger
a406045f2f test: accept Windows exec approval denial path 2026-04-03 02:04:26 +09:00
Peter Steinberger
247a06813e fix: avoid gateway cwd for node exec (#58977) (thanks @Starhappysh) 2026-04-03 02:04:26 +09:00
jianxing zhang
50b270a86b fix: widen HostExecApprovalParams.cwd to string | undefined
Remote node exec may have no explicit cwd when the gateway's own
process.cwd() is omitted. Allow undefined to flow through the
approval request type.
2026-04-03 02:04:26 +09:00
jianxing zhang
302c6e30bb fix: resolve type errors where workdir (string | undefined) flows to string-only params
After the node early-return, narrow workdir back to string via
resolvedWorkdir for gateway/sandbox paths. Update
buildExecApprovalPendingToolResult and buildApprovalPendingMessage
to accept string | undefined for cwd since node execution may omit it.
2026-04-03 02:04:26 +09:00
jianxing zhang
3b3191ab3a fix(exec): skip gateway cwd injection for remote node host
When exec runs with host=node and no explicit cwd is provided, the
gateway was injecting its own process.cwd() as the default working
directory. In cross-platform setups (e.g. Linux gateway + Windows node),
this gateway-local path does not exist on the node, causing
"SYSTEM_RUN_DENIED: approval requires an existing canonical cwd".

This change detects when no explicit workdir was provided (neither via
the tool call params.workdir nor via agent defaults.cwd) and passes
undefined instead of the gateway cwd. This lets the remote node use its
own default working directory.

Changes:
- bash-tools.exec.ts: Track whether workdir was explicitly provided;
  when host=node and no explicit workdir, pass undefined instead of
  gateway process.cwd()
- bash-tools.exec-host-node.ts: Accept workdir as string | undefined;
  only send cwd to system.run.prepare when defined
- bash-tools.exec-approval-request.ts: Accept workdir as
  string | undefined in HostExecApprovalParams

Fixes #58934

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 02:04:26 +09:00
pgondhi987
8aceaf5d0f fix(security): close fail-open bypass in exec script preflight [AI] (#59398)
* fix: address issue

* fix: finalize issue changes

* fix: address PR review feedback

* fix: address PR review feedback

* fix: address PR review feedback

* fix: address PR review feedback

* fix: address PR review feedback

* fix: address PR review feedback

* fix: address review feedback

* fix: address PR review feedback

* fix: address PR review feedback

* fix: address review-pr skill feedback

* fix: address PR review feedback

* fix: address review-pr skill feedback

* fix: address PR review feedback

* fix: address PR review feedback

* fix: address PR review feedback

* fix: address PR review feedback

* fix: address PR review feedback

* fix: address review-pr skill feedback

* fix: address PR review feedback

* fix: address PR review feedback

* fix: address review-pr skill feedback

* fix: address PR review feedback

* fix: address PR review feedback

* fix: address PR review feedback

* fix: address PR review feedback

* fix: address review-pr skill feedback

* fix: address PR review feedback

* fix: address PR review feedback

* fix: address PR review feedback

* chore: add changelog for exec preflight fail-closed hardening

---------

Co-authored-by: Devin Robison <drobison@nvidia.com>
2026-04-02 11:00:39 -06:00
Peter Steinberger
e36c563775 refactor(exec): dedupe executable candidate resolution 2026-04-03 01:58:37 +09:00
SudheerDev-AIML
48279dca84 UI: apply accent color to Settings page header and content headings
Fixes #52576 — the accent/theme color was not applied to the Settings
page title, breadcrumb, section headings, or theme card labels. Changed
four CSS rules from var(--text-strong) to var(--accent) so they reflect
the selected theme consistently.
2026-04-02 11:57:09 -05:00
Vincent Koc
990545181b fix(ci): preserve strict inline-eval denial after durable awk trust 2026-04-03 01:55:01 +09:00
Peter Steinberger
2170d36171 docs(changelog): add Windows drive-less exec fix note (#58040) (thanks @SnowSky1) 2026-04-03 01:53:25 +09:00
SnowSky1
e6ce31eb54 fix(exec): ignore malformed drive-less windows exec paths 2026-04-03 01:53:25 +09:00
Agustin Rivera
a26f4d0f3e Separate Gemini OAuth state from PKCE verifier (#59116)
* fix(google): separate oauth state from pkce verifier

* fix(google): drop unused oauth callback state arg

* docs(changelog): add #59116 google oauth state fix

---------

Co-authored-by: Jacob Tomlinson <jtomlinson@nvidia.com>
2026-04-02 09:51:11 -07:00
Vincent Koc
367969759c perf(memory): trim matrix host validation imports 2026-04-03 01:48:09 +09:00
Vincent Koc
47f5d72931 chore(checks): serialize local heavy gates 2026-04-03 01:46:28 +09:00
Devin Robison
96b55821bc fix: share ACP owner-only approval classes (#201) (#59255)
Co-authored-by: OpenClaw Dummy Agent <octriage-dummy@example.invalid>
2026-04-02 10:45:41 -06:00
Jacob Tomlinson
176c059b05 node-host: bind pnpm dlx approval scripts (#58374)
* node-host: bind pnpm dlx approval scripts

* node-host: cover pnpm dlx package alias

* node-host: cover pnpm dlx flag forms

* node-host: fail closed on unsafe pnpm dlx flags

* node-host: narrow pnpm dlx fail-closed guard

* node-host: scan pnpm dlx past global --

* node-host: allow pnpm dlx file args

* node-host: allow pnpm dlx data args

* node-host: fail closed on unknown pnpm dlx flags

* node-host: support pnpm workspace-root flag

* node-host: restrict pnpm dlx tail scan

* node-host: support pnpm parallel flag

* changelog: node-host pnpm dlx approval binding (#58374)
2026-04-02 09:41:28 -07:00
pgondhi987
7cea7c2970 fix(zalo): scope replay dedupe cache key to path and account [AI] (#59387)
* fix: address issue #139

* changelog: add zalo replay dedupe fix entry

---------

Co-authored-by: Jacob Tomlinson <jtomlinson@nvidia.com>
2026-04-02 09:36:35 -07:00
Peter Steinberger
d5b6bfc48c test(discord): align native approval fixture with auto mode 2026-04-02 17:33:35 +01:00
Vincent Koc
e4818a345e test(tasks): close flow registry before temp dir cleanup 2026-04-03 01:32:05 +09:00
Peter Steinberger
bf1fcf2e5f docs(approvals): clarify auto native approval routing 2026-04-02 17:31:02 +01:00
Peter Steinberger
17f6626ffe feat(approvals): auto-enable native chat approvals 2026-04-02 17:30:40 +01:00
Peter Steinberger
721cab2b8d refactor(exec): split allowlist segment evaluation helpers 2026-04-03 01:22:25 +09:00
Peter Steinberger
812a7636fb refactor: simplify exec approval followup delivery 2026-04-02 17:19:42 +01:00
Peter Steinberger
47dcfc49b8 fix: scope #57584 to shell allowlist changes 2026-04-03 01:11:20 +09:00
Ayaan Zaidi
34a5c47351 fix: preserve Android assistant auto-send queue 2026-04-02 21:39:24 +05:30
pgondhi987
462b4020bc fix(browser): block SSRF redirect bypass via real-time route interception (#58771)
Install a Playwright route handler before `page.goto()` so navigations
to private/internal IPs are intercepted and aborted mid-redirect instead
of being checked post-hoc after the request already reached the internal
host. Blocked targets are permanently marked and rejected for subsequent
tool calls.

Thanks @pgondhi987
2026-04-02 09:07:57 -07:00
biao
8d81e76f23 fix: evaluate shell wrapper inline commands against allowlist (#57377) (#57584)
When a skill constructs a compound command via a shell wrapper
(e.g. `sh -c "cat SKILL.md && gog-wrapper calendar events"`),
the allowlist check was comparing `/bin/sh` instead of the actual
target binaries, causing the entire command to be silently rejected.

This adds recursive inline command evaluation that:
- Detects chain operators (&&, ||, ;) in the -c payload
- Parses each sub-command independently via analyzeShellCommand
- Evaluates every sub-command against the allowlist
- Preserves per-sub-command segmentSatisfiedBy for accurate tracking
- Limits recursion depth to 3 to prevent abuse
- Skips recursion on Windows (no POSIX shell semantics)

Closes #57377

Co-authored-by: WZBbiao <wangzhenbiao326@gmail.com>
2026-04-03 01:06:40 +09:00
Peter Steinberger
578a0ed31a refactor(agent): dedupe tool error summary 2026-04-02 17:05:05 +01:00
Ayaan Zaidi
59bdf870b9 fix: add Android assistant auto-send changelog (#59721) 2026-04-02 21:27:14 +05:30
Ayaan Zaidi
5d524617e1 fix: clear stale Android assistant auto-send queue 2026-04-02 21:27:14 +05:30
Ayaan Zaidi
186647cb74 feat: auto-send Android assistant prompts 2026-04-02 21:27:14 +05:30
seonang
4207ca2eb8 Fix Telegram exec approval delivery and auto-resume fallback 2026-04-03 00:56:54 +09:00
Gustavo Madeira Santana
b5161042b7 Diffs: validate viewerBaseUrl in manifest schema
Reject invalid diffs viewerBaseUrl values during manifest config validation,
not later during plugin registration.

Keep runtime normalization intact and add manifest-level coverage so bad
protocols and query/hash values fail fast.
2026-04-02 11:55:05 -04:00
Priyansh Gupta
77e636cf78 fix(agents): include received keys in missing-param error for write tool (#55317)
Merged via squash.

Prepared head SHA: c1cf0691c9
Co-authored-by: priyansh19 <33621094+priyansh19@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-04-02 08:54:28 -07:00
Peter Steinberger
c0b6531ec7 docs: add changelog for cron exec timeout fix (#58247) (thanks @skainguyen1412) 2026-04-03 00:43:42 +09:00
spaceman1412
3b6825ab93 Cron: honor trigger for custom session timeouts 2026-04-03 00:43:42 +09:00
spaceman1412
102462b7a6 Cron: restrict exec visibility to timeouts 2026-04-03 00:43:42 +09:00
spaceman1412
d300a20440 Cron: surface exec timeouts in cron runs 2026-04-03 00:43:42 +09:00
Peter Steinberger
047b701859 refactor(telegram): unify callback-data byte limit checks 2026-04-03 00:38:44 +09:00
Peter Steinberger
7e2a450e31 docs: remove duplicated beta changelog fixes 2026-04-02 16:33:51 +01:00
Peter Steinberger
1f531d373b docs: dedupe changelog mirror fixes 2026-04-02 16:33:21 +01:00
Peter Steinberger
423f7c3487 build: prep 2026.4.2-beta.1 release 2026-04-02 16:33:21 +01:00
Vincent Koc
0ad2dbd307 fix(providers): route image generation through shared transport (#59729)
* fix(providers): route image generation through shared transport

* fix(providers): use normalized minimax image base url

* fix(providers): fail closed on image private routes

* fix(providers): bound shared HTTP fetches
2026-04-03 00:32:37 +09:00
Vincent Koc
d2ce3e9acc perf(plugins): keep gateway startup channel-only (#59754)
* perf(plugins): keep gateway startup channel-only

* fix(gateway): preserve startup sidecars in plugin scope
2026-04-03 00:28:15 +09:00
Peter Steinberger
988f7627de refactor(telegram): centralize approval callback shaping 2026-04-03 00:26:27 +09:00
Vincent Koc
efe9464f5f fix(tasks): tighten task-flow CLI surface (#59757)
* fix(tasks): tighten task-flow CLI surface

* fix(tasks): sanitize task-flow CLI text output
2026-04-03 00:25:10 +09:00
Peter Steinberger
0a76780f57 docs(changelog): mark 2026.4.1 as stable 2026-04-02 16:19:06 +01:00
Peter Steinberger
874a585d57 refactor(agent): share exec parser and runtime context codec 2026-04-03 00:15:43 +09:00
Vincent Koc
576337ef31 fix(tasks): use no-persist cleanup in executor tests 2026-04-03 00:15:02 +09:00
Peter Steinberger
8c3295038c test: harden task executor state-dir cleanup 2026-04-02 16:12:24 +01:00
Peter Steinberger
eb261fa690 fix: land Windows exec allowlist (#56285) (thanks @kpngr) 2026-04-03 00:09:28 +09:00
Peter Steinberger
36d953aab6 fix(exec): make Windows exec hints accurate and dynamic 2026-04-03 00:09:28 +09:00
Peter Steinberger
fff6333773 fix(exec): implement Windows argPattern allowlist flow 2026-04-03 00:09:28 +09:00
Vincent Koc
cc5146b9c6 fix(tasks): reset heartbeat and system event state in executor tests 2026-04-03 00:02:32 +09:00
Peter Steinberger
a5f99f4a30 test: stabilize docker test lanes 2026-04-02 15:59:23 +01:00
Vincent Koc
d46240090a test(tasks): add task-flow operator coverage (#59683) 2026-04-02 23:58:33 +09:00
Vincent Koc
3872a866a1 fix(xai): make x_search auth plugin-owned (#59691)
* fix(xai): make x_search auth plugin-owned

* fix(xai): restore x_search runtime migration fallback

* fix(xai): narrow legacy x_search auth migration

* fix(secrets): drop legacy x_search target registry entry

* fix(xai): no-op knob-only x_search migration fallback
2026-04-02 23:54:07 +09:00
Leo Zhang
b6debb4382 fix(agent): close remaining internal-context leak paths (#59649)
* fix(status): strip internal runtime context from task detail surfaces

* fix(agent): narrow legacy internal-context stripping

* fix(tasks): sanitize user-facing task status surfaces

* fix(agent): close remaining internal-context leak paths

* fix(agent): harden internal context delimiter sanitization

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-04-02 23:45:06 +09:00
Peter Steinberger
831729be4a docs(changelog): note telegram approval alias fix (#59217) (thanks @jameslcowan) 2026-04-02 23:41:12 +09:00
Peter Steinberger
52866656c3 fix(telegram): preserve allow-always callback alias 2026-04-02 23:41:12 +09:00
mappel-nv
53c29df2a9 Channel setup: ignore untrusted workspace shadows (#59158)
Keeps untrusted workspace channel metadata from overriding setup/login resolution for built-in channels. Workspace channel entries are only eligible during setup when the plugin is already explicitly trusted in config.

- Track discovered origin on channel catalog entries and add a setup-time catalog lookup that excludes workspace discoveries when needed
- Add resolver regression coverage for untrusted shadowing and trusted workspace overrides

Thanks @mappel-nv
2026-04-02 07:40:23 -07:00
Vincent Koc
4251ad6638 fix(telegram): allow trusted explicit proxy media fetches 2026-04-02 23:36:17 +09:00
James Cowan
7fea8250fb fix(approvals): use canonical decision values in interactive button payloads 2026-04-02 23:35:23 +09:00
Peter Steinberger
316d10637b refactor: canonicalize legacy x search secret target coverage 2026-04-02 15:30:05 +01:00
Peter Steinberger
65c1716ad4 refactor(infra): clarify jsonl socket contract 2026-04-02 15:20:37 +01:00
Peter Steinberger
ef86edacf7 fix: harden plugin auto-enable empty config handling 2026-04-02 15:19:53 +01:00
wangchunyue
b40ef364b7 fix: pin admin-only subagent gateway scopes (#59555) (thanks @openperf)
* fix(agents): pin subagent gateway calls to admin scope to prevent scope-upgrade pairing failures

callSubagentGateway forwards params to callGateway without explicit scopes,
so callGatewayLeastPrivilege negotiates the minimum scope per method
independently.  The first connection pairs the device at a lower tier and
every subsequent higher-tier call triggers a scope-upgrade handshake that
headless gateway-client connections cannot complete interactively
(close 1008 "pairing required").

Pin callSubagentGateway to operator.admin so the device is paired at the
ceiling scope on the very first (silent, local-loopback) handshake, avoiding
any subsequent scope-upgrade negotiation entirely.

Fixes #59428

* fix: pin admin-only subagent gateway scopes (#59555) (thanks @openperf)

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-04-02 19:40:03 +05:30
Vincent Koc
4f692190b4 fix(config): tolerate missing facade boundary config 2026-04-02 23:04:53 +09:00
skernelx
e0d20966ae Refine JSONL socket EOF regression test 2026-04-02 23:03:36 +09:00
skernelx
0e3cc12900 Fix macOS exec-host JSONL socket deadlock 2026-04-02 23:03:36 +09:00
Peter Steinberger
c4fb15e492 test: make web fetch runtime env handling hermetic 2026-04-02 15:02:40 +01:00
jacky
ecf72319ed fix: use JSON5 parser for plugin manifest loading (#57734) [AI-assisted] (#59084)
Merged via squash.

Prepared head SHA: 58a4d537fc
Co-authored-by: singleGanghood <179357632+singleGanghood@users.noreply.github.com>
Co-authored-by: hxy91819 <8814856+hxy91819@users.noreply.github.com>
Reviewed-by: @hxy91819
2026-04-02 22:02:04 +08:00
Vincent Koc
bb3f17fc02 refactor(plugins): drop generic status report alias (#59700) 2026-04-02 22:59:25 +09:00
Vincent Koc
b0f94a227b refactor(providers): normalize transport policy wiring (#59682)
* refactor(providers): normalize transport policy wiring

* fix(providers): address transport policy review

* fix(providers): harden transport overrides

* fix(providers): keep env proxy tls separate

* fix(changelog): note provider transport policy hardening
2026-04-02 22:54:34 +09:00
Peter Steinberger
4269f40811 docs(security): clarify exec yolo default 2026-04-02 14:52:51 +01:00
Peter Steinberger
c678ae7e7a feat(exec): default host exec to yolo 2026-04-02 14:52:51 +01:00
Vincent Koc
0500b410c5 docs: update config paths for Firecrawl web_fetch and xAI x_search migrations, add Android assistant section, backfill PR numbers 2026-04-02 22:52:00 +09:00
Vincent Koc
def5b954a8 feat(plugins): surface imported runtime state in status tooling (#59659)
* feat(plugins): surface imported runtime state

* fix(plugins): keep status imports snapshot-only

* fix(plugins): keep status snapshots manifest-only

* fix(plugins): restore doctor load checks

* refactor(plugins): split snapshot and diagnostics reports

* fix(plugins): track imported erroring modules

* fix(plugins): keep hot metadata where required

* fix(plugins): keep hot doctor and write targeting

* fix(plugins): track throwing module imports
2026-04-02 22:50:17 +09:00
Peter Steinberger
1ecd92af89 chore: refresh deps and backfill changelog 2026-04-02 14:49:47 +01:00
Ayaan Zaidi
a1f95e5278 fix: land Android assistant entrypoints (#59596) 2026-04-02 19:16:34 +05:30
Ayaan Zaidi
41b81ca7f8 fix: address Android assistant review feedback 2026-04-02 19:16:34 +05:30
Ayaan Zaidi
59eccef768 feat: add Google Assistant App Actions entrypoint 2026-04-02 19:16:34 +05:30
Ayaan Zaidi
e45b29b247 feat: add Android assistant role entrypoint 2026-04-02 19:16:34 +05:30
Ayaan Zaidi
fcf708665c feat: route Android assistant launches into chat 2026-04-02 19:16:34 +05:30
Agustin Rivera
290e5bf219 fix(dotenv): block helper interpreter workspace overrides (#58473)
* fix(dotenv): block helper interpreter workspace overrides

* fix(dotenv): cover trusted helper interpreter envs

* fix(changelog): note dotenv helper override hardening

* fix(changelog): remove dotenv entry from pr

* changelog: note dotenv helper override hardening

---------

Co-authored-by: Jacob Tomlinson <jtomlinson@nvidia.com>
2026-04-02 06:45:13 -07:00
Vincent Koc
52a6e354a8 fix(tasks): reset agent events in executor tests 2026-04-02 22:25:59 +09:00
Vincent Koc
ec6a07ef05 fix(secrets): add legacy x_search secret target 2026-04-02 22:24:08 +09:00
Jacob Tomlinson
3528e15817 changelog: add openshell mirror sync fix entry (#58515) 2026-04-02 13:23:58 +00:00
Peter Steinberger
3cca07a983 docs: reorder changelog entries by user interest 2026-04-02 14:22:19 +01:00
Agustin Rivera
b21c9840c2 OpenShell: constrain mirror sync roots (#58515)
* fix(openshell): constrain mirror sync roots

* fix(openshell): restore config test types

* fix(openshell): simplify managed root sync
2026-04-02 06:21:30 -07:00
Vincent Koc
3e4de956c0 !refactor(xai): move x_search config behind plugin boundary (#59674)
* refactor(xai): move x_search config behind plugin boundary

* chore(changelog): note x_search config migration

* fix(xai): include x_search migration helpers
2026-04-02 22:08:59 +09:00
Agustin Rivera
ef7c553dd1 fix(zalo): scope webhook replay dedupe (#58444)
* fix(zalo): scope webhook replay dedupe

* fix(zalo): harden replay metadata reads

* docs(changelog): add Zalo replay scope fix entry

---------

Co-authored-by: Jacob Tomlinson <jtomlinson@nvidia.com>
2026-04-02 06:07:14 -07:00
Vincent Koc
12bd6b7bb9 fix(tasks): address task-flow audit review (#59672) 2026-04-02 22:02:00 +09:00
Devin Robison
7eae9c0e62 Block remaining host env override pivots (#59233)
* Blck remaining host env override pivots

* Feedback update
2026-04-02 06:00:26 -07:00
Agustin Rivera
54a0878517 fix(gateway): enforce session kill HTTP scopes (#59128)
* fix(gateway): enforce session kill HTTP scopes

Co-authored-by: Jacob Tomlinson <jtomlinson@nvidia.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>

* fix(gateway): type session kill auth mock

* fix(gateway): gate session kill before lookup

* docs: add changelog entry for session kill HTTP scopes

---------

Co-authored-by: Jacob Tomlinson <jtomlinson@nvidia.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-04-02 05:56:17 -07:00
Agustin Rivera
be10ecef77 fix(compare): reuse shared secret comparison helper (#58432)
* fix(compare): reuse shared secret comparison helper

* fix(compare): reject empty bluebubbles auth tokens

* docs: add changelog entry for shared secret comparison fix

---------

Co-authored-by: Jacob Tomlinson <jtomlinson@nvidia.com>
2026-04-02 13:53:19 +01:00
Vincent Koc
4c08b0bb08 fix(tasks): allow task-flow registry audit seams 2026-04-02 21:49:26 +09:00
Vincent Koc
cfbad0a4f9 fix(providers): unify request policy resolution (#59653)
* fix(providers): unify request policy resolution

* fix(providers): preserve request config SDK contract

* fix(providers): harden request header policy
2026-04-02 21:42:11 +09:00
Vincent Koc
d4f69878da fix(tasks): close registry stores on test resets 2026-04-02 21:40:40 +09:00
Vincent Koc
6f91f87f3b refactor(tasks): move task-flow ownership under tasks 2026-04-02 21:40:40 +09:00
Vincent Koc
0f45630d19 fix(tasks): harden task-flow restore and maintenance 2026-04-02 21:40:40 +09:00
mappel-nv
9c22d63669 Browser: normalize localhost absolute-form CDP hosts (#59236)
* Browser: normalize localhost absolute-form CDP hosts

* CHANGELOG: note localhost absolute-form CDP fix

---------

Co-authored-by: Jacob Tomlinson <jtomlinson@nvidia.com>
2026-04-02 13:34:55 +01:00
Vincent Koc
e48ee8ae9e test(secrets): update inactive warning coverage 2026-04-02 21:21:38 +09:00
Vincent Koc
b18de06bff test(secrets): fix runtime coverage env allowlist 2026-04-02 21:10:30 +09:00
Vincent Koc
15e6a88c67 fix(config): sync generated base schema 2026-04-02 21:04:06 +09:00
gavyngong
761cdc967d fix(gateway): prune empty node-pending-work state entries to prevent memory leak (#58179)
Merged via squash.

Prepared head SHA: 1efee3099f
Co-authored-by: gavyngong <267269824+gavyngong@users.noreply.github.com>
Co-authored-by: hxy91819 <8814856+hxy91819@users.noreply.github.com>
Reviewed-by: @hxy91819
2026-04-02 20:00:18 +08:00
Vincent Koc
9823833383 fix(plugins): preserve activation provenance (#59641)
* fix(plugins): preserve activation provenance

* fix(gateway): preserve activation reason metadata

* fix(plugins): harden activation state policy
2026-04-02 20:57:14 +09:00
Vincent Koc
6eca1949d5 refactor(plugins): tighten web fetch provider boundary (#59646)
* refactor(plugins): tighten web fetch provider boundary

* fix(config): sync fetch secret parity and baseline

* fix(ci): enforce web fetch boundary guard
2026-04-02 20:53:57 +09:00
Vincent Koc
5abd5d889f fix(providers): classify copilot native endpoints (#59644)
* fix(providers): classify copilot native endpoints

* fix(changelog): add copilot endpoint note

* fix(providers): handle copilot proxy hints
2026-04-02 20:51:46 +09:00
Shakker
71d49012fc fix: align secretref web-fetch matrix 2026-04-02 12:47:09 +01:00
Vincent Koc
5639e8d242 fix(tasks): stabilize task-flow rename gates 2026-04-02 20:43:04 +09:00
Vincent Koc
e894c7e66e refactor(commands): switch flow tooling to task-flow names 2026-04-02 20:43:03 +09:00
Vincent Koc
b6c3ecedd8 refactor(tasks): update plugin and acp task-flow consumers 2026-04-02 20:43:03 +09:00
Vincent Koc
a7909d46d2 refactor(tasks): migrate task runtime callsites to task-flow 2026-04-02 20:43:03 +09:00
Vincent Koc
a51c976d27 refactor(tasks): rename flow registry modules to task-flow 2026-04-02 20:43:03 +09:00
Vincent Koc
c405bcfa98 refactor(providers): centralize request capabilities (#59636)
* refactor(providers): centralize request capabilities

* fix(providers): harden comparable base url parsing
2026-04-02 20:26:22 +09:00
Vincent Koc
38d2faee20 !feat(plugins): add web fetch provider boundary (#59465)
* feat(plugins): add web fetch provider boundary

* feat(plugins): add web fetch provider modules

* refactor(web-fetch): remove remaining core firecrawl fetch config

* fix(web-fetch): address review follow-ups

* fix(web-fetch): harden provider runtime boundaries

* fix(web-fetch): restore firecrawl compare helper

* fix(web-fetch): restore env-based provider autodetect

* fix(web-fetch): tighten provider hardening

* fix(web-fetch): restore fetch autodetect and compat args

* chore(changelog): note firecrawl fetch config break
2026-04-02 20:25:19 +09:00
Vincent Koc
82d5e6a2f7 fix(ci): isolate task executor delivery runtime cache 2026-04-02 20:21:23 +09:00
Mariano
bbf9800a8e Plugins: add bound TaskFlow runtime (#59622)
Merged via squash.

Prepared head SHA: b4649f3238
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Reviewed-by: @mbelinky
2026-04-02 13:17:09 +02:00
Vincent Koc
474409deb5 fix(ci): reset flow registry in acp manager tests 2026-04-02 20:04:19 +09:00
Vincent Koc
d49460b417 fix(providers): centralize Anthropic endpoint classification (#59608)
* fix(providers): centralize Anthropic endpoint classification

* fix(agents): share Anthropic thinking recovery gating
2026-04-02 19:54:43 +09:00
Vincent Koc
d87bc6706c fix(plugin-sdk): narrow groups runtime discord seam (#59623) 2026-04-02 19:51:36 +09:00
Vincent Koc
707f5485b9 fix(ci): tighten thinking recovery stream types 2026-04-02 19:47:52 +09:00
Mariano
8bdca2323d TaskFlow: add managed child task execution (#59610)
Merged via squash.

Prepared head SHA: e6cdde6c21
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Reviewed-by: @mbelinky
2026-04-02 12:45:03 +02:00
Vincent Koc
f65da8711a fix(plugin-sdk): narrow cli discord and matrix contract seams (#59565) 2026-04-02 19:40:04 +09:00
Vincent Koc
dfe95b1e1b fix(ci): align task store flow-link test with runtime guard 2026-04-02 19:32:07 +09:00
Vincent Koc
9aa2ef2736 fix(agents): recover Anthropic thinking after crash (#59062)
* fix(agents): recover Anthropic thinking after crash

* fix(agents): avoid duplicate Anthropic recovery chunks

* fix(agents): preserve Anthropic stream result
2026-04-02 19:30:25 +09:00
Vincent Koc
ec17260e26 docs: rename ClawFlow to TaskFlow and update references 2026-04-02 19:28:49 +09:00
Vincent Koc
f8e67ef698 docs: restore TaskFlow docs and fix Slack attribution 2026-04-02 19:24:36 +09:00
Vincent Koc
ecb4ea9830 fix(ci): restore exec approval masking semantics 2026-04-02 19:23:26 +09:00
Vincent Koc
0e9a9dae84 fix(providers): centralize Google endpoint classification (#59556)
* fix(providers): centralize Google endpoint classification

* fix(providers): tighten Google endpoint fallback parsing

* fix(security): harden provider endpoint fallback parsing
2026-04-02 19:21:31 +09:00
Mariano
2fa4c7cc61 TaskFlow: restore managed substrate (#58930)
Merged via squash.

Prepared head SHA: c99093838f
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Reviewed-by: @mbelinky
2026-04-02 12:17:56 +02:00
Ayaan Zaidi
52d2bd5cc6 fix: reject stale ACP reconnect prompts 2026-04-02 15:32:46 +05:30
Jacob Tomlinson
ac5bc4fb37 Slack: filter thread context by allowlist (#58380)
* Slack: filter thread context by allowlist

* Slack: honor room thread allowlists

* Slack: keep open-room thread context

* Slack: keep non-room thread context

* Changelog: add Slack thread context fix
2026-04-02 11:01:11 +01:00
Sliverp
0e3da03193 fix(doc):update wecom doc and qq (#57641)
* fix(doc):update wecom doc and qq
doc

* Update CHANGELOG with recent changes and enhancements

Added various updates including new features, improvements, and documentation changes across multiple components.
2026-04-02 17:55:32 +08:00
Ayaan Zaidi
e3319b2a63 fix: guard ACP disconnect clears by epoch 2026-04-02 15:15:21 +05:30
Ayaan Zaidi
d983970704 fix: preserve ACP reconnect epochs 2026-04-02 15:15:21 +05:30
Ayaan Zaidi
73c1b45819 fix: keep active ACP runs alive after reconnect timeout 2026-04-02 15:15:21 +05:30
Ayaan Zaidi
e48a7b9be8 refactor: distill ACP reconnect prompt state 2026-04-02 15:15:21 +05:30
Jacob Tomlinson
657295c347 docs(changelog): add missing merged PR entries 2026-04-02 09:44:43 +00:00
mappel-nv
2eaf5a695e Mattermost: guard probe fetches (#58529) 2026-04-02 10:30:33 +01:00
Jacob Tomlinson
2c45b06afd fix(qqbot): restrict structured payload local paths (#58453)
* fix(qqbot): restrict structured payload local paths

* fix(qqbot): narrow structured payload file access

* test(qqbot): cover payload path traversal guards

* fix(qqbot): reduce structured payload log exposure

* fix(qqbot): preserve inline image payload URLs
2026-04-02 10:20:52 +01:00
Julia Bush
5c36c2d0d2 fix: install devDependencies during ui:build (#59267) (thanks @juliabush)
* fix(ui): install devDependencies during ui:build

* fix: keep ui:build self-heal documented (#59267) (thanks @juliabush)

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-04-02 14:35:05 +05:30
Ayaan Zaidi
304da2cbd7 fix: keep ACP prompts alive across gateway reconnects (#59473)
* fix: keep acp prompts alive across gateway reconnects

* fix: bound ACP prompts after disconnect grace

* fix: preserve ACP send timeout semantics

* fix: defer pre-ack ACP disconnect failures

* fix: reconcile ACP runs after reconnect

* fix: keep ACP reconnect deadlines monotonic

* fix: keep pre-ack ACP deadlines after reconnect

* fix: keep ACP prompts alive across gateway reconnects (#59473)

* fix: reject superseded ACP pre-ack prompts (#59473)

* style: format ACP reconnect regression updates (#59473)

* style: format ACP reconnect regression updates (#59473)

* fix: guard ACP send acceptance by run id (#59473)

* fix: scope ACP reconnect deadline by prompt (#59473)

* fix: recheck ACP prompts at reconnect deadline (#59473)

* fix: key ACP reconnect deadline by run (#59473)
2026-04-02 14:34:11 +05:30
Ayaan Zaidi
c27b45fd12 fix: strip antml thinking tags (#59550) 2026-04-02 14:28:54 +05:30
Ayaan Zaidi
176ff18d18 fix: strip antml thinking tags 2026-04-02 14:28:54 +05:30
scoootscooob
251ba9b4d2 docs(changelog): note invalid exec approvals policy fix 2026-04-02 01:46:16 -07:00
wangchunyue
a597938be8 fix(exec): strip invalid approval policy enums during config normalization (#59112)
* fix(exec): strip invalid security/ask enum values during config normalization

* fix(exec): narrow invalid approvals config cleanup

---------

Co-authored-by: scoootscooob <zhentongfan@gmail.com>
2026-04-02 01:40:10 -07:00
Vincent Koc
d90c8db491 fix(plugin-sdk): narrow discord and matrix core seam imports (#59557) 2026-04-02 17:40:06 +09:00
Vincent Koc
331e835dab fix(providers): centralize stream request headers (#59542)
* fix(providers): centralize stream request headers

* Update src/agents/provider-request-config.ts

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-04-02 17:21:46 +09:00
Vincent Koc
08962b6812 fix(browser): keep static helper seams cold (#59471)
* fix(browser): keep static helper seams cold

* fix(browser): narrow sandbox helper facade imports

* fix(browser): harden host inspection helpers
2026-04-02 17:12:32 +09:00
Ayaan Zaidi
b441cd2f4f fix: normalize kimi anthropic tool payloads (#59440)
* fix: normalize kimi anthropic tool payloads

* fix: normalize kimi anthropic tool payloads (#59440)
2026-04-02 13:39:51 +05:30
Vincent Koc
53f1c9968a fix(ci): restore model override and trash-path fallbacks 2026-04-02 16:59:27 +09:00
Gustavo Madeira Santana
68bb76519a Matrix: fix delayed draft block boundaries 2026-04-02 03:47:57 -04:00
Gustavo Madeira Santana
8748b7c54c Matrix: keep partial previews aligned with block streaming (#59384)
Merged via squash.

Prepared head SHA: 981aa35a7c
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-02 03:39:27 -04:00
wittam-01
ce0ff42ff5 fix: harden Feishu comment-thread delivery (#59129)
* fix: harden Feishu comment-thread delivery

* fix: harden Feishu comment-thread delivery (#59129) (thanks @wittam-01)

---------

Co-authored-by: George Zhang <georgezhangtj97@gmail.com>
2026-04-02 00:31:52 -07:00
Gustavo Madeira Santana
a5cd921053 revert: remove TinyFish bundled plugin 2026-04-02 03:07:33 -04:00
Mingkuan
c15cfeb21c fix(qqbot): lazy-load silk-wasm to avoid hard failure when package is missing (#58829)
* fix(qqbot): lazy-load silk-wasm to avoid hard failure when package is missing

Replace the static top-level import with a cached dynamic import helper.
If silk-wasm is unavailable the plugin loads normally; voice encode/decode
degrades gracefully instead of crashing the module at load time.

* fix(qqbot): store in-flight Promise in loadSilkWasm to prevent duplicate imports

Concurrent cold-start calls to loadSilkWasm() before the first import()
resolves would each fire a separate dynamic import. Storing the Promise
instead of the resolved value (matching the detectFfmpeg pattern in
platform.ts) ensures all concurrent callers await the same import,
keeping the codebase consistent and avoiding redundant parallel loads.

* QQBot: add changelog for silk-wasm lazy load

* QQBot: move changelog entry for PR #58829

---------

Co-authored-by: sliverp <870080352@qq.com>
Co-authored-by: Sliverp <38134380+sliverp@users.noreply.github.com>
2026-04-02 14:46:53 +08:00
Gustavo Madeira Santana
0809c8d29a fix(matrix): preserve legacy mention edits 2026-04-02 02:33:00 -04:00
Vincent Koc
3e52f5a021 docs(changelog): add TinyFish PR number and attribution 2026-04-02 15:29:36 +09:00
Vincent Koc
f28f0f29ba fix(providers): centralize media request shaping (#59469)
* fix(providers): centralize media request shaping

* style(providers): normalize shared request imports

* fix(changelog): add media request shaping entry

* fix(google): preserve private network guard
2026-04-02 15:28:57 +09:00
Gustavo Madeira Santana
9786946b2d fix(matrix): restore guided setup flow (#59462)
Merged via squash.

Prepared head SHA: 9b29023c68
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-02 02:15:32 -04:00
Gustavo Madeira Santana
5c331687ff fix(matrix): ignore escaped backticks in mention masking 2026-04-02 02:06:50 -04:00
Gustavo Madeira Santana
be52594766 fix(matrix): emit spec-compliant mentions (#59323)
Merged via squash.

Prepared head SHA: 4b641e35a2
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-02 02:00:24 -04:00
Vincent Koc
7b748a57f0 fix(contracts): activate runtime-backed binding suites 2026-04-02 14:57:11 +09:00
Simantak Dabhade
b880118d2d feat: add TinyFish as bundled browser automation plugin (#58645)
* feat: add TinyFish as bundled browser automation plugin

Add a default-off bundled `tinyfish` plugin with one tool
(`tinyfish_automation`) for hosted browser automation of complex public
web workflows. Follows the existing plugin architecture pattern.

- Plugin entry, manifest with contracts, config schema, SecretRef support
- SSE stream parser with COMPLETE-terminal, SSRF guards, credential rejection
- Bundled skill with escalation guidance (web_fetch -> web_search -> tinyfish -> browser)
- Docs page, labeler rule, glossary entry, changelog entry
- 21 tests covering request serialization, auth, security, streaming, and error paths

Closes #41300

* plugins: address review feedback and regenerate baselines

- Split API_INTEGRATION into TINYFISH_API_INTEGRATION and CLIENT_SOURCE
  for semantic clarity (Greptile P2)
- Wrap post-finally parseEventBlock in try/catch so trailing malformed
  data does not mask "stream ended before COMPLETE" error (Greptile P2)
- Regenerate config-baseline and plugin-sdk-api-baseline for new plugin

---------

Co-authored-by: Simantak Dabhade <simantak@mac.local>
2026-04-02 01:46:05 -04:00
Vincent Koc
93fa6920b4 perf(memory): lazy-load telegram message context runtime 2026-04-02 14:44:14 +09:00
Vincent Koc
16c5bd466c perf(memory): split telegram body helper surface 2026-04-02 14:41:26 +09:00
Vincent Koc
52a018680d fix(plugins): guard runtime facade activation (#59412)
* fix(plugins): guard runtime facade activation

* refactor(plugin-sdk): localize facade load policy

* fix(plugin-sdk): narrow facade activation guards

* fix(browser): keep cleanup helpers outside activation guard

* style(browser): apply formatter follow-ups

* chore(changelog): note plugin activation guard regressions

* fix(discord): keep cleanup thread unbinds outside activation guard

* fix(browser): fallback when trash exits non-zero
2026-04-02 14:37:12 +09:00
Vincent Koc
ed6012eb5b fix(agents): honor cacheRetention for custom anthropic providers (#59049)
* fix(agents): honor cacheRetention for custom anthropic providers

* docs(changelog): add cache retention entry

* Update CHANGELOG.md

* test(agents): add direct cache retention assertions
2026-04-02 14:34:01 +09:00
Sally O'Malley
41aac73590 chore(docs): sync config baseline (#59461) 2026-04-02 01:33:21 -04:00
Vincent Koc
703a363589 perf(memory): lazy-load telegram context session helpers 2026-04-02 14:31:48 +09:00
Vincent Koc
1707493be4 refactor(providers): add internal request config seam (#59454) 2026-04-02 14:28:25 +09:00
Gustavo Madeira Santana
f69570f820 Exec approvals: fix policy source attribution (#59367)
Merged via squash.

Prepared head SHA: 974945a9f0
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-02 01:28:14 -04:00
Sally O'Malley
ad6e42906f chore(docs): sync generated baselines (#59450) 2026-04-02 01:26:45 -04:00
Vincent Koc
0e8e986c95 perf(memory): narrow telegram bot deps skill/runtime imports 2026-04-02 14:16:13 +09:00
Vincent Koc
7dc065dab0 docs(changelog): add missing PR numbers 2026-04-02 14:12:28 +09:00
Vincent Koc
5b952836e3 perf(memory): trim telegram command runtime imports 2026-04-02 14:11:28 +09:00
Vincent Koc
1a037ff6cd refactor(providers): centralize request attribution policy (#59433)
* refactor(providers): centralize request attribution policy

* style(providers): normalize request policy formatting

* style(providers): normalize request policy formatting

* style(providers): normalize request policy formatting

* docs(changelog): note provider request policy fix

* fix(providers): tighten request policy gates
2026-04-02 14:10:53 +09:00
Vincent Koc
4309dc6d5e perf(memory): lazy-load telegram monitor runtime graphs 2026-04-02 14:07:35 +09:00
Vincent Koc
fcfb9ddb1d fix(matrix): preserve mocked auth context in bootstrap 2026-04-02 14:03:07 +09:00
Gustavo Madeira Santana
e718493ae6 test(matrix): cover draft overflow fallback 2026-04-02 01:02:18 -04:00
Vincent Koc
85928e29f1 perf(memory): lazy-load matrix bootstrap and probe runtimes 2026-04-02 13:57:03 +09:00
Vincent Koc
be1b4e6683 fix(ci): route matrix config helper through local barrel 2026-04-02 13:52:54 +09:00
Vincent Koc
4fd1e1c64f perf(memory): lazy-load matrix client runtime deps 2026-04-02 13:51:48 +09:00
Scott Glover
9bbbee32e1 Docs: replace personal device names with generic placeholders (#50825) 2026-04-02 00:50:25 -04:00
Vincent Koc
6dbdcbda58 perf(memory): lazy-load matrix shared client creation 2026-04-02 13:48:20 +09:00
Vincent Koc
bfa561b1a7 perf(memory): lazy-load matrix secret config input 2026-04-02 13:46:06 +09:00
Vincent Koc
a398520ac8 perf(memory): trim matrix resolved config imports 2026-04-02 13:42:58 +09:00
Priyansh Gupta
b9c74fc884 fix(image-tool): resolve relative paths against workspaceDir (#57222)
Relative paths like "inbox/receipt.png" were resolved against
process.cwd() instead of the agent's workspaceDir, causing the
allowlist check to fail with "Local media path is not under an
allowed directory". This matches how the read tool already behaves.

Fixes #57215

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-02 00:41:09 -04:00
Marcus Castro
e1c96785ac fix(whatsapp): gate connect-time presence on selfChatMode to preserve phone notifications (#59410) 2026-04-02 01:40:06 -03:00
Vincent Koc
df60fa8d49 perf(memory): trim matrix account resolution imports 2026-04-02 13:39:33 +09:00
Vincent Koc
45adba882f docs: add contributor attribution for diffs viewerBaseUrl 2026-04-02 13:35:23 +09:00
Gustavo Madeira Santana
19c954bd78 diffs: add configurable viewer base URL (#59341)
Merged via squash.

Prepared head SHA: 3c2a84849f
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-02 00:31:29 -04:00
Vincent Koc
d55cefac00 perf(memory): trim matrix config import graph 2026-04-02 13:21:44 +09:00
Vincent Koc
75b5a4c713 perf(memory): lazy-load matrix send client bootstrap 2026-04-02 13:10:24 +09:00
Vincent Koc
4a5102c1bb docs: fix changelog attribution and add missing WhatsApp MIME fix entry 2026-04-02 13:03:35 +09:00
Vincent Koc
d4c7ef3778 perf(memory): lazy-load matrix crypto runtime graph 2026-04-02 12:51:50 +09:00
Sally O'Malley
ee274dbdd1 remove noisy podman output from launch script and update doc (#59368)
Signed-off-by: sallyom <somalley@redhat.com>
2026-04-01 23:28:11 -04:00
bobbyt74
cae1d9bc6d fix(whatsapp): add HTML/XML/CSS to MIME map + fallback for unknown media types (#51562)
Merged via squash.

Prepared head SHA: 83f2eabd49
Co-authored-by: bobbyt74 <262672147+bobbyt74@users.noreply.github.com>
Co-authored-by: mcaxtr <7562095+mcaxtr@users.noreply.github.com>
Reviewed-by: @mcaxtr
2026-04-02 00:25:12 -03:00
Vincent Koc
8c3167a7c7 perf(memory): trim matrix auth test sdk imports 2026-04-02 11:45:32 +09:00
Vincent Koc
534f0a644b fix(plugins): keep browser facade helpers cold 2026-04-02 11:44:49 +09:00
wangchunyue
51edd30bea fix: restore local loopback role upgrades (#59092) (thanks @openperf)
* fix(gateway ): allow silent role upgrades for local loopback clients

When a local loopback client connects with a role not covered by
existing device tokens, listEffectivePairedDeviceRoles incorrectly
returns an empty role set for devices whose tokens map is an empty
object. This triggers a role-upgrade pairing request that
shouldAllowSilentLocalPairing rejects because it does not recognise
the role-upgrade reason.

Fix listEffectivePairedDeviceRoles to fall back to legacy role fields
when the tokens map has no entries, and extend
shouldAllowSilentLocalPairing to accept role-upgrade for local
clients.

Fixes #59045

* fix: restore local loopback role upgrades (#59092) (thanks @openperf)

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-04-02 08:13:45 +05:30
Vincent Koc
1ea901b107 perf(memory): lazy-load slack action runtime graph 2026-04-02 11:31:19 +09:00
Vincent Koc
a7e3c0b0e1 feat(slack): add scoped prompts and mrkdwn hints (#59100)
* feat(slack): add scoped prompts and mrkdwn hints

* refactor(slack): drop dm prompt override

* refactor(slack): drop exposed prompt config

* chore(changelog): note slack mrkdwn fix
2026-04-02 11:23:43 +09:00
Vincent Koc
7c913f2e13 chore(changelog): record plugin loading hardening 2026-04-02 11:20:50 +09:00
Vincent Koc
7771c69caf fix(plugins): enforce activation before shipped imports (#59136)
* fix(plugins): enforce activation before shipped imports

* fix(plugins): remove more ambient bundled loads

* fix(plugins): tighten scoped loader matching

* fix(plugins): remove channel-id scoped loader matches

* refactor(plugin-sdk): relocate ambient provider helpers

* fix(plugin-sdk): preserve unicode ADC credential paths

* fix(plugins): restore safe setup fallback
2026-04-02 11:18:49 +09:00
Vincent Koc
765e8fb713 perf(memory): trim matrix send media imports 2026-04-02 11:16:50 +09:00
Vincent Koc
f4e2240b85 perf(memory): trim matrix account config imports 2026-04-02 11:12:37 +09:00
Gustavo Madeira Santana
7514324510 Docs: fix plugin architecture table formatting 2026-04-01 22:07:15 -04:00
Brad Groux
03c64df39f fix(msteams): use formatUnknownError instead of String(err) for error logging (#59321)
Replaces String(err) with the existing formatUnknownError() utility across
the msteams extension to prevent [object Object] appearing in error logs
when non-Error objects are caught (e.g., Axios errors, Bot Framework SDK
error objects).

Fixes #53910

thanks @bradgroux
2026-04-01 21:06:44 -05:00
Vincent Koc
474693bdb2 perf(memory): trim matrix monitor allowlist imports 2026-04-02 11:05:27 +09:00
Gustavo Madeira Santana
ba735d0158 Exec approvals: unify effective policy reporting and actions (#59283)
Merged via squash.

Prepared head SHA: d579b97a93
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-01 22:02:39 -04:00
Vincent Koc
dc66c36b9e perf(memory): trim telegram monitor test module churn 2026-04-02 10:57:01 +09:00
Gustavo Madeira Santana
32fa5c3be5 fix(agents): resolve compaction wait before channel flush (#59308)
Merged via squash.

Prepared head SHA: bf17502df8
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-01 21:40:23 -04:00
Vincent Koc
326490ab76 docs: cover compaction notifyUser config and provider replay hooks 2026-04-02 10:23:33 +09:00
Vincent Koc
687030cbf2 perf(memory): trim matrix and telegram runtime seams 2026-04-02 10:18:56 +09:00
Vincent Koc
1cc5526f7f docs: fix Nostr inbound signature verification order in docs 2026-04-02 10:11:09 +09:00
joshavant
c22233d96c Revert "refactor(plugins): remove before_install hook" 2026-04-01 19:57:07 -05:00
Brad Groux
57949397fa fix(msteams): prevent duplicate text when stream exceeds 4000 char limit (#59297)
When a streamed response exceeds TEAMS_MAX_CHARS, the stream sets streamFailed=true and finalizes. Previously, hasContent returned false when streamFailed was true, causing preparePayload to pass through the full payload for block delivery, duplicating already-streamed text.

Now tracks streamed length and strips the already-delivered prefix from fallback payloads.

Fixes #58601

thanks @bradgroux
2026-04-01 19:03:19 -05:00
Gustavo Madeira Santana
560ea25294 Matrix: restore ordered progress delivery with explicit streaming modes (#59266)
Merged via squash.

Prepared head SHA: 523623b7e1
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-01 19:35:03 -04:00
Gustavo Madeira Santana
91a7505af6 fix(tests): serialize shared channel audit state cases 2026-04-01 19:12:05 -04:00
Gustavo Madeira Santana
a204f790ce fix(matrix): package verification bootstrap runtime (#59249)
Merged via squash.

Prepared head SHA: df5891b663
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-01 17:52:51 -04:00
Gustavo Madeira Santana
c87c8e66bf Refactor channel approval capability seams (#58634)
Merged via squash.

Prepared head SHA: c9ad4e4706
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-01 17:10:25 -04:00
Logan Ye
d9a7ffe003 failover: classify AbortError / stream-abort messages as timeout (#58315) (#58324)
Merged via squash.

Prepared head SHA: d8412f27e6
Co-authored-by: yelog <14227866+yelog@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-04-02 00:02:31 +03:00
zqchris
75ab5bce6b fix(bluebubbles): add enrichGroupParticipantsFromContacts to core Zod schema (#56889)
* fix(bluebubbles): add enrichGroupParticipantsFromContacts to core Zod schema

The field was added to the extension config schema in #54984 but not
synced to the core strict Zod validator, causing config validation to
reject the key at startup with 'Unrecognized key'.

* test(config): add BlueBubbles schema regression coverage

* fix(bluebubbles): accept enrichGroupParticipantsFromContacts config

---------

Co-authored-by: Chris Zhang <chris@ChrisdeMac-mini.local>
Co-authored-by: Altay <altay@uinaf.dev>
2026-04-01 23:55:58 +03:00
Josh Lehman
71346940ad refactor: add provider replay runtime hook surfaces (#59143)
Merged via squash.

Prepared head SHA: 56b41e87a5
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-04-01 13:45:41 -07:00
Bruno Lorente
ca76e2fedc fix(cron-tool): add typed properties to job/patch schemas (#55043)
Merged via squash.

Prepared head SHA: 979bb0e8b7
Co-authored-by: brunolorente <127802443+brunolorente@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-04-01 23:41:19 +03:00
Kris Wu
7027dda8cd fix(ui): prevent premature compaction status update on retry (#55132)
Merged via squash.

Prepared head SHA: e7e562f982
Co-authored-by: mpz4life <32388289+mpz4life@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-04-01 13:38:51 -07:00
Joshua Lelon Mitchell
7cb323d84f feat(plugins): add before_agent_reply hook (claiming pattern) (#20067)
Merged via squash.

Prepared head SHA: e40dfbdfb9
Co-authored-by: JoshuaLelon <23615754+JoshuaLelon@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-04-01 13:31:11 -07:00
Nimrod Gutman
017bc5261c fix(gateway): prefer bootstrap auth over tailscale (#59232)
* fix(gateway): prefer bootstrap auth over tailscale

* fix(gateway): prefer bootstrap auth over tailscale (#59232) (thanks @ngutman)
2026-04-01 23:20:10 +03:00
9ra55
5cf254a5f7 fix: honor authHeader provider config by injecting Authorization Bear… (#54390)
Merged via squash.

Prepared head SHA: 9889615571
Co-authored-by: lndyzwdxhs <16411017+lndyzwdxhs@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-04-01 13:18:37 -07:00
Josh Lehman
90eb5b073f fix: pass session identity to plugin commands (#59044)
Merged via squash.

Prepared head SHA: 0f7a23f139
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-04-01 13:07:17 -07:00
VACInc
711c9e7249 fix(gateway): emit before_reset on session reset (#53872)
Merged via squash.

Prepared head SHA: a47894ef16
Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-04-01 12:46:53 -07:00
Oguri Cap
1f99c87a44 feat: add agents.defaults.compaction.notifyUser config option (default: false) [Fix #54249] (#54251)
Merged via squash.

Prepared head SHA: 6fd4cdb7c3
Co-authored-by: oguricap0327 <266246182+oguricap0327@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-04-01 12:29:17 -07:00
Luke
5b73108e58 fix: /context detail severely underestimates token count (#28391)
Merged via squash.

Prepared head SHA: 5ea6a074f3
Co-authored-by: ImLukeF <92253590+ImLukeF@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-04-01 12:11:08 -07:00
Josh Lehman
1c83e2eec7 fix: scope session create aliases to requested agent (#58207)
Merged via squash.

Prepared head SHA: 9462848777
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-04-01 11:39:31 -07:00
Peter Steinberger
8abba663c5 chore: bump version to 2026.4.2 2026-04-01 19:39:27 +01:00
Peter Steinberger
8f617bf4d7 fix: validate npm dist-tag auth before publish 2026-04-01 19:39:09 +01:00
Gustavo Madeira Santana
b24961c5d1 fix(matrix): tighten account scoping and default detection 2026-04-01 14:20:02 -04:00
Moliendo
d076153fc9 fix(config): coerce numeric Discord IDs to strings instead of rejecting (#45125)
Merged via squash.

Prepared head SHA: 099ba514a1
Co-authored-by: moliendocode <29582793+moliendocode@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-04-01 21:07:28 +03:00
Josh Lehman
4f407d2658 docs(changelog): move #55336 note to 2026.4.1 2026-04-01 10:57:35 -07:00
Onur
38faa3c767 Docs: clarify release preflight promotion 2026-04-01 19:55:21 +02:00
Daan van der Plas
7fa1a31094 fix(matrix): honor room account scoping (#58449)
Merged via squash.

Prepared head SHA: d83f06ee3f
Co-authored-by: Daanvdplas <93204684+Daanvdplas@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
2026-04-01 13:49:22 -04:00
Doğu Abaris
5190b3b3fa fix: avoid locally caught ACP session init exception (#55136) (thanks @doguabaris) 2026-04-01 19:46:22 +02:00
Joshua McKiddy
dd7df0753f fix(security): prevent memory exhaustion in inline image decoding (#22325)
thanks @hackersifu
2026-04-01 12:44:05 -05:00
Peter Steinberger
5e3352f367 chore: update appcast for 2026.4.1 2026-04-01 18:33:09 +01:00
Onur
f1f5a3fcf4 Release: trim duplicate preflight work (#59117)
* Release: skip duplicate runtime-deps staging

* Release: trim public mac validation workflow

* Release: require promoted npm publish

* Release: verify promoted npm provenance

* Release: restore public mac validation build

* Release: skip pack check on npm promote

* Release: skip pack check on npm promote
2026-04-01 19:24:37 +02:00
Peter Steinberger
da64a978e5 chore: prepare 2026.4.1 release 2026-04-01 17:57:10 +01:00
Vincent Koc
34332257b0 perf(matrix): cut avoidable startup memory in client and monitor tests 2026-04-02 01:00:28 +09:00
Eric Wong
2427304654 fix(msteams): pass teamId and teamName to resolveAgentRoute() (#50214)
thanks @slbteam08
2026-04-01 10:50:41 -05:00
Lewis
e881e96bd0 fix(msteams): sanitize error messages sent to users (#33343)
thanks @lewiswigmore
2026-04-01 10:44:24 -05:00
Vincent Koc
8c6f31cc6b perf(test): narrow discord chunking imports 2026-04-02 00:41:57 +09:00
Peter Steinberger
79d0c92f3d ci: prefix reused npm tarball paths 2026-04-01 16:39:08 +01:00
Vincent Koc
67f8dc5712 fix(discord): avoid duplicate component id exports 2026-04-02 00:34:48 +09:00
Vincent Koc
0b06c4b352 perf(test): narrow telegram draft chunking imports 2026-04-02 00:32:13 +09:00
Vincent Koc
cb7e391285 fix(discord): restore component custom id barrel exports 2026-04-02 00:31:13 +09:00
Vincent Koc
ec426ac356 perf(test): narrow slack string normalization imports 2026-04-02 00:29:50 +09:00
Peter Steinberger
b569f5d313 fix: normalize local npm publish tarball paths 2026-04-01 16:25:37 +01:00
Vincent Koc
b6ba45c4a4 perf(test): lazy-load discord reply runtime 2026-04-02 00:18:45 +09:00
Vincent Koc
19724340f8 perf(test): narrow line reply context import 2026-04-02 00:16:03 +09:00
Peter Steinberger
2988a68b70 ci: skip reused npm publish metadata check 2026-04-01 16:12:26 +01:00
Vincent Koc
913a9ba367 perf(test): narrow discord auth runtime imports 2026-04-02 00:00:26 +09:00
Menglin Li
d5554491a8 fix(msteams): prevent path-to-regexp crash with express 5 (#55440)
Fixes #55250, #54960, #54889, #54852, #54703

thanks @lml2468
2026-04-01 10:00:06 -05:00
Peter Steinberger
181aef5cbe ci: skip npm publish validation when reusing preflight 2026-04-01 15:59:04 +01:00
Vincent Koc
c942812e5d perf(test): narrow discord component import path 2026-04-01 23:58:06 +09:00
Vincent Koc
0453d355fd perf(test): narrow discord monitor runtime seams 2026-04-01 23:47:22 +09:00
LawrenceLuo
83149ed046 fix(msteams): clear pending upload timeout on removal (#32555)
thanks @PinoHouse
2026-04-01 09:46:24 -05:00
Vincent Koc
cfe4b720a4 docs: add glm-5.1 and glm-5v-turbo to GLM model examples 2026-04-01 23:41:46 +09:00
Vincent Koc
3d647f14d0 perf(test): lazy-load discord preflight runtimes 2026-04-01 23:37:06 +09:00
Vincent Koc
3a7d0938c6 perf(test): keep discord model picker provider runtime lazy 2026-04-01 23:32:33 +09:00
Peter Steinberger
c3490b3c70 fix: keep prepared prepack logs off stdout 2026-04-01 15:31:02 +01:00
Vincent Koc
76c4ecd651 perf(test): narrow sdk seams for channel hotspots 2026-04-01 23:14:48 +09:00
Peter Steinberger
8988894ff7 build: prepare 2026.4.1-beta.1 release 2026-04-01 15:09:19 +01:00
Peter Steinberger
47be52e2cb build: bump version to 2026.4.1-beta.1 2026-04-01 15:09:19 +01:00
Vincent Koc
5a0fd1cffc fix(matrix): narrow lazy test module access 2026-04-01 23:05:51 +09:00
Vincent Koc
1d05cbba7a perf(test): trim matrix setup overhead 2026-04-01 22:56:13 +09:00
Gustavo Madeira Santana
2dab0c518a fix(regression): ship diffs viewer runtime asset 2026-04-01 09:56:07 -04:00
Peter Steinberger
4de1606f4c fix: drain discord startup restart sockets 2026-04-01 22:55:58 +09:00
Vincent Koc
78a58726e7 perf(test): trim more matrix import churn 2026-04-01 22:54:22 +09:00
Peter Steinberger
9065243729 fix: stop stale Windows gateway before upgrade onboard 2026-04-01 14:52:44 +01:00
Vincent Koc
38410705bf perf(test): trim matrix directory reload churn 2026-04-01 22:51:08 +09:00
Vincent Koc
da9ffad368 fix(channels): restore target parsing barrel seam 2026-04-01 22:46:10 +09:00
Ayaan Zaidi
095e7b830a fix: let carbon own gateway reconnects (#59019)
* refactor(discord): let carbon own gateway reconnects

* fix: finalize Discord gateway reconnect landing (#59019)
2026-04-01 19:12:35 +05:30
Vincent Koc
c5cfc05104 perf(test): trim more sdk and telegram reload churn 2026-04-01 22:40:44 +09:00
Vincent Koc
cb7c0e24d0 perf(test): trim more browser reload churn 2026-04-01 22:40:44 +09:00
Vincent Koc
e1b6c9b29b perf(test): trim more matrix and telegram reload churn 2026-04-01 22:40:44 +09:00
Vincent Koc
dd5bf6b1d0 perf(test): cut more hotspot reload churn 2026-04-01 22:40:44 +09:00
Vincent Koc
1673d969e8 test: document reload-churn guardrail 2026-04-01 22:40:31 +09:00
Peter Steinberger
b0ef107b56 docs(changelog): add missing thanks 2026-04-01 14:33:19 +01:00
Peter Steinberger
9cfb792dba docs: fix docs formatting drift 2026-04-01 14:31:28 +01:00
Vincent Koc
fd4dbad38c docs: cover cron --tools allowlist and agents.defaults.params config reference 2026-04-01 22:29:53 +09:00
Peter Steinberger
00218ac8a4 fix(auth): persist codex oauth refresh tokens 2026-04-01 14:25:33 +01:00
Peter Steinberger
af5f4f6716 docs(changelog): drop docs-only breaking note 2026-04-01 14:23:42 +01:00
Vincent Koc
c42659176a docs: cover unreleased feature gaps (Telegram errorPolicy, Android notifications, node pairing, Slack approvals, MCP transport, reactions) 2026-04-01 22:20:20 +09:00
Vincent Koc
7a7549f12f perf(test): reduce hotspot reload churn (#59033) 2026-04-01 22:19:19 +09:00
Peter Steinberger
adb961e056 docs(changelog): refresh unreleased ordering 2026-04-01 14:13:11 +01:00
Jacob Tomlinson
14a779ee8d revert: sandbox: block sensitive external bind sources (#59016)
This reverts commit 8db20c1965.
2026-04-01 14:01:05 +01:00
Ayaan Zaidi
7096819f2b fix(acpx): retry queue-owner repair without resume-session (thanks @obviyus) 2026-04-01 18:30:38 +05:30
Peter Steinberger
fc745db76d ci: remove bun workflow 2026-04-01 21:58:46 +09:00
Peter Steinberger
131f6dac37 refactor: unify failover signal classification 2026-04-01 21:50:58 +09:00
Neerav Makwana
ed482b1ce7 fix: repair queue owner session recovery (#58669) (thanks @neeravmakwana)
* fix(acpx): repair queue owner session recovery

* fix(acpx): avoid duplicate queue owner recovery

* fix: repair queue owner session recovery (#58669) (thanks @neeravmakwana)

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-04-01 18:20:23 +05:30
Neerav Makwana
cd07ebef99 fix: correct flows docs to tasks (#58690) (thanks @neeravmakwana)
* Docs: fix stale flows command references

* Docs: address flows review comments

* docs: remove stale flows subtree from cli index

* fix: correct flows docs to tasks (#58690) (thanks @neeravmakwana)

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-04-01 18:14:31 +05:30
Peter Steinberger
25e2934809 refactor: route session target matching through plugin parsers 2026-04-01 13:42:57 +01:00
Tomsun28
6433e923d4 fix: add ZAI GLM-5.1 and GLM-5V Turbo support (#58793) (thanks @tomsun28)
* provider(zai): add GLM-5.1 and GLM-5V Turbo models

* feat(zai): extract model definition builder for glm-5 forward compat

* test(zai): cover persisted glm-5 dynamic metadata

* fix: add ZAI GLM-5.1 and GLM-5V Turbo support (#58793) (thanks @tomsun28)

* fix: preserve ZAI dynamic model transport config (#58793) (thanks @tomsun28)

---------

Co-authored-by: gongchao <chao.gong@aminer.cn>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-04-01 18:09:02 +05:30
Peter Steinberger
33fbd9b770 refactor: dedupe auth profile store normalization 2026-04-01 13:37:37 +01:00
Peter Steinberger
ab3c646bb1 fix: preserve telegram exec approval topic routing 2026-04-01 13:34:50 +01:00
Peter Steinberger
f55b6b1acf fix: add changelog for auth profile store load crash (#58923) (thanks @openperf) 2026-04-01 21:33:10 +09:00
openperf
ac68321d4d fix(auth-profiles ): ensure credential key and token are strings to prevent crash
Fixes #58861
2026-04-01 21:33:10 +09:00
Peter Steinberger
cd4b03c568 fix: unify structured provider failover classification (#58856) (thanks @aaron-he-zhu) 2026-04-01 21:31:18 +09:00
Aaron Zhu
c96ee42300 fix(agents): normalize provider errors for better failover
Add provider-specific error patterns for AWS Bedrock, Ollama, Mistral,
Cohere, DeepSeek, Together AI, and Cloudflare Workers AI. These providers
return errors in non-standard formats that the generic classifiers miss,
causing incorrect failover behavior (e.g., context overflow misclassified
as format error, ThrottlingException not recognized as rate limit).

Wire provider patterns into isContextOverflowError() and
classifyFailoverReason() as catch-all layers after generic classifiers.
2026-04-01 21:31:18 +09:00
Chinar Amrutkar
74b9f22a42 fix: add Telegram error suppression controls (#51914) (thanks @chinar-amrutkar)
* feat(telegram): add error policy for suppressing repetitive error messages

Introduces per-account error policy configuration that can suppress
repetitive error messages (e.g., 429 rate limit, ECONNRESET) to
prevent noisy error floods in Telegram channels.

Closes #34498

* fix(telegram): track error cooldown per message

* fix(telegram): prune expired error cooldowns

* fix: add Telegram error suppression controls (#51914) (thanks @chinar-amrutkar)

---------

Co-authored-by: chinar-amrutkar <chinar-amrutkar@users.noreply.github.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-04-01 17:52:28 +05:30
Ayaan Zaidi
ef286987e7 test: fix context pruning runtime fixtures 2026-04-01 16:48:00 +05:30
Ayaan Zaidi
f70ad924a6 fix: align cache-ttl pruning with thinking replay sanitization 2026-04-01 16:33:57 +05:30
Vincent Koc
c7510e0f1a fix(hooks): skip full gate for docs-only commits 2026-04-01 20:02:54 +09:00
Ayaan Zaidi
c65e152b39 fix: preserve anthropic thinking replay (#58916)
* test: add anthropic thinking replay regressions

* fix: preserve anthropic thinking blocks on replay

* fix: preserve anthropic thinking replay (#58916)

* fix: move anthropic replay changelog entry (#58916)
2026-04-01 16:23:47 +05:30
Vincent Koc
00a49fe8b4 docs: add gateway.webchat.chatHistoryMaxChars config reference 2026-04-01 19:25:17 +09:00
Charles Dusek
32ae841098 feat(web-search): add SearXNG as bundled web search provider plugin (#57317)
* feat(web-search): add bundled searxng plugin

* test(web-search): cover searxng config wiring

* test(web-search): include searxng in bundled provider inventory

* test(web-search): keep searxng ordering aligned

* fix(web-search): sanitize searxng result rows

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-04-01 19:24:33 +09:00
Chinar Amrutkar
3f67581e50 fix: retry safe wrapped Telegram send failures (#51895) (thanks @chinar-amrutkar)
* fix(telegram): traverse error .cause chain in formatErrorMessage and match grammY HttpError

grammY wraps network failures in HttpError with message
'Network request for ... failed!' and the original error in .cause.
formatErrorMessage only checked err.message, so shouldRetry never
fired for the most common transient failure class.

Changes:
- formatErrorMessage now traverses .cause chain, appending nested
  error messages (with cycle protection)
- Added 'Network request' to TELEGRAM_RETRY_RE as belt-and-suspenders
- Added tests for .cause traversal, circular references, and grammY
  HttpError retry behavior

Fixes #51525

* style: fix oxfmt formatting in retry-policy.ts

* fix: add braces to satisfy oxlint requirement

* fix(telegram): keep send retries strict

* test(telegram): cover wrapped retry paths

* fix(telegram): retry rate-limited sends safely

* fix: retry safe wrapped Telegram send failures (#51895) (thanks @chinar-amrutkar)

* fix: preserve wrapped Telegram rate-limit retries (#51895) (thanks @chinar-amrutkar)

---------

Co-authored-by: chinar-amrutkar <chinar-amrutkar@users.noreply.github.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-04-01 15:54:29 +05:30
Vincent Koc
5c8d9da749 docs: add SearXNG web search provider page and navigation 2026-04-01 19:18:15 +09:00
Vincent Koc
f1595f59b4 fix(ci): allow plugin npm preview without publish token (#58929) 2026-04-01 19:16:46 +09:00
upupc
d766bfc6b2 fix(memory): preserve session indexing during full reindex (#39732)
Merged via squash.

Prepared head SHA: 0dbaf5fffb
Co-authored-by: upupc <12829489+upupc@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-04-01 13:12:30 +03:00
Luke
1654c3a851 feat(gateway): make chat history max chars configurable (#58900)
* feat(gateway): make chat history max chars configurable

* fix(gateway): address review feedback

* docs(changelog): note configurable chat history limits
2026-04-01 21:08:37 +11:00
ImLukeF
b2bb129e2c docs(changelog): note chat error fallback fix 2026-04-01 20:53:35 +11:00
ImLukeF
78b48735fa test: restore fetch stubs in embedding suites 2026-04-01 20:53:16 +11:00
ImLukeF
101c31f5e1 test: harden ci-sensitive unit suites 2026-04-01 20:53:16 +11:00
ImLukeF
4e63dc0b1c fix: hide raw provider errors from chat replies 2026-04-01 20:53:16 +11:00
ryanlee-gemini
fbe3ca4d7d fix(plugins): pass dangerouslyForceUnsafeInstall through archive and … (#58879)
Merged via squash.

Prepared head SHA: 87eb27d902
Co-authored-by: ryanlee-gemini <181323138+ryanlee-gemini@users.noreply.github.com>
Co-authored-by: odysseus0 <8635094+odysseus0@users.noreply.github.com>
Reviewed-by: @odysseus0
2026-04-01 02:52:01 -07:00
Vincent Koc
72af92ba4e qqbot: require explicit allowlist for /bot-logs to prevent info disclosure (#58895)
* qqbot: harden /bot-logs authorization fallback

* fix(qqbot): harden bot logs allowlist guard

* fix(qqbot): normalize bot logs allowlist entries
2026-04-01 18:40:46 +09:00
Vincent Koc
07c60ae461 fix(agent): treat webchat exec approvals as native UI (#58904)
* fix(agent): treat webchat exec approvals as native UI

* docs(changelog): note webchat exec approval UI fix

* test(agent): cover webchat native approval guidance
2026-04-01 18:36:21 +09:00
Peter Steinberger
8b2d24b62b docs(security): clarify node pairing trust boundary 2026-04-01 18:27:23 +09:00
Peter Steinberger
29784af1e2 style(gateway): normalize node reconnect formatting 2026-04-01 18:27:06 +09:00
Peter Steinberger
f6317fb747 fix(gateway): stop pinning node commands to pairing state 2026-04-01 18:27:06 +09:00
Peter Steinberger
fe57ee513f test: drop stale task boundary allowlist entries 2026-04-01 10:17:47 +01:00
Peter Steinberger
d005cc8b42 test: align cron abort regression with #58833 2026-04-01 10:17:47 +01:00
Peter Steinberger
92f1772e93 test: allow boundary test on main 2026-04-01 10:17:47 +01:00
Peter Steinberger
f559ea126d fix: land slash command metadata parsing (#58725) (thanks @Mlightsnow) 2026-04-01 10:17:47 +01:00
HansY
3b1f8e3461 fix: strip inbound metadata before slash command detection (#58674)
Slash commands like /model and /new were silently ignored when the inbound
message body included metadata prefix blocks (Conversation info, Sender info,
timestamps) injected by buildInboundUserContextPrefix. The command detection
functions (hasControlCommand, isControlCommandMessage, parseSendPolicyCommand)
now call stripInboundMetadata before normalizeCommandBody so embedded slash
commands are correctly recognized.
2026-04-01 10:17:20 +01:00
Ayaan Zaidi
fb28b02540 fix: preserve bundled channel plugin compat (#58873)
* fix: preserve bundled channel plugin compat

* fix: preserve bundled channel plugin compat (#58873)

* fix: scope channel plugin compat to bundled plugins (#58873)
2026-04-01 14:42:36 +05:30
Vincent Koc
2d53ffdec1 fix(exec): resolve remote approval regressions (#58792)
* fix(exec): restore remote approval policy defaults

* fix(exec): handle headless cron approval conflicts

* fix(exec): make allow-always durable

* fix(exec): persist exact-command shell trust

* fix(doctor): match host exec fallback

* fix(exec): preserve blocked and inline approval state

* Doctor: surface allow-always ask bypass

* Doctor: match effective exec policy

* Exec: match node durable command text

* Exec: tighten durable approval security

* Exec: restore owner approver fallback

* Config: refresh Slack approval metadata

---------

Co-authored-by: scoootscooob <zhentongfan@gmail.com>
2026-04-01 02:07:20 -07:00
Peter Steinberger
4ceb01f9ed docs(gateway): document node pairing repair flow 2026-04-01 18:02:56 +09:00
Peter Steinberger
db0cea5689 refactor(gateway): extract node pairing reconciliation 2026-04-01 18:02:31 +09:00
Peter Steinberger
4590ac31cc fix: note exec approval continuation in changelog (#58860) (thanks @Nanako0129) 2026-04-01 17:56:55 +09:00
nanakotsai
7f53c1ca00 test(exec): cover delayed Discord approval continuation 2026-04-01 17:56:55 +09:00
nanakotsai
63da2c7034 fix(exec): resume agent session after approval completion 2026-04-01 17:56:55 +09:00
Forgely3D
4fa11632b4 fix: escalate to model fallback after rate-limit profile rotation cap (#58707)
* fix: escalate to model fallback after rate-limit profile rotation cap

Per-model rate limits (e.g. Anthropic Sonnet-only quotas) are not
relieved by rotating auth profiles — if all profiles share the same
model quota, cycling between them loops forever without falling back
to the next model in the configured fallbacks chain.

Apply the same rotation-cap pattern introduced for overloaded_error
(#58348) to rate_limit errors:

- Add `rateLimitedProfileRotations` to auth.cooldowns config (default: 1)
- After N profile rotations on a rate_limit error, throw FailoverError
  to trigger cross-provider model fallback
- Add `resolveRateLimitProfileRotationLimit` helper following the same
  pattern as `resolveOverloadProfileRotationLimit`

Fixes #58572

* fix: cap prompt-side rate-limit failover (#58707) (thanks @Forgely3D)

* fix: restore latest-main gates for #58707

---------

Co-authored-by: Ember (Forgely3D) <ember@forgely.co>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-04-01 17:54:10 +09:00
Vincent Koc
8fce663861 fix(subagents): harden task-registry lifecycle writes (#58869)
* fix(subagents): harden task-registry lifecycle writes

* chore(changelog): note subagent task-registry hardening

* fix(subagents): align lifecycle terminal timestamps

* fix(subagents): sanitize lifecycle warning metadata
2026-04-01 17:50:52 +09:00
sandpile
1ce410a7be fix(sandbox): use browser image for browser runtime matching (#58759)
* fix(sandbox): compare browser runtimes against sandbox browser image

* refactor(sandbox): route docker runtime matching by config label kind

* fix(sandbox): tag browser runtime removals correctly

* test(sandbox): share docker backend test config

* fix(sandbox): normalize browser runtime image matching

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-04-01 17:44:52 +09:00
scoootscooob
10750fb80e Cron: avoid busy-wait drift for recurring main jobs (#58872) 2026-04-01 01:31:21 -07:00
Peter Steinberger
2b67f96895 docs(anthropic): note oauth context1m fallback 2026-04-01 09:21:49 +01:00
Peter Steinberger
32f392eda4 refactor(core): drop old anthropic stream wrapper file 2026-04-01 09:21:48 +01:00
Peter Steinberger
59c23dee09 refactor(anthropic): move stream wrappers into plugin 2026-04-01 09:21:48 +01:00
Peter Steinberger
2bc8a0d67c refactor: add doctor cron migration helpers 2026-04-01 17:06:24 +09:00
Peter Steinberger
19d0c2dd1d refactor: remove cron legacy delivery from runtime 2026-04-01 17:06:01 +09:00
Vincent Koc
2d79c9cb16 docs: add WhatsApp reactionLevel and Feishu Drive comment actions 2026-04-01 16:56:47 +09:00
Peter Steinberger
95182d51cc fix: harden bundled plugin runtime deps 2026-04-01 08:55:00 +01:00
Vincent Koc
edfac5f2df docs(changelog): note line runtime packaging fix 2026-04-01 13:20:50 +05:30
Vincent Koc
d4643e06bd fix(line): resolve dist runtime contract path 2026-04-01 13:20:50 +05:30
Vincent Koc
facdeb3432 feat(tasks): add chat-native task board (#58828) 2026-04-01 16:48:36 +09:00
Peter Steinberger
7cf8ccf9b3 fix: avoid startup gateway reload loop (#58678) (thanks @yelog) 2026-04-01 16:47:55 +09:00
Vincent Koc
71f341c4b4 docs: add /tasks chat command, cleanup-aware status, and QQ Bot troubleshooting 2026-04-01 16:46:04 +09:00
Peter Steinberger
f5431bc07e docs: clarify doctor cron migration guidance 2026-04-01 16:44:10 +09:00
Peter Steinberger
802bdb099e refactor: move cron legacy delivery migration to doctor 2026-04-01 16:44:10 +09:00
Peter Steinberger
31ed09bc96 fix: run bundled deps postinstall for global npm 2026-04-01 08:38:24 +01:00
Vincent Koc
cfa307baed fix(status): keep task snapshots pure 2026-04-01 16:36:57 +09:00
Ayaan Zaidi
5a95d65f1e fix: restore bundled runtime dependency provisioning (#58782) (thanks @obviyus)
* fix: restore bundled runtime dependency provisioning

* fix: ship npm runner in packed installs

* fix: address bundled runtime staging review feedback

* fix: include npm runner in docker build contexts

* fix: restore bundled runtime dependency provisioning (#58782) (thanks @obviyus)

* fix: allow caret specs through windows npm cmd (#58782) (thanks @obviyus)
2026-04-01 13:03:36 +05:30
Peter Steinberger
86b519850e refactor: consolidate cron delivery boundary parsing 2026-04-01 16:31:51 +09:00
Vincent Koc
340c99d657 fix(status): filter stale task rows from status cards (#58810)
* fix(status): filter stale task rows

* test(status): use real task snapshot semantics

* fix(status): prefer failure task context in recent failures
2026-04-01 16:19:02 +09:00
Peter Steinberger
9ab3352b1a fix: avoid duplicate discord resolve logs 2026-04-01 08:14:54 +01:00
Peter Steinberger
622b91d04e fix: queue model switches behind busy runs 2026-04-01 16:14:10 +09:00
Peter Steinberger
6776306387 fix: preserve telegram topic delivery routing (#58489) (thanks @cwmine) 2026-04-01 16:13:24 +09:00
yi-bot
e643ba2f5e fix: preserve telegram topic routing in announce and delivery context 2026-04-01 16:13:24 +09:00
wittam-01
1b94e8ca14 feat: feishu comment event (#58497)
Merged via squash.

Prepared head SHA: a9dfeb0d62
Co-authored-by: wittam-01 <271711640+wittam-01@users.noreply.github.com>
Co-authored-by: odysseus0 <8635094+odysseus0@users.noreply.github.com>
Reviewed-by: @odysseus0
2026-04-01 00:12:38 -07:00
chi
cad3da52c9 fix(memory): prefer --mask over --glob for qmd collection pattern flag (#58736)
* fix(memory): prefer --mask over --glob for qmd collection pattern flag

qmd 2.0.1 silently ignores the --glob flag when creating collections,
causing all patterns (e.g. MEMORY.md, memory.md) to fall back to the
default **/*.md glob. This leads to collection conflicts when multiple
collections target the same workspace directory with different patterns.

The existing flag negotiation logic in addCollection() tries --glob
first (when collectionPatternFlag is null), and since qmd accepts the
flag without error, OpenClaw never falls back to --mask. The result is
that memory-root-{agent} gets created with **/*.md instead of MEMORY.md,
and memory-alt-{agent} fails with a duplicate path+pattern conflict.

Fix: default collectionPatternFlag to '--mask' so the working flag is
tried first. The fallback to --glob is preserved for older qmd versions
that may not support --mask.

* docs(changelog): note qmd collection flag fix

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-04-01 16:11:56 +09:00
Peter Steinberger
bd6c017192 fix: skip failing gateway HTTP stages (#58746) (thanks @yelog) 2026-04-01 16:09:36 +09:00
yelog
ffa1e5fa92 test: assert console.error in async-rejection stage test 2026-04-01 16:09:36 +09:00
yelog
0a636aef24 fix: catch per-stage errors in HTTP request pipeline to prevent cascade 500s (#58689) 2026-04-01 16:09:36 +09:00
Peter Steinberger
d9a2690535 test: trim mattermost setup cases 2026-04-01 08:03:26 +01:00
Peter Steinberger
035028208f test: trim line webhook/slack setup prompts 2026-04-01 08:02:26 +01:00
Peter Steinberger
e441e8bb17 test: stabilize timed-heavy channel planner checks 2026-04-01 07:46:25 +01:00
Peter Steinberger
709668ccd1 test: trim line/twitch setup validations 2026-04-01 07:46:25 +01:00
Peter Steinberger
add54e1d26 test: trim low-signal matrix monitor tests 2026-04-01 07:46:25 +01:00
Peter Steinberger
25eaebb9b6 test: drop duplicate telegram/discord command tests 2026-04-01 07:46:25 +01:00
Peter Steinberger
c130ebad35 fix: verify linux gateway in parallels smoke 2026-04-01 07:38:49 +01:00
Tars
5f3737f229 fix: auto-enable minimax plugin for API key auth route (#57127)
Merged via squash.

Prepared head SHA: 5782b26738
Co-authored-by: tars90percent <252094836+tars90percent@users.noreply.github.com>
Co-authored-by: odysseus0 <8635094+odysseus0@users.noreply.github.com>
Reviewed-by: @odysseus0
2026-03-31 22:52:19 -07:00
Neerav Makwana
26a891aaeb fix: preserve rewritten stream snapshots in webchat (#58641) (thanks @neeravmakwana) 2026-04-01 11:09:19 +05:30
Kenny Xie
e1d963ed2e fix: bound discord inbound media downloads (#58593) (thanks @aquaright1)
* fix(discord): bound attachment downloads by timeout

* fix(ci): unblock check and clarify discord timeouts

* fix: bound discord inbound media downloads (#58593) (thanks @aquaright1)

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-04-01 10:59:58 +05:30
Josh Lehman
5836ddea3f test: fix amazon-bedrock extension test boundary (#58753)
* Tests: stop amazon-bedrock from importing private core plugin types

* Config: refresh generated doc baseline
2026-03-31 22:24:38 -07:00
Marcus Castro
ac6db066d3 feat(whatsapp): add reaction guidance levels (#58622)
* WhatsApp: add reaction guidance policy

* WhatsApp: expose reaction guidance to agents
2026-04-01 01:42:10 -03:00
Owen Wang
21403a3898 fix(whatsapp): pass Timestamp to finalizeInboundContext (#58590)
Merged via squash.

Prepared head SHA: 74aa9a1408
Co-authored-by: Maninae <9339187+Maninae@users.noreply.github.com>
Co-authored-by: mcaxtr <7562095+mcaxtr@users.noreply.github.com>
Reviewed-by: @mcaxtr
2026-04-01 01:02:23 -03:00
Ayaan Zaidi
2c5796c924 fix(tasks): recheck current state during maintenance sweep 2026-04-01 09:25:38 +05:30
joshavant
ccb67bd4bf config: regenerate base config schema baseline 2026-03-31 22:54:36 -05:00
joshavant
ed83d79a05 fix: tighten reply payload typing and safe text coercion 2026-03-31 22:54:36 -05:00
Ayaan Zaidi
05c311e67d fix: record task sweep gateway hang fix (#58670) (thanks @openperf) 2026-04-01 09:12:57 +05:30
Ayaan Zaidi
2dbfd4ebe2 refactor(tasks): distill task registry sweep scheduling 2026-04-01 09:12:57 +05:30
openperf
97fd6c27a1 fix(tasks): prevent synchronous task registry sweep from blocking event loop 2026-04-01 09:12:57 +05:30
Jamil Zakirov
69685f99fe fix: preserve Telegram local Bot API MIME types (#54603) (thanks @jzakirov)
* fix(telegram): preserve content type for local Bot API media files

* fix: preserve Telegram local Bot API MIME types (#54603) (thanks @jzakirov)

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-04-01 09:08:31 +05:30
Peter Steinberger
098125e998 test: merge channel reply pipeline typing cases 2026-04-01 03:26:24 +01:00
Peter Steinberger
7ae093cf0f test: merge command auth cases 2026-04-01 03:25:39 +01:00
Peter Steinberger
ba808573ef test: merge allowlist config helper cases 2026-04-01 03:24:41 +01:00
Peter Steinberger
a217e97fe5 test: merge approval renderer cases 2026-04-01 03:23:41 +01:00
Peter Steinberger
cf3d7c8d57 test: merge account status helper cases 2026-04-01 03:22:33 +01:00
Peter Steinberger
d11df8e13e test: merge approval auth helper cases 2026-04-01 03:21:32 +01:00
Peter Steinberger
d65c290748 test: merge temp download path cases 2026-04-01 03:20:28 +01:00
Peter Steinberger
fbca5bcc12 test: merge status helper default/explicit cases 2026-04-01 03:19:35 +01:00
Peter Steinberger
cb131a7938 test: merge dm allowlist pairing policy cases 2026-04-01 03:18:35 +01:00
Peter Steinberger
54f2c8e939 test: merge mattermost setup registration checks 2026-04-01 03:17:10 +01:00
Peter Steinberger
655d52815d test: merge channel send result stamping coverage 2026-04-01 03:16:06 +01:00
Peter Steinberger
6e2738ef00 test: merge kilocode provider registration coverage 2026-04-01 03:13:26 +01:00
Peter Steinberger
a59f2f43b6 test: drop thread-ownership hook registration smoke 2026-04-01 03:12:19 +01:00
Peter Steinberger
3c6e0cfe25 test: drop feishu plugin registration smoke 2026-04-01 03:11:33 +01:00
Peter Steinberger
8076c78b2e test: drop subagent hook registration smokes 2026-04-01 03:10:47 +01:00
Peter Steinberger
5e371fe875 test: drop discord command registration smoke 2026-04-01 03:09:52 +01:00
Peter Steinberger
6e773cc3b6 test: drop webhook registration smokes 2026-04-01 03:08:33 +01:00
Peter Steinberger
35c9372dc4 test: merge diffs registration smoke into config defaults 2026-04-01 03:05:46 +01:00
Peter Steinberger
5c27f15fe6 test: drop browser plugin registration smoke 2026-04-01 03:03:27 +01:00
Peter Steinberger
4765ce3ad7 test: drop low-signal extension registration smokes 2026-04-01 03:02:40 +01:00
Peter Steinberger
042a9ab48a test: fix plugin-sdk subpaths contract imports 2026-04-01 03:02:34 +01:00
Peter Steinberger
73ead2425b test: drop redundant web search registration smokes 2026-04-01 02:57:08 +01:00
Peter Steinberger
49ac85b56d test: merge secret input schema coverage 2026-04-01 02:53:40 +01:00
Peter Steinberger
5816294b4c test: merge request-url coverage into fetch auth 2026-04-01 02:52:44 +01:00
Peter Steinberger
08bbb51bf7 test: merge allowlist resolution coverage 2026-04-01 02:51:26 +01:00
Peter Steinberger
f5a23b710c test: move plugin-sdk index and root alias guardrails 2026-04-01 02:50:22 +01:00
Peter Steinberger
016f065d7e test: move remaining plugin-sdk guardrails to contracts 2026-04-01 02:46:50 +01:00
Peter Steinberger
7e02005ca9 test: move plugin-sdk guardrails to contracts suite 2026-04-01 02:41:02 +01:00
Peter Steinberger
219116e862 test: drop redundant status-issues skip checks 2026-04-01 02:32:55 +01:00
Peter Steinberger
09c03fcfed test: drop low-signal memory plugin metadata check 2026-04-01 02:30:53 +01:00
Peter Steinberger
3c69e1ea4e test: drop low-signal plugin runtime type contract 2026-04-01 02:29:18 +01:00
Peter Steinberger
0614d992a4 test: drop redundant openai registration smoke 2026-04-01 02:26:50 +01:00
Peter Steinberger
f9c18186a8 test: move openai live smoke to live suite 2026-04-01 02:24:12 +01:00
Peter Steinberger
1226361c6d test: move memory lancedb live smoke to live suite 2026-04-01 02:18:20 +01:00
Peter Steinberger
beb2171ab5 test: move openrouter live test to live suite 2026-04-01 02:15:35 +01:00
Morrow
be5a035d97 fix: harden embedded text normalization (#58555)
Co-authored-by: Morrow <271612559+agent-morrow@users.noreply.github.com>
2026-03-31 21:10:49 -04:00
Owen Wang
50cc28c559 fix: differentiate overloaded vs rate-limit user-facing error messages (#58562) 2026-03-31 21:10:38 -04:00
dudu1111685
ed8e6b0a74 plugins: suppress provenance warning for allowlisted local plugins (#58604)
Co-authored-by: me <shlomo@vmi1916417.contaboserver.net>
2026-03-31 21:10:30 -04:00
Zhang
d2663262d4 Fix broken URL in Twitch extension README (#58563)
Remove stray `%20` (URL-encoded space) from the StreamWeasels username-to-ID
converter link, which caused a 404 when clicked.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 21:10:28 -04:00
Andy Tien
6c3eea3ce9 fix(session): prevent heartbeat/cron/exec events from triggering session reset (#58605)
Fixes #58409 - Heartbeat system causes silent session reset leading to user data loss.

The issue occurred when automated system events (heartbeat, cron-event, exec-event)
triggered the session initialization logic, which evaluated session freshness based on
idle/daily reset policies. Stale sessions were reset, causing complete context loss.

Changes:
- Detect system event providers (heartbeat, cron-event, exec-event) in initSessionState
- Force freshEntry=true for system events to skip reset policy evaluation
- Add comprehensive test coverage for heartbeat no-reset behavior

This ensures automated check-ins preserve session continuity and never cause
accidental data loss.
2026-03-31 21:10:24 -04:00
OfflynAI
b554516f21 routing: support wildcard peer bindings (peer.id="*") for multi-agent routing (#58609)
* routing: support wildcard peer bindings (peer.id="*") for multi-agent routing

Bindings with `peer: { kind: "direct", id: "*" }` were treated as a literal
peer ID "*" instead of a wildcard. This caused the binding to be indexed
exclusively in the byPeer map under key "direct:*", which never matches
actual incoming peer IDs like "direct:12345678". The binding silently fell
through to the default agent ("main"), breaking multi-agent setups that use
wildcard peer constraints to route all DMs on a named account to a specific
agent.

Add a "wildcard-kind" peer constraint state that restricts on chat type
(direct/group/channel) without requiring an exact peer ID match. Wildcard
peer bindings now fall through to the byAccount/byChannel index tiers and
correctly match via matchesBindingScope with kind-only filtering.

Resolves #58546

Made-with: Cursor

* routing: add dedicated binding.peer.wildcard tier for clarity

Address Greptile feedback: wildcard-peer bindings now report
matchedBy: "binding.peer.wildcard" instead of "binding.account",
making logs/debugging clearer for operators.

- Add byPeerWildcard index bucket
- Add binding.peer.wildcard tier between peer.parent and guild+roles
- Update tests to expect the new matchedBy value

Made-with: Cursor
2026-03-31 21:10:18 -04:00
Reed
b86f5d5ea4 fix(sandbox): resolve pinned fs helper python without PATH (#58573) 2026-03-31 21:10:17 -04:00
zssggle-rgb
a37c66906c fix(acpx): retry backend health probes after ensure (#58612)
* fix(acpx): retry backend health probes after ensure

* fix(acpx): keep doctor checks diagnostic-only
2026-03-31 21:10:09 -04:00
zssggle-rgb
8e0f495197 fix(acpx): preserve control command error details (#58613) 2026-03-31 21:10:04 -04:00
Sharoon Sharif
7941f21bef fix(voice-call): clear connection timeout on successful STT connect (#58586)
The 10-second connection timeout in OpenAIRealtimeSTTSession.doConnect()
was never cleared on success or teardown, leaking a timer on every
connection and accumulating stale timers across reconnect cycles.

Store the timeout handle and clear it in both the open handler and
close(), matching the existing clearTimeout pattern in
waitForTranscript().

Co-authored-by: Sharoon Sharif <ssharif@Hosanna.local>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 21:10:02 -04:00
zssggle-rgb
187d3ed053 fix(acpx): fall back to PATH node for shebang wrappers (#58614) 2026-03-31 21:09:58 -04:00
Michael Flanagan
eee185af99 feat(amazon-bedrock): add Bedrock Guardrails support (#58588)
* feat(amazon-bedrock): just the kiro plans, need to remove before PR

* docs(bedrock-guardrails): add environment setup instructions

* docs(bedrock-guardrails): mark environment setup tasks as completed

* feat(amazon-bedrock): add trace configuration to guardrail settings

* feat(amazon-bedrock): implement guardrail wrapper factory and wire into registration

* test(amazon-bedrock): add comprehensive guardrail configuration tests

* docs(bedrock): add guardrails configuration documentation

* docs(bedrock-guardrails): add comprehensive manual testing guide for Docker deployment

* docs(bedrock-guardrails): expand manual testing guide with STS credentials and config options

* docs(bedrock-guardrails): complete manual testing verification with 8 test scenarios

* chore: remove kiro spec files from PR

* fix(docs): correct guardrail config path to plugins.entries.*.config

* style: format docs and test files
2026-03-31 21:09:52 -04:00
狼哥
40b24dfa6b fix(session-status): infer custom runtime providers from config (#58474)
* fix(session-status): infer custom runtime providers from config

* test(session-status): satisfy custom provider type checks
2026-03-31 21:09:42 -04:00
Han Yang
547154865b Fix: live session model switch no longer blocks failover (Resolves #58466) (#58589)
* fix: prevent infinite retry loop when live session model switch blocks failover (#58466)

* fix: remove unused resolveOllamaBaseUrlForRun import after rebase
2026-03-31 21:09:41 -04:00
Phillip Hampton
350fe63bbf feat(macos): Voice Wake option to trigger Talk Mode (#58490)
* feat(macos): add "Trigger Talk Mode" option to Voice Wake settings

When enabled, detecting a wake phrase activates Talk Mode (full voice
conversation: STT -> LLM -> TTS playback) instead of sending a text
message to the chat. Enables hands-free voice assistant UX.

Implementation:
- Constants.swift: new `openclaw.voiceWakeTriggersTalkMode` defaults key
- AppState.swift: new property with UserDefaults persistence + runtime
  refresh on change so the toggle takes effect immediately
- VoiceWakeSettings.swift: "Trigger Talk Mode" toggle under Voice Wake,
  disabled when Voice Wake is off
- VoiceWakeRuntime.swift: `beginCapture` checks `triggersTalkMode` and
  activates Talk Mode directly, skipping the capture/overlay/forward
  flow. Pauses the wake listener via `pauseForPushToTalk()` to prevent
  two audio pipelines competing on the mic.
- TalkModeController.swift: resumes voice wake listener when Talk Mode
  exits by calling `VoiceWakeRuntime.refresh`, mirroring PTT teardown.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: address review feedback

- TalkModeController: move VoiceWakeRuntime.refresh() after
  TalkModeRuntime.setEnabled(false) completes, preventing mic
  contention race where wake listener restarts before Talk Mode
  finishes tearing down its audio engine (P1)
- VoiceWakeRuntime: remove redundant haltRecognitionPipeline()
  before pauseForPushToTalk() which already calls it via stop() (P2)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: resume wake listener based on enabled state, not toggle value

Check swabbleEnabled instead of voiceWakeTriggersTalkMode when deciding
whether to resume the wake listener after Talk Mode exits. This ensures
the paused listener resumes even if the user toggled "Trigger Talk Mode"
off during an active session. (P2)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: play trigger chime on Talk Mode activation + send chime before TTS

- VoiceWakeRuntime: play the configured trigger chime in the
  triggersTalkMode branch before pausing the wake listener. The early
  return was skipping the chime that plays in the normal capture flow.
- TalkModeRuntime: play the Voice Wake "send" chime before TTS playback
  starts, giving audible feedback that the AI is about to respond. Talk
  Mode never used this chime despite it being configurable in settings.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: move send chime to transcript finalization (on send, not on reply)

The send chime now plays when the user's speech is finalized and about
to be sent to the AI, not when the TTS response starts. This matches
the semantics of "send sound" -- it confirms your input was captured.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 21:09:36 -04:00
Han Yang
68ee3113a9 Fix: CDP profiles prefer cdpPort over stale WebSocket cdpUrl (Resolves #58494) (#58499)
* fix(browser): prefer cdpPort over stale WebSocket cdpUrl for attach-only profiles

* fix(browser): preserve profile host when dropping stale devtools WS path
2026-03-31 21:09:31 -04:00
Kenny Xie
2650ce31fc fix(media): resolve relative MEDIA paths against agent workspace (#58624)
* fix(media): resolve relative MEDIA: paths against agent workspace dir

* fix(agents): remove stale ollama compat import

* fix(media): preserve workspace dir in outbound access
2026-03-31 21:09:28 -04:00
ryanngit
0b3d31c0ce feat(auth): WHAM-aware Codex cooldown for multi-profile setups (#58625)
Instead of exponential backoff guesses on Codex 429, probe the WHAM
usage API to determine real availability and write accurate cooldowns.

- Burst/concurrency contention: 15s circuit-break
- Genuine rate limit: proportional to real reset time (capped 2-4h)
- Expired token: 12h cooldown
- Dead account: 24h cooldown
- Probe failure: 30s fail-open

This prevents cascade lockouts when multiple agents share a pool of
Codex profiles, and avoids wasted retries on genuinely exhausted
profiles.

Closes #26329, relates to #1815, #1522, #23996, #54060

Co-authored-by: ryanngit <ryanngit@users.noreply.github.com>
2026-03-31 21:09:25 -04:00
Jalen
915e15c13d fix(gateway): skip restart when config.patch has no actual changes (#58502)
config.patch unconditionally writes the config file and sends SIGUSR1
even when diffConfigPaths detects zero changed paths. This causes a
full gateway restart (~10s downtime, all SSE/WebSocket connections
dropped) on every control-plane config.patch call, even when the
config is identical — e.g. a model hot-apply that doesn't change any
gateway.* paths.

Fix: when changedPaths is empty, return early with `noop: true`
without writing the file or scheduling SIGUSR1. The validated config
is still returned so the caller knows the current state.

This lets control-plane clients safely call config.patch for
idempotent updates without triggering unnecessary restarts.
2026-03-31 21:09:23 -04:00
Rico0919x
ee42e44d88 fix(auth): add qwen-dashscope and anthropic-openai to known API key env vars (#58503) 2026-03-31 21:09:20 -04:00
Andy
4d8c07b97c feat(cron): add --tools flag for per-job tool allow-list (#58504)
Add toolsAllow field to cron agent-turn payloads, enabling users to
restrict which tool schemas are sent to the model for a given cron job.

When --tools is set:
- Only listed tools are included in the provider request
- promptMode is forced to 'minimal' (strips skills catalog, reply tags,
  heartbeat, messaging, docs, memory, model aliases, silent replies)
- Dramatically reduces input tokens for small local models (~16K to ~800)

CLI surface:
- openclaw cron add --tools exec,read,write
- openclaw cron edit <id> --tools exec
- openclaw cron edit <id> --clear-tools (remove allow-list)

Closes #58435

Co-authored-by: andyk-ms <andyk-ms@users.noreply.github.com>
2026-03-31 21:09:17 -04:00
Qkal
3a52b475ab Docs: clarify first-contribution fallback when no good-first-issue labels are open (#58530) 2026-03-31 21:09:10 -04:00
Lee Pender
8b6b4b18a8 feat: add agents.defaults.params for global default provider params (#58548)
Allow setting provider params (e.g. cacheRetention) once at the
agents.defaults level instead of repeating them per-model. The merge
order is now: defaults.params -> defaults.models[key].params ->
list[agentId].params.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 21:09:07 -04:00
hcl
b8fea43bf2 fix(gateway): return default scopes when trusted HTTP request has no scope header (#58603)
resolveTrustedHttpOperatorScopes() returns [] when the x-openclaw-scopes
header is absent, even for trusted requests (--auth none). This causes
403 "missing scope: operator.write" on /v1/chat/completions.

Root cause: src/gateway/http-utils.ts:138-140. PR #57783 (f0af18672)
replaced the old resolveGatewayRequestedOperatorScopes which had an
explicit fallback to CLI_DEFAULT_OPERATOR_SCOPES when no header was
present. The new function treats absent header the same as empty header
— both return [].

Fix: distinguish absent header (undefined → return defaults) from empty
header ("" → return []). Trusted clients without an explicit scope
header get the default operator scopes, matching pre-#57783 behavior.

Closes #58357

Signed-off-by: HCL <chenglunhu@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-31 21:09:05 -04:00
Peter Steinberger
5b8f0cf1d5 test: centralize inbound contract suites 2026-04-01 02:04:53 +01:00
Peter Steinberger
b59adf9d2a test: fix outbound payload contract import 2026-04-01 02:02:07 +01:00
Peter Steinberger
051e31fb55 test: centralize outbound payload contracts 2026-04-01 02:01:48 +01:00
Peter Steinberger
ddf39180a4 test: remove extension dm policy wrappers 2026-04-01 01:59:22 +01:00
Peter Steinberger
2db2b078ca test: remove extension group policy wrappers 2026-04-01 01:57:18 +01:00
Peter Steinberger
aea016d02c test: fix registry-backed contract import 2026-04-01 01:54:03 +01:00
Peter Steinberger
1f97f907b2 test: centralize registry-backed channel contracts 2026-04-01 01:53:23 +01:00
Peter Steinberger
7614c45980 test: fix channel catalog contract import 2026-04-01 01:52:01 +01:00
Peter Steinberger
ba5b373ad4 test: centralize channel catalog contracts 2026-04-01 01:51:26 +01:00
Peter Steinberger
85679252c4 test: remove extension provider contract wrappers 2026-04-01 01:49:55 +01:00
Peter Steinberger
4af52a7428 test: centralize provider runtime, discovery, and auth contracts 2026-04-01 01:49:11 +01:00
Peter Steinberger
78be556299 test: consolidate plugin registration contracts 2026-04-01 01:46:56 +01:00
Peter Steinberger
63819bb383 test: consolidate provider and web-search contracts 2026-04-01 01:44:43 +01:00
Peter Steinberger
b910cc5869 test: remove extension manifest and core-extension wrappers 2026-04-01 01:44:43 +01:00
Peter Steinberger
c41df4873e test: consolidate package manifest and core-extension contracts 2026-04-01 01:44:43 +01:00
Mingxuan
302c047d86 hotfix(ollama): Show only Ollama models after provider selection (#55290)
* Fix: Add ollama to NON_PI_NATIVE_MODEL_PROVIDERS to ensure correct model selection

* Add test coverage for ollama provider to ensure models are merged correctly

* Fix test case for ollama provider by adding required model properties

* Changelog: add Ollama model picker fix

* Changelog: move Ollama fix entry to section tail

---------

Co-authored-by: Bruce MacDonald <brucewmacdonald@gmail.com>
2026-03-31 16:48:22 -07:00
Gustavo Madeira Santana
bea53d7a3f Fix: move bootstrap session grammar into plugin-owned session-key surfaces (#58400)
Merged via squash.

Prepared head SHA: b062b18b03
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
2026-03-31 19:41:01 -04:00
oliviareid-svg
bf0f33db32 fix(compaction): resolve model override in runtime context for all context engines (#56710)
Merged via squash.

Prepared head SHA: 72550aa5f0
Co-authored-by: oliviareid-svg <269669958+oliviareid-svg@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-03-31 15:25:16 -07:00
Peter Steinberger
78d1120a41 test: retry gateway acp bind warmup 2026-03-31 23:20:25 +01:00
Peter Steinberger
d771f7dcb7 fix: harden acpx live startup 2026-03-31 23:20:24 +01:00
Peter Steinberger
f21abb2151 fix: harden queue cleanup lane resolution 2026-03-31 23:20:24 +01:00
Peter Steinberger
cdaf6d5749 docs: allow sponsor table markup in markdownlint 2026-03-31 23:18:55 +01:00
Josh Lehman
adc329b26b test: dedupe extension-owned coverage (#58554)
* test: dedupe extension-owned coverage

* test: remove duplicate coverage files

* test: move helper coverage into extensions

* test: trim duplicate helper assertions

* test: remove cloudflare helper import from agent test

* test: align stale expectations with current main
2026-03-31 15:18:29 -07:00
Peter Steinberger
d7e9d341cc fix: require npm auth for dist-tag mirror 2026-03-31 23:14:19 +01:00
Peter Steinberger
4f83409345 docs: add theme-aware sponsor logos 2026-04-01 07:14:05 +09:00
Peter Steinberger
091c6105a4 style(test): format skills install test 2026-03-31 23:11:53 +01:00
Peter Steinberger
aa6cf87814 refactor(approvals): share origin target reconciliation 2026-03-31 23:11:53 +01:00
Peter Steinberger
ddce362d34 refactor(approvals): share native delivery runtime 2026-03-31 23:11:53 +01:00
Peter Steinberger
5997317c09 docs: add NVIDIA sponsor logo 2026-04-01 06:59:06 +09:00
Peter Steinberger
9ea7e06460 build: bump version to 2026.4.1 2026-03-31 22:53:17 +01:00
Peter Steinberger
313a27d82f docs: update appcast for 2026.3.31 2026-03-31 22:51:49 +01:00
Vincent Koc
11318ef9b9 fix(status): align session_status with /status 2026-04-01 06:40:50 +09:00
Vincent Koc
94d72efedc fix(slack): accept bare approve fallback 2026-04-01 06:34:01 +09:00
Vincent Koc
ee8baf6766 fix(reply): stop mention-wrapped status double replies 2026-04-01 06:32:34 +09:00
Peter Steinberger
ad06d5ab4d build: reuse release preflight artifacts 2026-04-01 06:30:36 +09:00
Gustavo Madeira Santana
6679690737 fix(regression): restore diffs viewer toolbar buttons 2026-03-31 17:26:20 -04:00
Vincent Koc
db0f7c2cd5 changelog: note discord and telegram approval UX fixes 2026-04-01 06:21:56 +09:00
Vincent Koc
b73dd9b326 fix(approvals): suppress manual native approval narration 2026-04-01 06:21:56 +09:00
Vincent Koc
211b5a51af docs(changelog): note status followups 2026-04-01 06:16:15 +09:00
Vincent Koc
e1d2b299f6 fix(reply): avoid double status replies 2026-04-01 06:12:47 +09:00
Peter Steinberger
968bc3d5b0 fix(ci): preserve workspace openclaw plugin links 2026-03-31 22:05:59 +01:00
Vincent Koc
f85aba43a9 fix(approvals): restore native DM approval behavior 2026-04-01 06:02:04 +09:00
Vincent Koc
fdad8ea3b0 fix(status): show agent-local task counts when session tasks are empty 2026-04-01 06:01:29 +09:00
Vincent Koc
93e2d0e3de build(bluebubbles): align openclaw dependency specifiers 2026-04-01 06:00:55 +09:00
Peter Steinberger
213a704b71 fix: unblock 2026.3.31 release preflight 2026-03-31 21:54:12 +01:00
Vincent Koc
44baf3bb2b build(pnpm): exclude openclaw from minimum release age 2026-04-01 05:50:35 +09:00
Vincent Koc
107fefd255 docs(changelog): note pi tui reply flush fix 2026-04-01 05:47:10 +09:00
Peter Steinberger
6f111516ef docs: refresh plugin sdk api baseline 2026-03-31 21:46:21 +01:00
Vincent Koc
f425ea06bf fix(pi): flush message-boundary block replies on message end 2026-04-01 05:44:10 +09:00
Vincent Koc
0a7024e209 test(tasks): allow status command task-registry import 2026-04-01 05:42:45 +09:00
Peter Steinberger
418fa12dfa fix: make overload failover configurable 2026-03-31 21:34:35 +01:00
Peter Steinberger
2a60e34f2a build: prepare 2026.3.31 stable release 2026-03-31 21:32:38 +01:00
Peter Steinberger
eee37bf836 fix(slack): prevent duplicate draft replies 2026-03-31 21:22:50 +01:00
Vincent Koc
fc169215d7 fix(exec): deliver approval followups directly to chat 2026-04-01 05:22:35 +09:00
Vincent Koc
58ee76fc84 feat(status): show session task counts in slash status 2026-04-01 05:09:37 +09:00
Peter Steinberger
913e7d5eba fix: correct callable plugin sdk facades 2026-03-31 21:00:52 +01:00
Vincent Koc
a4f45c55b2 fix(slack): restore callable directory facade exports 2026-04-01 04:51:36 +09:00
Peter Steinberger
ffa2143d20 build: refresh slack facade output 2026-03-31 20:40:31 +01:00
Peter Steinberger
49458fc50e fix: pin parallels version checks to packed build commit 2026-03-31 20:39:21 +01:00
Peter Steinberger
0d742c3c1b test: skip unavailable live model providers 2026-03-31 20:37:42 +01:00
Peter Steinberger
64091caf8f fix: preserve cli and slack fallback behavior 2026-03-31 20:37:42 +01:00
Peter Steinberger
9d1b443542 fix: harden live docker auth harness 2026-03-31 20:37:42 +01:00
Jacob Tomlinson
1216ecbe58 Docs: add missing changelog entries 2026-03-31 19:30:43 +00:00
Vincent Koc
6f5f0c065b fix(slack): restore callable directory facade exports 2026-04-01 04:29:10 +09:00
Vincent Koc
2e530fc2e1 revert(amazon-bedrock): restore locked aws sdk version 2026-04-01 04:26:27 +09:00
Peter Steinberger
ce58f55fe0 fix: require doctor migration for legacy web search config 2026-04-01 04:22:41 +09:00
Peter Steinberger
2001603020 fix(amazon-bedrock): pin installable aws sdk build 2026-03-31 20:19:33 +01:00
Vincent Koc
b441e59d25 fix(build): isolate bundled runtime dependency staging 2026-04-01 04:15:08 +09:00
Peter Steinberger
cc278a76a4 test(tasks): update registry seam allowlist 2026-03-31 20:08:42 +01:00
Peter Steinberger
a23b4dd5bc fix(ci): route task executor through runtime seam 2026-03-31 20:02:10 +01:00
Vincent Koc
2a72a6d507 test(tasks): allow task executor registry seam 2026-04-01 04:01:06 +09:00
Peter Steinberger
d0bcd86348 fix(tasks): restore registry build compatibility 2026-03-31 19:59:21 +01:00
Vincent Koc
bb912daaed test(openai): fix ModelRegistry constructor typing 2026-04-01 03:58:55 +09:00
Vincent Koc
1816d6a4ed fix(tasks): normalize task create compatibility 2026-04-01 03:54:42 +09:00
Vincent Koc
80ed55332d fix(tasks): restore owner-key task scope 2026-04-01 03:53:12 +09:00
Peter Steinberger
5fdde9b237 fix(tasks): restore session key registry compatibility 2026-03-31 19:48:51 +01:00
Peter Steinberger
2d2c271cdf build: exclude source maps from release tarball 2026-03-31 19:42:56 +01:00
Vincent Koc
338d313043 fix(tasks): scope shared run updates by session 2026-04-01 03:41:29 +09:00
Peter Steinberger
8fa5ac5a96 build: refresh plugin sdk api baseline 2026-03-31 19:37:05 +01:00
Peter Steinberger
6f7629995c build: refresh beta release generated artifacts 2026-03-31 19:32:55 +01:00
Peter Steinberger
91be36ca4f build: prepare 2026.3.31-beta.1 release 2026-03-31 19:32:49 +01:00
Peter Steinberger
62a39381da fix: land codex native search follow-ups (#46579) (thanks @Evizero) 2026-04-01 03:30:06 +09:00
Christof Salis
13f1190149 CLI: suppress Codex native search summary when web search is off 2026-04-01 03:30:06 +09:00
Christof Salis
0a891543c9 Codex: use model-level API for native search relevance 2026-04-01 03:30:06 +09:00
Christof Salis
797a70fd95 Codex: add native web search for embedded Pi runs 2026-04-01 03:30:06 +09:00
Peter Steinberger
b87f33c920 test(ci): deflake windows npm exec coverage 2026-03-31 19:28:11 +01:00
Vincent Koc
66413487c8 fix(tasks): make task-store writes atomic (#58521) 2026-04-01 03:23:06 +09:00
Peter Steinberger
0abd143d37 test: fix CI flakes and registry test API 2026-03-31 19:20:07 +01:00
Peter Steinberger
b7013ec207 docs: fix changelog release placement 2026-03-31 19:14:57 +01:00
Vincent Koc
cb661122e2 test(bluebubbles): trim webhook auth import cost 2026-04-01 03:12:56 +09:00
Vincent Koc
e5537f8b64 test(ci): rebalance bluebubbles webhook auth timing 2026-04-01 03:12:56 +09:00
Vincent Koc
6d59ce366e test(ci): rebalance matrix extension timings 2026-04-01 03:12:56 +09:00
Vincent Koc
3ec143254f test(ci): rebalance discord webhook activity timing 2026-04-01 03:12:56 +09:00
Vincent Koc
7cd0ff2d88 refactor(tasks): add owner-key task access boundaries (#58516)
* refactor(tasks): add owner-key task access boundaries

* test(acp): update task owner-key assertion

* fix(tasks): align owner key checks and migration scope
2026-04-01 03:12:33 +09:00
Nimrod Gutman
69fe999373 fix(pairing): restore qr bootstrap onboarding handoff (#58382) (thanks @ngutman)
* fix(pairing): restore qr bootstrap onboarding handoff

* fix(pairing): tighten bootstrap handoff follow-ups

* fix(pairing): migrate legacy gateway device auth

* fix(pairing): narrow qr bootstrap handoff scope

* fix(pairing): clear ios tls trust on onboarding reset

* fix(pairing): restore qr bootstrap onboarding handoff (#58382) (thanks @ngutman)
2026-03-31 21:11:35 +03:00
Peter Steinberger
693d17c4a2 fix: support edit tool edits[] payloads 2026-03-31 19:05:44 +01:00
Peter Steinberger
ae730d9a86 fix: cover Azure disabled reasoning omission (#58208) (thanks @jalehman) 2026-04-01 02:47:29 +09:00
Josh Lehman
a1cb2bdc57 OpenAI: omit disabled reasoning payloads
Strip `reasoning.effort: "none"` from OpenAI-compatible payloads so GPT-5 models do not receive the unsupported value when thinking is off. Cover both the shared payload wrapper path and the websocket `response.create` builder with regression tests.

Regeneration-Prompt: |
  Fix the OpenAI request-building bug where thinking set to off still forwards
  a reasoning payload with effort "none". GPT-5 mini rejects that value with a
  400, so disabled reasoning must be represented by omitting the reasoning
  parameter entirely. Keep the change additive: sanitize OpenAI-compatible
  payloads in OpenClaw’s wrapper layer, make the websocket request builder stop
  emitting a reasoning block for "none", and add focused regression tests for
  both code paths.
2026-04-01 02:47:29 +09:00
Peter Steinberger
62e13bbf21 style: format sandbox and helper files 2026-03-31 18:44:39 +01:00
Peter Steinberger
5e0e46f405 style: format config UI templates 2026-03-31 18:44:05 +01:00
Peter Steinberger
b4433a1bfe fix: normalize raw MCP schemas for OpenAI Responses (#58299) (thanks @yelog) 2026-04-01 02:30:45 +09:00
yelog
dd3796aef3 fix: normalize MCP tool schemas missing properties field for OpenAI Responses API
Tools with no parameters produce { type: "object" } schemas without a
properties field. The OpenAI Responses API rejects these, silently
crashing entire sessions.

Add properties: {} injection in normalizeToolParameters() and
convertTools() to ensure all object-type schemas include a properties
field.

Closes #58246
2026-04-01 02:30:45 +09:00
Vincent Koc
fcb802e826 refactor(plugins): remove before_install hook 2026-04-01 02:28:06 +09:00
Vincent Koc
1a313caff3 refactor(tasks): remove flow registry layer 2026-04-01 02:25:13 +09:00
Vincent Koc
76b3235207 chore(changelog): soften sdk removal note 2026-04-01 02:21:24 +09:00
Vincent Koc
5c9408d3ca docs: update docs for unreleased channel and gateway changes
Cover Teams member-info action, Teams/Matrix sender-allowlist
context filtering, macOS MagicDNS discovery preference, and
trusted-proxy mixed token config hardening.
2026-04-01 02:20:44 +09:00
Peter Steinberger
e039c72a76 fix: unblock onboard docker smoke 2026-03-31 18:12:00 +01:00
Vincent Koc
bfba84a69d chore(changelog): attribute task entries 2026-04-01 02:10:39 +09:00
Peter Steinberger
4b1c15d059 docs: simplify install policy changelog note 2026-03-31 18:09:33 +01:00
Vincent Koc
c03e2beca1 chore(changelog): rename flow entries to tasks 2026-04-01 02:08:46 +09:00
Peter Steinberger
539ba0d244 docs: merge install breaking change notes 2026-03-31 18:07:35 +01:00
Peter Steinberger
b9f8bb6308 docs: tune unreleased changelog priorities 2026-03-31 18:04:22 +01:00
Vincent Koc
d794c5ca56 chore(changelog): add missing breaking notes 2026-04-01 02:01:48 +09:00
Vincent Koc
8ef81cc983 chore(changelog): reorder unreleased sections 2026-04-01 02:00:14 +09:00
Vincent Koc
dc948bb4eb chore(changelog): sort unreleased entries 2026-04-01 01:59:05 +09:00
Peter Steinberger
cebe697082 docs: update qq bot channel docs 2026-03-31 17:55:41 +01:00
Peter Steinberger
7672e48c19 docs: fill changelog gaps since last release 2026-03-31 17:50:00 +01:00
Peter Steinberger
1e17a96983 chore: surface parallels npm-update progress 2026-03-31 17:49:24 +01:00
Peter Steinberger
28673a9388 fix: restore bundled plugin runtime deps after global install 2026-03-31 17:49:24 +01:00
Peter Steinberger
9e8129907e test(tasks): fix relative mocks in task runtime tests 2026-03-31 17:42:52 +01:00
Peter Steinberger
83038fcaf2 docs: add missing unreleased changelog entries 2026-03-31 17:42:05 +01:00
Vincent Koc
35c6b3f648 test(ci): mock googlechat action media loader 2026-04-01 01:32:24 +09:00
Peter Steinberger
8bf8baef87 Revert "refactor: move tasks into bundled plugin"
This reverts commit c75f4695b7.
2026-04-01 01:30:22 +09:00
Peter Steinberger
759d37635d Revert "refactor: move tasks behind plugin-sdk seam"
This reverts commit da6e9bb76f.
2026-04-01 01:30:22 +09:00
Vincent Koc
6f74a572d9 test(ci): fix outbound media loader seams 2026-04-01 01:17:08 +09:00
Jacob Tomlinson
8a563d603b fix(matrix): filter fetched room context by sender allowlist (#58376)
* fix(matrix): filter fetched room context by sender allowlist

* style(matrix): normalize reply context guard formatting

* fix(matrix): drop raw ids from allowlist context logs
2026-03-31 17:09:03 +01:00
Jacob Tomlinson
6c679e5f04 Gateway: reject mixed trusted-proxy token config (#58371)
* Gateway: reject mixed trusted-proxy token config

Co-authored-by: boy-hack <w8ay@qq.com>

* Gateway: fail closed for loopback trusted-proxy auth

---------

Co-authored-by: boy-hack <w8ay@qq.com>
2026-03-31 17:05:03 +01:00
Peter Steinberger
aab7335236 fix(media): restore whatsapp outbound compatibility 2026-04-01 01:00:27 +09:00
Jacob Tomlinson
78e74d4a64 Plugins: preserve prompt build system prompt precedence (#58375) 2026-03-31 16:52:09 +01:00
Peter Steinberger
1a4c9c3e85 fix: repair extension media ci coverage 2026-03-31 16:47:13 +01:00
Peter Steinberger
7d2b4ed4e1 fix: restore whatsapp runtime seams 2026-03-31 16:47:13 +01:00
Peter Steinberger
6eddd55393 test: accept media loader option expansion 2026-03-31 16:47:13 +01:00
Peter Steinberger
a842e34f15 test: require Claude 4.6 for Anthropic live selection 2026-03-31 16:41:50 +01:00
Peter Steinberger
8f2e1194b7 docs: reorder changelog by user interest 2026-03-31 16:34:45 +01:00
Peter Steinberger
43ef8a5a86 refactor(media): centralize outbound access plumbing 2026-04-01 00:32:53 +09:00
Vincent Koc
c416527df6 fix(whatsapp): restore runtime send and action seam 2026-04-01 00:25:35 +09:00
Peter Steinberger
015ab98591 fix: restore ci status fast path and whatsapp tests 2026-03-31 16:21:55 +01:00
Vincent Koc
2a1db0c0f1 fix(gateway): narrow plugin route runtime scopes (#58167)
* wip(gateway): preserve plugin route scope progress

* test(gateway): cover plugin route runtime scopes

* test(gateway): finish plugin route scope rebase

* fix(gateway): drop scopes from plugin-auth routes
2026-04-01 00:20:49 +09:00
Peter Steinberger
85611f0021 fix: tighten gateway startup plugin loading 2026-04-01 00:20:06 +09:00
Vincent Koc
1ca12ec8bf fix(hooks): rebind hook agent session keys to the target agent (#58225)
* fix(hooks): rebind hook agent session keys

* fix(hooks): preserve scoped hook session keys

* fix(hooks): validate normalized dispatch keys
2026-04-01 00:16:39 +09:00
Peter Steinberger
fc5a2f9293 fix(media): add host media read helper 2026-04-01 00:08:20 +09:00
Peter Steinberger
3bb02d3338 fix(media): align outbound sends with fs read capability 2026-04-01 00:07:50 +09:00
openperf
56b5ba0dcb fix: address security and review feedback
- Fix CWE-209: use static safe message instead of raw provider error text
- Fix CWE-117: sanitize provider/model in logs via sanitizeForLog
- Hide CLI hints from external channels via shouldSurfaceToControlUi
- Move overload cap check before advanceAuthProfile to save setup latency
- Export MAX_LIVE_SWITCH_RETRIES as module-level constant
- Use exact toBe() assertions in tests
- Correct failover decision label to fallback_model
2026-03-31 20:25:09 +05:30
openperf
1fcd179d8c fix(gateway): prevent session death loop on overloaded fallback
- Add MAX_LIVE_SWITCH_RETRIES=2 guard in agent-runner-execution.ts
- Add MAX_OVERLOAD_PROFILE_ROTATIONS=1 cap in run.ts for overloaded errors
- Return kind:final with user-visible error on retry exhaustion
- Escalate to cross-provider fallback instead of exhausting same-provider profiles

Fixes #58348
2026-03-31 20:25:09 +05:30
Peter Steinberger
bf96c67fd1 fix: align skill install security gate 2026-03-31 15:53:29 +01:00
Peter Steinberger
192484ed0a fix: log malformed tool parameters on failure 2026-03-31 15:50:14 +01:00
Peter Steinberger
7dffd8160a test(extensions): use ModelRegistry factory 2026-03-31 23:50:03 +09:00
Peter Steinberger
a1e2d2bf42 test: repair stale task and image mocks 2026-03-31 15:48:00 +01:00
Peter Steinberger
c425ef3e74 build: bump version to 2026.3.31 2026-03-31 15:48:00 +01:00
Peter Steinberger
5e30da3cad fix(exec): restore strict inline-eval allow-always reuse 2026-03-31 23:45:22 +09:00
Vincent Koc
5aac609e08 test(ci): rebalance telegram thread binding timing 2026-03-31 23:42:05 +09:00
Peter Steinberger
ac6f025c43 refactor(approvals): share telegram account binding 2026-03-31 15:39:59 +01:00
Vincent Koc
b3a2734cc9 test(ci): rebalance telegram acp binding timing 2026-03-31 23:38:45 +09:00
Vincent Koc
983891a603 fix(ci): narrow telegram route test seams 2026-03-31 23:37:18 +09:00
Peter Steinberger
461a3a4052 refactor(approvals): share request filter matching 2026-03-31 15:32:49 +01:00
Vincent Koc
7c4bffdecd fix(ci): rebalance telegram dm thread tests 2026-03-31 23:32:15 +09:00
Peter Steinberger
177687ae29 fix: adapt pi model registry calls to constructor API 2026-03-31 15:28:29 +01:00
Peter Steinberger
0d7f1e2c84 feat(security): fail closed on dangerous skill installs 2026-03-31 23:27:20 +09:00
Vincent Koc
98c0c38186 fix(ci): rebalance telegram channel tails 2026-03-31 23:24:16 +09:00
Peter Steinberger
da6e9bb76f refactor: move tasks behind plugin-sdk seam 2026-03-31 15:22:09 +01:00
Peter Steinberger
e1da91791a build: externalize bundled plugin runtime deps 2026-03-31 15:22:08 +01:00
Peter Steinberger
9537094841 test: refresh plugin sdk baseline 2026-03-31 15:22:08 +01:00
Peter Steinberger
c75f4695b7 refactor: move tasks into bundled plugin 2026-03-31 15:22:08 +01:00
Peter Steinberger
584db0aff2 fix(approvals): centralize native request binding 2026-03-31 15:20:47 +01:00
Vincent Koc
2523e25c93 test(ci): rebalance telegram implicit mention timing 2026-03-31 23:17:40 +09:00
Peter Steinberger
0ed7f1fd22 refactor: remove core WhatsApp runtime channel seam 2026-03-31 15:17:13 +01:00
Peter Steinberger
e8cb0b3659 fix: tighten live gateway empty-response skips and outbound harness typing 2026-03-31 15:17:13 +01:00
Peter Steinberger
d90b627e1b build: copy bundled plugin postinstall script into cleanup smoke image 2026-03-31 15:17:13 +01:00
Peter Steinberger
44b9936136 feat(plugins): add dangerous unsafe install override 2026-03-31 23:16:11 +09:00
Peter Steinberger
59866dd253 fix(memory): restore readonly recovery helper seams 2026-03-31 23:14:24 +09:00
Altay
ba4116e6a9 build: comment out pnpm release-age exclude 2026-03-31 23:10:07 +09:00
Altay
9407ac87df build: move pnpm minimum release age to workspace config 2026-03-31 23:10:07 +09:00
Peter Steinberger
8807b017d1 test: harden channel planner lane matching 2026-03-31 23:08:23 +09:00
Peter Steinberger
4fb373466e refactor: simplify memory recovery and test setup 2026-03-31 15:02:11 +01:00
Vincent Koc
6936033e98 test(telegram): stop overriding message-context session mocks 2026-03-31 23:01:21 +09:00
Peter Steinberger
0711cb4a05 fix(hooks): reduce registration log noise 2026-03-31 14:59:22 +01:00
Peter Steinberger
dc0e0b0f68 docs(security): mark shared-secret HTTP auth as designed 2026-03-31 22:58:09 +09:00
Jacob Tomlinson
a4d72a83f0 fix(tlon): preserve explicit empty settings during migration (#58370) 2026-03-31 14:57:03 +01:00
Peter Steinberger
c1ea0ae9c8 build: update deps and align pi sdk usage 2026-03-31 22:56:20 +09:00
Peter Steinberger
cbfeecfab4 fix(gateway): restore shared-secret HTTP tool invoke auth 2026-03-31 22:55:15 +09:00
Jacob Tomlinson
0c83754246 Exec approvals: reject shell init-file script matches (#58369) 2026-03-31 14:53:43 +01:00
Vincent Koc
0ed4f8a72b fix(media): reject oversized image inputs before decode (#58226)
* fix(media): cap oversized image inputs

* chore(changelog): add media input guard note

* fix(media): address input guard review feedback

* fix(media): fail closed on unknown sips dimensions

* fix(media): avoid sips fallback in input guard
2026-03-31 22:52:55 +09:00
Vincent Koc
aaf6077f27 test(telegram): skip session persistence in message-context harness 2026-03-31 22:51:25 +09:00
Vincent Koc
4ee742174f fix(nostr): verify inbound dm signatures before pairing replies (#58236)
* fix(nostr): verify inbound dm signatures before pairing

* fix(nostr): authorize senders before rate limiting

* test(nostr): cover pending auth rate-limit starvation

* fix(nostr): rate limit oversized inbound ciphertext

* fix(nostr): dedupe blocked inbound replays

* fix(nostr): rate limit before auth work
2026-03-31 22:51:22 +09:00
Peter Steinberger
5fc8f6ca8f test: align targeted channel batching expectation 2026-03-31 14:49:04 +01:00
Vincent Koc
29b9310319 fix(scripts): normalize bundled entry paths and planner counts 2026-03-31 22:47:12 +09:00
Vincent Koc
3be08454f4 test(telegram): narrow resolve-media retry imports 2026-03-31 22:45:39 +09:00
Vincent Koc
91115cdf61 test(telegram): stub menu sync in command harness 2026-03-31 22:31:12 +09:00
Vincent Koc
2df86cce1c refactor(telegram): narrow native command reply dispatch seam 2026-03-31 22:28:53 +09:00
Peter Steinberger
5a93344d82 fix: ship bundled runtime support packages 2026-03-31 14:25:32 +01:00
Vincent Koc
5b7443d175 perf(whatsapp): narrow reply chunking imports 2026-03-31 22:25:14 +09:00
Peter Steinberger
e7e383b7cf build: exclude @mariozechner packages from pnpm release age 2026-03-31 22:23:30 +09:00
Vincent Koc
ff36bc314d test(telegram): use shared delivery mock in registry test 2026-03-31 22:18:29 +09:00
Vincent Koc
3f2fb73cfe perf(slack): avoid module resets in outbound adapter test 2026-03-31 22:13:39 +09:00
Frank Yang
dbe6663c34 fix(qqbot): align speech schema and setup validation (#58253)
* fix(qqbot): align speech schema and setup validation

* fix(qqbot): preserve use-env setup flow

* fix(qqbot): reject use-env on named accounts

* fix(qqbot): restore default account schema support
2026-03-31 21:11:45 +08:00
Gustavo Madeira Santana
8dbba7d17c fix(scripts/pr): make cleanup worktree-safe 2026-03-31 09:07:42 -04:00
Gustavo Madeira Santana
27b9665871 chore: clarify test performance guardrail 2026-03-31 09:07:42 -04:00
Vincent Koc
d369c9373b perf(whatsapp): avoid module resets in poll adapter test 2026-03-31 22:06:01 +09:00
Vincent Koc
37099dae3e fix(ci): restore matrix monitor import guards and windows npm exit codes 2026-03-31 22:04:35 +09:00
Vincent Koc
35072c4751 perf(discord): avoid broad send barrel in webhook activity test 2026-03-31 22:02:01 +09:00
Vincent Koc
675b80c4a4 perf(slack): narrow send chunking imports 2026-03-31 21:58:00 +09:00
Gustavo Madeira Santana
4ea1ca4849 Sessions: parse thread suffixes by channel (#58100)
Merged via squash.

Prepared head SHA: 2829b9c5b5
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
2026-03-31 08:54:16 -04:00
Vincent Koc
11590eb6ce fix(ci): restore dotenv trust boundary and windows npm exit handling 2026-03-31 21:51:17 +09:00
Gustavo Madeira Santana
3ceec929df Matrix: narrow monitor runtime seam 2026-03-31 08:46:53 -04:00
Vincent Koc
7710579a82 perf(telegram): narrow native command import surface 2026-03-31 21:43:16 +09:00
Vincent Koc
b19e28a85e fix(telegram): lazy-load sticker vision model lookup 2026-03-31 21:31:05 +09:00
Vincent Koc
dba96e7507 fix(discord): gate voice ingress by allowlists (#58245)
* fix(discord): gate voice ingress by allowlists

* fix(discord): preserve voice allowlist context

* fix(discord): fetch guild metadata for voice allowlists

* fix(discord): reuse voice speaker context

* fix(discord): preserve cached speaker context

* fix(discord): tighten voice ingress authorization
2026-03-31 21:29:13 +09:00
Vincent Koc
25a3d37970 fix(ci): restore matrix guardrails and windows exec shim 2026-03-31 21:27:43 +09:00
Gustavo Madeira Santana
f8af407c86 build: pin axios to 1.13.6
Pin axios through pnpm overrides and collapse the lockfile to a single
1.13.6 resolution.

This avoids accidental adoption of the compromised axios releases called
out in the ongoing supply chain attack reports while upstream guidance
settles.
2026-03-31 08:27:00 -04:00
Vincent Koc
4d912e0451 fix(exec): block proxy-style env overrides (#58202)
* fix(exec): block proxy-style env overrides

* fix(exec): keep trusted host proxy env inherited

* fix(exec): block git tls override env vars

* fix(skills): block dangerous env override keys
2026-03-31 21:25:36 +09:00
Gustavo Madeira Santana
28bb8c600e Matrix: narrow thread binding runtime seam 2026-03-31 08:12:46 -04:00
Gustavo Madeira Santana
305977571d Matrix: narrow storage and routing imports 2026-03-31 08:12:46 -04:00
Vincent Koc
e6441760d2 test(telegram): normalize message-context timing inputs 2026-03-31 21:10:43 +09:00
Vincent Koc
415e7d941b test(slack): remove slash metadata polling 2026-03-31 21:02:06 +09:00
Vincent Koc
730ba40763 fix(exec): unwrap arch and xcrun dispatch wrappers (#58203)
* fix(exec): unwrap arch and xcrun dispatch wrappers

* fix(infra): scope arch wrapper unwrapping to macos

* fix(exec): scope arch wrapper unwrapping to macos

* fix(infra): validate macos arch wrapper selectors

* test(infra): cover invalid arch name wrappers
2026-03-31 21:00:14 +09:00
Jacob Tomlinson
2ce44ca6a1 fix(plugins): guard marketplace archive downloads (#58267)
* Plugins: guard marketplace archive downloads

* Plugins: harden marketplace download cleanup

* Plugins: bound marketplace archive downloads

* Plugins: harden marketplace archive failures

* Plugins: reject drive-relative marketplace archives

* Plugins: stream marketplace archive downloads
2026-03-31 12:59:42 +01:00
Mariano
607076d164 ClawFlow: add runtime substrate (#58336)
Merged via squash.

Prepared head SHA: 6a6158179e
Reviewed-by: @mbelinky
2026-03-31 13:58:29 +02:00
Vincent Koc
f2d4089ca2 test(discord): remove monitor polling overhead 2026-03-31 20:56:37 +09:00
Vincent Koc
334085fbe9 test(channels): inject telegram reply pipeline for dispatch tests 2026-03-31 20:54:30 +09:00
Vincent Koc
5474796735 docs(security): clarify acpx yolo mode 2026-03-31 20:54:30 +09:00
pgondhi987
d8c68c8d42 fix: migrate Telegram pairing allowFrom to default account only (#58165)
* fix: migrate Telegram pairing allowFrom to default account only

* fix: address PR review feedback

* fix: address PR review feedback
2026-03-31 12:51:38 +01:00
Vincent Koc
62c28c0708 test(discord): isolate ACP binding routing seam 2026-03-31 20:49:31 +09:00
Vincent Koc
b4ac69c652 docs(acp): align approval policy wording 2026-03-31 20:49:31 +09:00
Vincent Koc
cd5179314d fix(acp): use semantic approval classes 2026-03-31 20:49:31 +09:00
Gustavo Madeira Santana
d077faab1a Matrix: narrow monitor runtime imports 2026-03-31 07:29:47 -04:00
Gustavo Madeira Santana
2bdf2fbf14 Matrix: trim storage test import churn 2026-03-31 07:29:47 -04:00
Vincent Koc
225dfe0094 fix(ci): stabilize planner executor fallback tests 2026-03-31 20:26:28 +09:00
Gustavo Madeira Santana
8c0245f57b fix(matrix): tighten DM invite promotion state (#58099)
Merged via squash.

Prepared head SHA: 6638d4b505
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
2026-03-31 07:09:18 -04:00
Vincent Koc
1243e2c0b6 fix(telegram): keep test harness CJS-safe 2026-03-31 20:04:21 +09:00
Vincent Koc
e704323ff3 fix(media): drop auth headers on cross-origin redirects (#58224)
* fix(media): drop auth headers on cross-origin redirects

* chore(changelog): sync unreleased context

* fix(media): keep fetch-guard redirect helper working
2026-03-31 19:57:42 +09:00
Vincent Koc
3d5af14984 fix(agents): reject escaping symlinks in ssh sandbox uploads (#58220)
* fix(agents): reject escaping ssh sandbox upload symlinks

* fix(agents): allow safe ssh upload symlink aliases

* test(ssh): keep upload stdin open in fake ssh

* Update CHANGELOG.md
2026-03-31 19:56:45 +09:00
FMLS
44caf1ee3d fix(browser): prevent cross-origin images from disappearing in CDP screenshots (#54358)
fromSurface: true + captureBeyondViewport: true triggers a Chromium compositor
bug where cross-origin image textures are lost when extending the capture
surface. Switch to fromSurface: false to use the software rendering path.

For full-page captures, temporarily expand the viewport via
Emulation.setDeviceMetricsOverride, preserving the current mobile/DPR/screen
state during capture and restoring it afterward so pre-existing device
emulation is not lost.

Made-with: Cursor

Co-authored-by: hakunaliu <hakunaliu@tencent.com>
2026-03-31 18:55:25 +08:00
Vincent Koc
57700d716f fix(config): redact Nostr privateKey in config views (#58177)
* wip(config): preserve nostr redaction progress

* fix(config): add private key redaction fallback

* fix(config): align nostr privateKey secret input handling

* fix(config): require resolved nostr private keys
2026-03-31 19:55:03 +09:00
Vincent Koc
efe9183f9d fix(voice-call): pin plivo callback origins (#58238) 2026-03-31 19:50:35 +09:00
Vincent Koc
cf3ae2612b fix(ci): reduce slow channel test skew 2026-03-31 19:49:40 +09:00
Vincent Koc
da7f016db6 fix(doctor): align qmd probe cwd with runtime 2026-03-31 19:49:40 +09:00
Vincent Koc
6b3f99a11f fix(gateway): enforce trusted-proxy HTTP origin checks (#58229)
* fix(gateway): enforce trusted-proxy HTTP origin checks

* Update CHANGELOG.md
2026-03-31 19:49:26 +09:00
Vincent Koc
9abcfdadf5 fix(voice-call): reject oversized pre-start media frames (#58241)
* fix(voice-call): reject oversized pre-start frames

* fix(voice-call): avoid normalizing oversized frames

* chore(changelog): remove stray spacing

* fix(voice-call): remove dead inbound size guard
2026-03-31 19:47:10 +09:00
Vincent Koc
9bc1f896c8 fix(pairing): scope pending request caps per account (#58239)
* fix(pairing): scope pending pairing caps per account

* fix(pairing): count legacy default-account requests
2026-03-31 19:45:45 +09:00
Vincent Koc
f45e5a6569 fix(feishu): filter fetched group thread context (#58237)
* fix(feishu): filter fetched group thread context

* fix(feishu): preserve filtered thread bootstrap
2026-03-31 19:43:54 +09:00
Vincent Koc
2194587d70 fix(tlon): cap inbound image downloads (#58223) 2026-03-31 19:40:15 +09:00
Vincent Koc
9023a0436c fix(exec): unwrap transparent approval wrappers (#58215)
* fix(exec): unwrap transparent approval wrappers

* fix(exec): normalize sandbox-exec -D wrapper parsing
2026-03-31 19:38:34 +09:00
Vincent Koc
eb8de6715f fix(exec): block risky host env overrides (#58209)
* fix(exec): block risky host env overrides

* fix(exec): block GOPRIVATE host env overrides
2026-03-31 19:37:43 +09:00
Vincent Koc
57c47d8c7f fix(line): bound preverify webhook concurrency (#58199)
* fix(line): bound preverify webhook concurrency

* test(line): cover preauth release timing

* fix(line): release webhook preauth slots earlier
2026-03-31 19:34:25 +09:00
Vincent Koc
4d038bb242 fix(zalo): scope webhook replay dedupe per target (#58196) 2026-03-31 19:33:57 +09:00
Vincent Koc
57fccca2dc fix(exec): keep awk and sed out of safeBins fast path (#58175)
* wip(exec): preserve safe-bin semantics progress

* test(exec): cover safe-bin semantic variants

* fix(exec): address safe-bin review follow-up
2026-03-31 19:29:53 +09:00
Vincent Koc
330a9f98cb fix(config): block workspace bundled-root dotenv overrides (#58170)
* wip(config): preserve bundled hooks root progress

* test(config): cover bundled trust-root dotenv blocking
2026-03-31 19:25:12 +09:00
Vincent Koc
b9f857708c wip(config): preserve bundled plugins root progress (#58168) 2026-03-31 19:23:11 +09:00
Jacob Tomlinson
781775ec08 Media: secure image temp dirs (#58270) 2026-03-31 11:12:47 +01:00
Ayaan Zaidi
6be0c7ef09 fix(android): drop bootstrap auth after manual endpoint changes 2026-03-31 15:32:36 +05:30
Jacob Tomlinson
7bd2761b92 Exec approvals: detect command carriers in strict inline eval (#57842)
* Exec approvals: detect command carriers in strict inline eval

* Exec approvals: cover carrier option edge cases

* Exec approvals: cover make and find carriers

* Exec approvals: catch attached eval flags

* Exec approvals: keep sed -E out of inline eval

* Exec approvals: treat sed in-place flags as optional
2026-03-31 10:58:17 +01:00
Ayaan Zaidi
cbc75f13b2 test(android): cover node-only onboarding state 2026-03-31 15:21:39 +05:30
Ayaan Zaidi
132208c01f fix(android): require node connection before onboarding finish 2026-03-31 15:21:39 +05:30
Ayaan Zaidi
c1269eddb8 fix(android): preserve bootstrap auth for manual reconnect 2026-03-31 15:21:39 +05:30
Jacob Tomlinson
eb84d91a80 UI: build delete confirm popover without HTML strings (#58269)
* UI: build delete confirm popover safely

* UI: share delete confirm storage key
2026-03-31 10:42:07 +01:00
Jacob Tomlinson
df0e136bc7 Canvas Host: build default status with DOM nodes (#58266) 2026-03-31 10:29:28 +01:00
Vincent Koc
e95f786aa2 fix(dev): sync run-node test types 2026-03-31 18:04:22 +09:00
Jacob Tomlinson
a23c33a681 macOS: use MagicDNS for wide-area gateway discovery (#57833)
* macOS: use MagicDNS for wide-area gateway discovery

Co-authored-by: nexrin <268879349+nexrin@users.noreply.github.com>

* macOS: tighten wide-area discovery review follow-ups

---------

Co-authored-by: nexrin <268879349+nexrin@users.noreply.github.com>
2026-03-31 10:04:11 +01:00
Vincent Koc
f288ff3f9f fix(tests): stabilize cron and blocked-flow assertions 2026-03-31 17:58:41 +09:00
Vincent Koc
cd8d0881ed fix(dev): classify dirty-tree watch invalidations 2026-03-31 17:54:05 +09:00
Vincent Koc
622bdfdad1 docs(memory): clarify qmd symlink traversal limits 2026-03-31 17:54:00 +09:00
Vincent Koc
2befbc5e60 fix(matrix): restore local helper seams 2026-03-31 17:42:37 +09:00
Vincent Koc
4e2a072b5b fix(memory): add qmd degraded status changelog 2026-03-31 17:37:12 +09:00
Vincent Koc
d27165f5de fix(tasks): allow new task read-model importers 2026-03-31 17:35:39 +09:00
Vincent Koc
3a5042b6cc fix(memory): surface qmd degraded vector status 2026-03-31 17:35:36 +09:00
Vincent Koc
af37fca556 fix(qqbot): mark npm-publishable package public 2026-03-31 17:33:48 +09:00
Vincent Koc
0b76d85509 fix(qqbot): declare silk-wasm codec types 2026-03-31 17:30:22 +09:00
Vincent Koc
72d1725fcf fix(tasks): preserve nullable flow patch clears 2026-03-31 17:24:54 +09:00
zsxsoft
d15d7d0962 fix(scripts/pr): shell-escape env file values to prevent command injection via branch names 2026-03-31 17:24:19 +09:00
pgondhi987
f865a5455e fix(media): drop sensitive headers on cross-origin redirects [AI] (#58156) 2026-03-31 09:22:11 +01:00
Vincent Koc
549169f746 fix(docs): format memory config reference 2026-03-31 17:18:21 +09:00
Vincent Koc
d2dcd6fca6 fix(memory): stagger qmd embed maintenance across agents (#58180)
* fix(memory): stagger qmd embed maintenance across agents

* fix(memory): keep qmd embed serialization in-process

* fix(memory): extend qmd embed lock wait budget
2026-03-31 17:17:20 +09:00
Sliverp
bf6f506dfa Feature/add qq channel (#52986)
* feat: add QQ Bot channel extension

* fix(qqbot): add setupWizard to runtime plugin for onboard re-entry

* fix: fix review

* fix: fix review

* chore: sync lockfile and config-docs baseline for qqbot extension

* refactor: 移除图床服务器相关代码

* fix

* docs: 新增 QQ Bot 插件文档并修正链接路径

* refactor: remove credential backup functionality and update setup logic

- Deleted the credential backup module to streamline the codebase.
- Updated the setup surface to handle client secrets more robustly, allowing for configured secret inputs.
- Simplified slash commands by removing unused hot upgrade compatibility checks and related functions.
- Adjusted types to use SecretInput for client secrets in QQBot configuration.
- Modified bundled plugin metadata to allow additional properties in the config schema.

* feat: 添加本地媒体路径解析功能,修正 QQBot 媒体路径处理

* feat: 添加本地媒体路径解析功能,修正 QQBot 媒体路径处理

* feat: remove qqbot-media and qqbot-remind skills, add tests for config and setup

- Deleted the qqbot-media and qqbot-remind skills documentation files.
- Added unit tests for qqbot configuration and setup processes, ensuring proper handling of SecretRef-backed credentials and account configurations.
- Implemented tests for local media path remapping, verifying correct resolution of media file paths.
- Removed obsolete channel and remind tools, streamlining the codebase.

* feat: 更新 QQBot 配置模式,添加音频格式和账户定义

* feat: 添加 QQBot 频道管理和定时提醒技能,更新媒体路径解析功能

* fix

* feat: 添加 /bot-upgrade 指令以查看 QQBot 插件升级指引

* feat: update reminder and qq channel skills

* feat: 更新remind工具投递目标地址格式

* feat: Refactor QQBot payload handling and improve code documentation

- Simplified and clarified the structure of payload interfaces for Cron reminders and media messages.
- Enhanced the parsing function to provide clearer error messages and improved validation.
- Updated platform utility functions for better cross-platform compatibility and clearer documentation.
- Improved text parsing utilities for better readability and consistency in emoji representation.
- Optimized upload cache management with clearer comments and reduced redundancy.
- Integrated QQBot plugin into the bundled channel plugins and updated metadata for installation.

* OK apps/macos/Sources/OpenClaw/HostEnvSecurityPolicy.generated.swift

> openclaw@2026.3.26 check:bundled-channel-config-metadata /Users/yuehuali/code/PR/openclaw
> node --import tsx scripts/generate-bundled-channel-config-metadata.ts --check

[bundled-channel-config-metadata] stale generated output at src/config/bundled-channel-config-metadata.generated.ts
 ELIFECYCLE  Command failed with exit code 1.
 ELIFECYCLE  Command failed with exit code 1.

* feat: 添加 QQBot 渠道配置及相关账户设置

* fix(qqbot): resolve 14 high-priority bugs from PR #52986 review

DM routing (7 fixes):
- #1: DM slash-command replies use sendDmMessage(guildId) instead of sendC2CMessage(senderId)
- #2: DM qualifiedTarget uses qqbot:dm:${guildId} instead of qqbot:c2c:${senderId}
- #3: sendTextChunks adds DM branch
- #4: sendMarkdownReply adds DM branch for text and Base64 images
- #5: parseAndSendMediaTags maps DM to targetType:dm + guildId
- #6: sendTextToTarget DM branch uses sendDmMessage; MessageTarget adds guildId field
- #7: handleImage/Audio/Video/FilePayload add DM branches

Other high-priority fixes:
- #8: Fix sendC2CVoiceMessage/sendGroupVoiceMessage parameter misalignment
- #9: broadcastMessage uses groupOpenid instead of member_openid for group users
- #10: Unify KnownUser storage - proactive.ts delegates to known-users.ts
- #11: Remove invalid recordKnownUser calls for guild/DM users
- #12: sendGroupMessage uses sendAndNotify to trigger onMessageSent hook
- #13: sendPhoto channel unsupported returns error field
- #14: sendTextAfterMedia adds channel and dm branches

Type fixes:
- DeliverEventContext adds guildId field
- MediaTargetContext.targetType adds dm variant
- sendPlainTextReply imgMediaTarget adds DM branch

* fix(qqbot): resolve 2 blockers + 7 medium-priority bugs from PR #52986 review

Blocker-1: Remove unused dmPolicy config knob
- dmPolicy was declared in schema/types/plugin.json but never consumed at runtime
- Removed from config-schema.ts, types.ts, and openclaw.plugin.json
- allowFrom remains active (already wired into framework command-auth)

Blocker-2: Gate sensitive slash commands with allowFrom authorization
- SlashCommand interface adds requireAuth?: boolean
- SlashCommandContext adds commandAuthorized: boolean
- /bot-logs set to requireAuth: true (reads local log files)
- matchSlashCommand rejects unauthorized senders for requireAuth commands
- trySlashCommandOrEnqueue computes commandAuthorized from allowFrom config

Medium-priority fixes:
- #15: Strip non-HTTP/non-local markdown image tags to prevent path leakage
- #16: applyQQBotAccountConfig clears clientSecret when setting clientSecretFile and vice versa
- #17: getAdminMarkerFile sanitizes accountId to prevent path traversal
- #18: URGENT_COMMANDS uses exact match instead of startsWith prefix match
- #19: isCronExpression validates each token starts with a cron-valid character
- #20: --token format validation rejects malformed input without colon separator
- #21: resolveDefaultQQBotAccountId checks QQBOT_APP_ID environment variable

* test(qqbot): add focused tests for slash command authorization path

- Unauthorized sender rejected for /bot-logs (requireAuth: true)
- Authorized sender allowed for /bot-logs
- Non-requireAuth commands (/bot-ping, /bot-help, /bot-version) work for all senders
- Unknown slash commands return null (passthrough)
- Non-slash messages return null
- Usage query (/bot-logs ?) also gated by auth check

* fix(qqbot): align global TTS fallback with framework config resolution

- Extract isGlobalTTSAvailable to utils/audio-convert.ts, mirroring core
  resolveTtsConfig logic: check auto !== 'off', fall back to legacy
  enabled boolean, default to off when neither is set.
- Add pre-check in reply-dispatcher before calling globalTextToSpeech to
  avoid unnecessary TTS calls and noisy error logs when TTS is not
  configured.
- Remove inline as any casts; use OpenClawConfig type throughout.
- Refactor handleAudioPayload into flat early-return structure with
  unified send path (plugin TTS → global fallback → send).

* fix(qqbot): break ESM circular dependency causing multi-account startup crash

The bundled gateway chunk had a circular static import on the channel
chunk (gateway -> outbound-deliver -> channel, while channel dynamically
imports gateway). When two accounts start concurrently via Promise.all,
the first dynamic import triggers module graph evaluation; the circular
reference causes api exports (including runDiagnostics) to resolve as
undefined before the module finishes evaluating.

Fix: extract chunkText and TEXT_CHUNK_LIMIT from channel.ts into a new
text-utils.ts leaf module. outbound-deliver.ts now imports from
text-utils.ts, breaking the cycle. channel.ts re-exports for backward
compatibility.

* fix(qqbot): serialize gateway module import to prevent multi-account startup race

When multiple accounts start concurrently via Promise.all, each calls
await import('./gateway.js') independently. Due to ESM circular
dependencies in the bundled output, the first import can resolve
transitive exports as undefined before module evaluation completes.

Fix: cache the dynamic import promise in a module-level variable so all
concurrent startAccount calls share the same import, ensuring the
gateway module is fully evaluated before any account uses it.

* refactor(qqbot): remove startup greeting logic

Remove getStartupGreetingPlan and related startup greeting delivery:
- Delete startup-greeting.ts (greeting plan, marker persistence)
- Delete admin-resolver.ts (admin resolution, greeting dispatch)
- Remove startup greeting calls from gateway READY/RESUMED handlers
- Remove isFirstReadyGlobal flag and adminCtx

* fix(qqbot): skip octal escape decoding for Windows local paths

Windows paths like C:\Users\1\file.txt contain backslash-digit sequences
that were incorrectly matched as octal escape sequences and decoded,
corrupting the file path. Detect Windows local paths (drive letter or UNC
prefix) and skip the octal decoding step for them.

* fix bot issue

* feat: 支持 TTS 自动开关并清理配置中的 clientSecretFile

* docs: 添加 QQBot 配置和消息处理的设计说明

* rebase

* fix(qqbot): align slash-command auth with shared command-auth model

Route requireAuth:true slash commands (e.g. /bot-logs) through the
framework's api.registerCommand() so resolveCommandAuthorization()
applies commands.allowFrom.qqbot precedence and qqbot: prefix
normalization before any handler runs.

- slash-commands.ts: registerCommand() now auto-routes by requireAuth
  into two maps (commands / frameworkCommands); getFrameworkCommands()
  exports the auth-required set for framework registration; bot-help
  lists both maps
- index.ts: registerFull() iterates getFrameworkCommands() and calls
  api.registerCommand() for each; handler derives msgType from ctx.from,
  sends file attachments via sendDocument, supports multi-account via
  ctx.accountId
- gateway.ts (inbound): replace raw allowFrom string comparison with
  qqbotPlugin.config.formatAllowFrom() to strip qqbot: prefix and
  uppercase before matching event.senderId
- gateway.ts (pre-dispatch): remove stale auth computation; commandAuthorized
  is true (requireAuth:true commands never reach matchSlashCommand)
- command-auth.test.ts: add regression tests for qqbot: prefix
  normalization in the inbound commandAuthorized computation
- slash-commands.test.ts: update /bot-logs tests to expect null
  (command routed to framework, not in local registry)

* rebase and solve conflict

* fix(qqbot): preserve mixed env setup credentials

---------

Co-authored-by: yuehuali <yuehuali@tencent.com>
Co-authored-by: walli <walli@tencent.com>
Co-authored-by: WideLee <limkuan24@gmail.com>
Co-authored-by: Frank Yang <frank.ekn@gmail.com>
2026-03-31 16:13:16 +08:00
Mariano
f86e5c0a08 ClawFlow: add linear flow control surface (#58227)
* ClawFlow: add linear flow control surface

* Flows: clear blocked metadata on resume
2026-03-31 10:08:50 +02:00
Vincent Koc
ab4ddff7f1 feat(memory): add per-agent QMD extra collections for cross-agent session search (#58211)
* feat(memory): add per-agent qmd extra collections

* test(config): cover qmd extra collections schema outputs

* docs(config): refresh qmd extra collections baseline

* docs(config): regenerate qmd extra collections baselines

* docs(config): clarify qmd extra collection naming
2026-03-31 17:08:18 +09:00
Vincent Koc
5707038e6c fix(memory): preserve qmd query semantics and collection recovery (#58183)
* fix(memory): preserve qmd search queries and repair collection rebuilds

* fix(qmd): cover null-byte rebuild cycle
2026-03-31 17:07:35 +09:00
Vincent Koc
f96e150450 fix(doctor): suppress qmd session orphan cleanup (#58182) 2026-03-31 17:06:24 +09:00
Vincent Koc
075645f5cb fix(memory): use explicit qmd snippet line metadata (#58181)
* fix(memory): preserve qmd snippet line metadata

* Memory/QMD: preserve snippet span with partial line metadata
2026-03-31 17:05:53 +09:00
Vincent Koc
fcc2488579 fix(tasks): align flow patch optionals 2026-03-31 17:04:20 +09:00
Vincent Koc
34ae78bfee fix(tests): reduce matrix extension import churn 2026-03-31 16:59:38 +09:00
Vincent Koc
dfc124c772 fix(matrix): reduce extension test import churn 2026-03-31 16:54:04 +09:00
Peter Steinberger
0633406ff6 fix(gateway): restore compat HTTP operator auth 2026-03-31 16:49:30 +09:00
Vincent Koc
6eb42593fa fix(slack): restore plugin approval auth 2026-03-31 16:45:46 +09:00
Josh Avant
788f56f30f Secrets: hard-fail unsupported SecretRef policy and fix gateway restart token drift (#58141)
* Secrets: enforce C2 SecretRef policy and drift resolution

* Tests: add gateway auth startup/reload SecretRef runtime coverage

* Docs: sync C2 SecretRef policy and coverage matrix

* Config: hard-fail parent SecretRef policy writes

* Secrets: centralize unsupported SecretRef policy metadata

* Daemon: test service-env precedence for token drift refs

* Config: keep per-ref dry-run resolvability errors

* Docs: clarify config-set parent-object policy checks

* Gateway: fix drift fallback and schema-key filtering

* Gateway: align drift fallback with credential planner

* changelog

Signed-off-by: joshavant <830519+joshavant@users.noreply.github.com>

---------

Signed-off-by: joshavant <830519+joshavant@users.noreply.github.com>
2026-03-31 02:37:31 -05:00
Mariano
8d942000c9 Tasks: add blocked flow retry state (#58204) 2026-03-31 09:33:26 +02:00
sudie-codes
4e67e7c02c msteams: add member-info action via Graph API (#57528) 2026-03-31 02:24:33 -05:00
Vincent Koc
5ec362fe0b feat(slack): add native exec approvals (#58155)
* feat(slack): add native exec approvals

* feat(slack): wire native exec approvals

* Update CHANGELOG.md

* fix(slack): gate native approvals by request filters

* fix(slack): keep local approval prompt path
2026-03-31 16:20:57 +09:00
Vincent Koc
2feb83babb fix(ci): shard fast extension checks 2026-03-31 15:58:50 +09:00
Vincent Koc
a6046c94f7 fix(ci): speed up fast extension scheduling 2026-03-31 15:52:40 +09:00
James L. Cowan Jr.
3bed73dc36 fix(config): migrate removed telegram groupMentionsOnly key (#55336)
Merged via squash.

Prepared head SHA: 23731e27bf
Co-authored-by: jameslcowan <112015792+jameslcowan@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-03-30 23:11:44 -07:00
Vincent Koc
8dfbcaa200 fix(ci): default local low-memory checks 2026-03-31 15:05:04 +09:00
Josh Lehman
3a87783632 test: avoid extra plugin-sdk guardrail analysis 2026-03-30 22:51:18 -07:00
Vincent Koc
8ef9e7f159 docs: add Related sections to install and help pages
- install/docker.md: link to podman, clawdock, updating, config
- install/node.md: link to overview, updating, getting-started
- install/updating.md: link to overview, doctor, migrating
- help/troubleshooting.md: link to FAQ, gateway/channel/automation troubleshooting, doctor
2026-03-31 14:38:46 +09:00
Vincent Koc
5ee054e9db docs: merge network-model stub into network hub, improve bridge deprecation
- network.md: add Core model prose (loopback-first, canvas host, remote access)
  from the 22-line network-model.md stub
- network-model.md: add redirect note pointing to /network#core-model
- bridge-protocol.md: replace scattered deprecation notes with prominent
  <Warning> callout at the top
2026-03-31 14:37:43 +09:00
Vincent Koc
b970187379 docs: fix oxfmt formatting in remote.md and THREAT-MODEL-ATLAS.md 2026-03-31 14:36:49 +09:00
Vincent Koc
9f0845137a docs: add Related sections to plugin and web interface pages
- building-plugins.md, manifest.md: link to architecture, SDK, channel/provider plugins
- control-ui.md, tui.md: link to sibling web interfaces and CLI
2026-03-31 14:34:56 +09:00
Vincent Koc
74830c7bac docs: add Related sections to 6 major tool pages
Add cross-linking Related sections to tool pages that were dead ends:
- exec, exec-approvals, browser, pdf, skills, lobster

Each page now links to 2-4 related topics for navigation continuity.
2026-03-31 14:34:56 +09:00
Vincent Koc
ff1ae5df22 docs: add 8 missing doctor checks and --generate-gateway-token flag 2026-03-31 14:34:56 +09:00
Vincent Koc
641a6880cf docs: add Related sections to 10 concept pages
Add cross-linking Related sections to concept pages that were dead ends:
- model-providers, models, context, context-engine, agent-workspace,
  architecture, messages, streaming, compaction, oauth

Each page now links to 3-4 related topics for navigation continuity.
2026-03-31 14:34:56 +09:00
Vincent Koc
1bf8fb26f4 docs: fix config examples -- perSession deprecation and dmScope guidance
- Replace perSession: true with scope: "session" (preferred syntax)
- Add dmScope: "per-channel-peer" to expanded example for multi-user safety
2026-03-31 14:34:56 +09:00
Vincent Koc
4ab7947ec0 docs: merge remote-gateway-readme content into remote.md 2026-03-31 14:34:56 +09:00
Ayaan Zaidi
3059eadca2 test: fix provider runtime mocks and test planner load shedding 2026-03-31 11:04:28 +05:30
Vincent Koc
aebdb8f8cf fix(lint): scope oxlint type-aware tsconfig 2026-03-31 14:28:41 +09:00
Vincent Koc
637f15375b docs: fix Gateway & Ops audit findings (7 pages)
- cli-backends.md: remove duplicate modelAliases key
- discovery.md: add missing transport=gateway and displayName TXT keys
- authentication.md: retitle to "Authentication (Model Providers)", add
  disambiguation Note pointing to gateway connection auth docs
- health.md: expand frontmatter scope, add --probe flag and response shape docs
- gateway-lock.md: remove stale hardcoded date, add Related section
- troubleshooting.md: fix wrong auth cross-link (model auth -> gateway config)
- logging.md: add Related section linking to gateway logging internals
2026-03-31 14:24:19 +09:00
Vincent Koc
6c6792446b docs: fix THREAT-MODEL-ATLAS pairing TTLs and invalid file paths 2026-03-31 14:24:19 +09:00
Vincent Koc
d352bd050a docs: fix tools-invoke default deny list (was missing 8 of 13 entries) 2026-03-31 14:24:19 +09:00
Vincent Koc
ab8d999917 docs: fix sandbox scope default (session -> agent per resolveSandboxScope) 2026-03-31 14:24:19 +09:00
Ayaan Zaidi
e42330eff7 fix: remove duplicate sandbox browser start branch 2026-03-31 10:34:09 +05:30
Ayaan Zaidi
aeee17a689 fix(acp): preserve Telegram topic-bound conversation ids 2026-03-31 10:31:01 +05:30
Josh Avant
81b777c768 fix(config): harden SecretRef round-trip handling in Control UI and RPC writes (#58044)
* Config: harden SecretRef round-trip handling

* Gateway: test SecretRef preflight on config writes

* Agents: align skill loader with upstream Skill type

* Docs: align SecretRef write semantics with Control UI and RPC behavior

* Config: add UI and gateway regression evidence for SecretRef hardening

* Config: add token SecretRef restore regression and skill sourceInfo compat

* UI: scope structured-value lockout to SecretRef fields

* Agents: remove out-of-scope skill loader compat edits

* UI: reduce app-render churn to rawAvailable-only changes

* Gateway: scope SecretRef preflight to submitted config

* Docs: clarify config write SecretRef preflight scope

* changelog

Signed-off-by: joshavant <830519+joshavant@users.noreply.github.com>

---------

Signed-off-by: joshavant <830519+joshavant@users.noreply.github.com>
2026-03-30 23:55:03 -05:00
Gabriel M.
f7ced438f7 fix: restore Telegram forum-topic routing (#56060) (thanks @one27001)
* feat(telegram): add child thread-binding placement via createForumTopic

Enable ACP subagent spawn on Telegram by adding "child" placement
support to the thread-bindings adapter. When a child binding is
requested, the adapter creates a new forum topic via the Telegram
Bot API and binds the subagent session to it using the canonical
chatId:topic:topicId conversation ID format.

When the ACP spawn context provides only a topic ID (not a full
group chat ID), the adapter resolves the group from the configured
Telegram groups in openclaw.json.

This mirrors the Discord adapter's child placement behavior
(thread creation + session binding) and unblocks the orchestrator
pattern on Telegram forum-enabled groups.

Closes #5737
Ref #23414

* fix(telegram): return null with warning instead of silent group fallback for bare topic IDs in child bind

* telegram: fix ACP child thread spawn with group chat ID from agentGroupId

* telegram: scope agentGroupId substitution to telegram channel only

* Telegram: fix forum topic replies routing to root chat instead of topic thread

* fix: clean up dead guard in child bind + add explicit threadId override test

- Simplify bare-topic-ID guards in thread-bindings.ts: split into
  separate !chatId and !chatId.startsWith("-") checks, removing
  unreachable second condition
- Add regression test confirming explicit turnSourceThreadId overrides
  session lastThreadId on same channel

* fix: guard threadId fallback against shared-session race

Codex review P1: when turnSourceTo differs from the session's stored
to, the session threadId may belong to a different chat/topic. Only
fall back to context.threadId when the destination also matches.

* fix(telegram): enable ACP spawn from forum topics without thread binding

extractExplicitGroupId returned topic-qualified IDs (-100...:topic:1264)
instead of bare group chat IDs, breaking agentGroupId resolution.
agentGroupId was also never wired in the inline actions path.

For Telegram forum topics, skip thread binding entirely — the delivery
plan already routes correctly via requester origin (to + threadId).
Creating new forum topics per child session is unnecessary; output goes
back to the same topic the user asked from.

* fix(acp): bind Telegram forum sessions to current topic

* fix: restore Telegram forum-topic routing (#56060) (thanks @one27001)

---------

Co-authored-by: openclaw <mgabrie.dev@gmail.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-03-31 10:18:09 +05:30
Neerav Makwana
54c69414ad fix: normalize xai tool result image replay (#58017) (thanks @neeravmakwana)
* fix(xai): normalize image tool results for responses

* fix(xai): handle reviewed tool result payload cases

* fix: normalize xai tool result image replay (#58017) (thanks @neeravmakwana)

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-03-31 10:10:51 +05:30
Neerav Makwana
763d5cea44 fix: hydrate WhatsApp participating groups on connect (#58007) (thanks @neeravmakwana)
* Web: hydrate participating groups on connect

* Web: avoid blocking inbox listeners during group hydration
2026-03-31 10:09:18 +05:30
fuller-stack-dev
235908c30e fix: support multi-kind plugins for dual slot ownership (#57507) (thanks @fuller-stack-dev)
* feat(plugins): support multi-kind plugins for dual slot ownership

* fix: address review feedback on multi-kind plugin support

- Use sorted normalizeKinds() for kind-mismatch comparison in loader.ts
  (fixes order-sensitive JSON.stringify for arrays)
- Derive slot-to-kind reverse mapping from SLOT_BY_KIND in slots.ts
  (removes hardcoded ternary that would break for future slot types)
- Use shared hasKind() helper in config-state.ts instead of inline logic

* fix: don't disable dual-kind plugin that still owns another slot

When a new plugin takes over one slot, a dual-kind plugin that still
owns the other slot must not be disabled — otherwise context engine
resolution fails at runtime.

* fix: exempt dual-kind plugins from memory slot disablement

A plugin with kind: ["memory", "context-engine"] must stay enabled even
when it loses the memory slot, so its context engine role can still load.

* fix: address remaining review feedback

- Pass manifest kind (not hardcoded "memory") in early memory gating
- Extract kindsEqual() helper for DRY kind comparison in loader.ts
- Narrow slotKeyForPluginKind back to single PluginKind with JSDoc
- Reject empty array in parsePluginKind
- Add kindsEqual tests

* fix: use toSorted() instead of sort() per lint rules

* plugins: include default slot ownership in disable checks and gate dual-kind memory registration
2026-03-31 10:06:48 +05:30
issaba1
10ac6ead6b fix: complete cron isolated model-switch retry (#57972) (thanks @issaba1)
* fix: handle LiveSessionModelSwitchError in cron isolated sessions

The main agent runner catches LiveSessionModelSwitchError and retries
with the requested model, but cron isolated sessions hit this error
and fail immediately. This extends the retry to cover cron execution.

When a cron job with `sessionTarget: 'isolated'` specifies a `model`
different from the agent's primary, the embedded runner throws
LiveSessionModelSwitchError (because the session initialized with the
wrong model). The fix wraps the initial runPrompt call in a retry loop
that catches this error, updates provider/model state, and re-runs —
mirroring the existing retry logic in agent-runner-execution.ts.

Fixes #57206

* fix: carry auth profile through cron model retry

* fix: complete cron isolated model-switch retry (#57972) (thanks @issaba1)

---------

Co-authored-by: Isaac Saba <isaacsaba@Isaacs-Mac-mini.local>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-03-31 10:03:37 +05:30
Neerav Makwana
7516b423eb fix(sandbox): relabel managed workspace mounts for SELinux (#58025) 2026-03-31 00:30:34 -04:00
ToToKr
e89bd883d8 fix: allow Telegram RFC2544 media downloads (#57624) (thanks @MoerAI)
* fix(telegram): allow RFC 2544 benchmark IPs in media download SSRF policy (#57452)

Telegram CDN file servers may resolve to IPs in the RFC 2544 benchmark range (198.18.0.0/15). The SSRF policy blocked these downloads while Discord and Slack correctly allowed them. Set allowRfc2544BenchmarkRange to true to match other channel plugins.

* fix: note Telegram media RFC2544 CDN downloads (#57624) (thanks @MoerAI)

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-03-31 09:53:31 +05:30
Ayaan Zaidi
9d9ee0f313 fix(security): restore strict SSRF pinning 2026-03-31 09:41:19 +05:30
Gustavo Madeira Santana
28ede9a23e Matrix: isolate verification events hotspot 2026-03-31 00:00:25 -04:00
Gustavo Madeira Santana
1346e6668e Matrix: trim file sync store imports 2026-03-31 00:00:25 -04:00
Gustavo Madeira Santana
57003ffddf Matrix: narrow client auth imports 2026-03-31 00:00:25 -04:00
Josh Avant
44674525f2 feat(tts): add structured provider diagnostics and fallback attempt analytics (#57954)
* feat(tts): add structured fallback diagnostics and attempt analytics

* docs(tts): document attempt-detail and provider error diagnostics

* TTS: harden fallback loops and share error helpers

* TTS: bound provider error-body reads

* tts: add double-prefix regression test and clean baseline drift

* tests(tts): satisfy error narrowing in double-prefix regression

* changelog

Signed-off-by: joshavant <830519+joshavant@users.noreply.github.com>

---------

Signed-off-by: joshavant <830519+joshavant@users.noreply.github.com>
2026-03-30 22:55:28 -05:00
Gustavo Madeira Santana
329d4bf1a8 Matrix: trim sdk test import churn 2026-03-30 23:25:39 -04:00
Kiryl Kavalenka
082778df1a fix: respect hostname-scoped proxy bypass (#50650) (thanks @kkav004)
* fix(infra/net): route through env proxy in STRICT mode while preserving DNS pinning

When HTTP_PROXY/HTTPS_PROXY env vars are configured, the SSRF guard's
pinned dispatcher connects directly to the DNS-resolved IP, bypassing the
proxy. This fails in environments where direct outbound connections are
blocked (OpenShell sandboxes, Docker containers, corporate networks).

Use `createPinnedDispatcher` with `mode: "env-proxy"` when
`hasEnvHttpProxyConfigured()` returns true. This preserves DNS-pinning
(the resolved IP is threaded into the connect option via
`EnvHttpProxyAgent`) while routing through the proxy.

- Uses `hasEnvHttpProxyConfigured()` (not `hasProxyEnvConfigured()`) to
  avoid the ALL_PROXY edge case where EnvHttpProxyAgent ignores ALL_PROXY
- Preserves STRICT mode's anti-DNS-rebinding guarantee
- TRUSTED_ENV_PROXY remains the explicit opt-in for unpinned proxy routing
- No change when proxy env vars are not set

Fixes #47598, #49948, #32947, #46306
Related: #45248

* test(infra): stabilize fetch guard proxy assertions

* fix: respect hostname-scoped proxy bypass (#50650) (thanks @kkav004)

---------

Co-authored-by: Kiryl Kavalenka <kiryl.kavalenka@whiparound.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-03-31 08:40:45 +05:30
Neerav Makwana
e394262bd8 Agents: fix subagent model precedence 2026-03-31 08:38:24 +05:30
BUGKillerKing
d4cccda570 fix: add requireAgentId to block sessions_spawn without explicit agen… (#29380)
* fix: add requireAgentId to block sessions_spawn without explicit agentId (#29368)

* Config: regenerate base schema for requireAgentId

Signed-off-by: sallyom <somalley@redhat.com>

---------

Signed-off-by: sallyom <somalley@redhat.com>
Co-authored-by: 周鹤0668001310 <zhou.he3@xydigit.com>
Co-authored-by: sallyom <somalley@redhat.com>
2026-03-30 23:06:59 -04:00
Josh Avant
c918ab4faf fix(tts): restore 3.28 schema compatibility and fallback observability (#57953)
* fix(tts): restore legacy config compatibility and fallback observability

* fix(tts): surface fallback attempts in status and telephony

* test(tts): cover /tts audio to /tts status fallback flow

* docs(tts): align migration and fallback observability guidance

* TTS: redact fallback logs and scope legacy plugin migration

* Infra: dedupe UV_EXTRA_INDEX_URL in host env policy

* Docs: scope doctor TTS migration to voice-call

* voice-call: restore strict known TTS provider validation
2026-03-30 22:05:03 -05:00
Teconomix
697dddbeb6 feat(matrix): thread-isolated sessions and per-chat-type threadReplies (#57995)
Merged via squash.

Prepared head SHA: 9ed96dd063
Co-authored-by: teconomix <6959299+teconomix@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
2026-03-30 22:45:32 -04:00
Gustavo Madeira Santana
d859746862 tests: fix matrix test typing 2026-03-30 22:39:25 -04:00
Gustavo Madeira Santana
47136536c8 tests: use multi-sample CLI startup baselines 2026-03-30 22:35:50 -04:00
Gustavo Madeira Santana
20481d424c cli: clarify cron channel help 2026-03-30 22:33:44 -04:00
Gustavo Madeira Santana
ef6250d9a0 docs: refresh channel delivery examples 2026-03-30 22:33:44 -04:00
Gustavo Madeira Santana
b3f894ea7e fix(matrix): repair fresh invited DMs (#58024)
Merged via squash.

Prepared head SHA: 69b5229632
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
2026-03-30 22:30:47 -04:00
Gustavo Madeira Santana
68e49fa791 tests: standardize CLI startup benchmarks 2026-03-30 22:15:56 -04:00
jlxyfll
1c95c41c37 fix(acpx): retain named sessions on queue owner unavailable (#56232) thanks @jlxyfll
Co-authored-by: jl <jlxyfllz@gmail.com>
Co-authored-by: sallyom <somalley@redhat.com>
2026-03-30 22:14:59 -04:00
Peter Steinberger
2cb15255a7 test: fix ci regressions 2026-03-31 02:42:13 +01:00
Shadow
d8d13f2bde Update Discord channel for sharing showcase 2026-03-30 20:39:46 -05:00
Shadow
4bec7622ab Update Discord channel for project submissions 2026-03-30 20:39:02 -05:00
Vincent Koc
67bb3454ee fix(openshell): support remote fs read mutation in tests 2026-03-31 10:30:37 +09:00
Peter Steinberger
3f1d6fe147 test: speed up cli and command suites 2026-03-31 02:25:02 +01:00
Peter Steinberger
6b6ddcd2a6 test: speed up core runtime suites 2026-03-31 02:25:02 +01:00
Peter Steinberger
f7285e0a9e test: speed up extension suites 2026-03-31 02:25:02 +01:00
Vincent Koc
1f6a964e57 fix(ci): handle missing native command capabilities 2026-03-31 10:16:06 +09:00
Vincent Koc
5d8ca42c7d fix(ci): regenerate mac host env policy 2026-03-31 10:12:20 +09:00
Gustavo Madeira Santana
bf6d3176fc scripts: preserve changelog subsection detection 2026-03-30 21:05:31 -04:00
Vincent Koc
2412357bb7 docs: fix QMD install command to use npm package instead of git URL 2026-03-31 10:05:22 +09:00
Vincent Koc
9b6ebc1992 fix(test): trim browser runtime gateway session mocks 2026-03-31 10:02:24 +09:00
Peter Steinberger
4f2df617fe fix: handle Telegram audio auto-transcription 2026-03-31 02:01:01 +01:00
Vincent Koc
121870a085 fix(sandbox): pin remote fs bridge reads (#58016)
* fix(sandbox): pin remote fs bridge reads

* fix(sandbox): reject mount-root reads in remote fs bridge

* fix(sandbox): reject non-regular targets in pinned reads
2026-03-31 09:55:51 +09:00
Vincent Koc
7ae1bb0c77 fix(host-env): block Python package index redirection env vars (#58011)
* fix(host-env): block Python package index redirection vars

* docs(changelog): note Python index override block

* Update src/infra/host-env-security-policy.json

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* fix(exec): block remaining uv index override env vars

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-03-31 09:53:32 +09:00
Vincent Koc
873549c8f1 fix(perf): bypass speech facade in core tts runtime 2026-03-31 09:51:47 +09:00
Vincent Koc
fc4ef34478 fix(config): accept block markdown table mode safely 2026-03-31 09:50:41 +09:00
Vincent Koc
8bcaf1a147 fix(test): trim reply command plugin imports 2026-03-31 09:43:54 +09:00
Shakker
81e65e119f test: mock supervisor timeout flows 2026-03-31 01:40:55 +01:00
Shakker
ee38d13f33 test: remove gateway flake from channel mcp notifications 2026-03-31 01:40:55 +01:00
Shakker
a966630a91 test: mock gateway reads in channel mcp tools 2026-03-31 01:40:55 +01:00
Shakker
6d39209430 test: split channel mcp event waiting coverage 2026-03-31 01:40:55 +01:00
Shakker
f8c7512ca5 test: stabilize channel event wait mcp flow 2026-03-31 01:40:55 +01:00
Shakker
afe4a4b260 test: clean test planner typing edges 2026-03-31 01:40:55 +01:00
Shakker
d46f64199a fix: retry bundled runtime dependency staging 2026-03-31 01:40:55 +01:00
Shakker
cefa191417 test: stabilize gateway and session cleanup flows 2026-03-31 01:40:55 +01:00
Shakker
82695bb24d test: remove timeout-prone windows ci waits 2026-03-31 01:40:55 +01:00
Shakker
da03d857f9 test: stabilize recurring windows ci suites 2026-03-31 01:40:55 +01:00
Shakker
6ab0f62b3b test: stabilize remaining windows ci timeouts 2026-03-31 01:40:55 +01:00
Shakker
7d70b1b51e test: stabilize windows registry cleanup flows 2026-03-31 01:40:55 +01:00
Shakker
72cb2a88f1 test: fix planner typing assertions 2026-03-31 01:40:55 +01:00
Shakker
5fb19f296a test: complete exec timeout child lifecycle 2026-03-31 01:40:55 +01:00
Shakker
1dda032531 style: format rebased main files 2026-03-31 01:40:55 +01:00
Shakker
020858647d test: fix qmd and discord ci regressions 2026-03-31 01:40:55 +01:00
Shakker
a8ba6f2c03 test: stabilize channel lifecycle timers in ci 2026-03-31 01:40:55 +01:00
Shakker
82681ba215 test: stabilize exec timeout assertions on windows 2026-03-31 01:40:55 +01:00
Shakker
56c9e2493b test: harden windows timeout-sensitive suites 2026-03-31 01:40:55 +01:00
Shakker
b878a34591 test: stabilize windows flow and session cleanup tests 2026-03-31 01:40:55 +01:00
Shakker
2ff7bb604c test: update planner expectations for current catalog 2026-03-31 01:40:55 +01:00
Shakker
9590e2ccae test: stabilize windows task registry and exec timeouts 2026-03-31 01:40:55 +01:00
Shakker
7ec3674b46 test: stabilize discord and channel mcp ci coverage 2026-03-31 01:40:55 +01:00
Shakker
ab0af5997d test: isolate browser snapshot navigation from proxy env 2026-03-31 01:40:55 +01:00
Shakker
4892c60ee5 test: avoid suite gateway hooks in channel mcp 2026-03-31 01:40:55 +01:00
Gustavo Madeira Santana
8d4040af58 fix(tests): align matrix verification DM fixtures 2026-03-30 20:32:49 -04:00
Gustavo Madeira Santana
31a4b45db0 Maintainer: split PR workflow script modules 2026-03-30 20:28:32 -04:00
scoootscooob
eba41dae4f fix(exec): dedupe Discord approval delivery (#58002)
* fix(exec): dedupe Discord approval delivery

* Update extensions/discord/src/approval-native.ts

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-03-30 17:27:34 -07:00
Gustavo Madeira Santana
7b7d7cc743 Matrix: short-circuit aborted monitor startup 2026-03-30 20:13:33 -04:00
Gustavo Madeira Santana
61ae6d7201 Matrix: trim shared test import churn 2026-03-30 20:13:32 -04:00
Gustavo Madeira Santana
f96e5bec39 Diffs: normalize viewer payload languages 2026-03-30 20:12:19 -04:00
Vincent Koc
af0c0862f2 fix(gateway): preserve shared-auth rate limits during mixed handshakes (#57647)
* fix(gateway): preserve shared-auth handshake rate limits

* fix(gateway): scope shared-auth lockouts to shared-auth handshakes
2026-03-31 09:08:57 +09:00
Vincent Koc
a30214a624 fix(heartbeat): block owner-only auth inheritance for exec events (#57652) 2026-03-31 09:06:51 +09:00
Vincent Koc
91f7a6b0fd fix(gateway): revoke active sessions on token rotation (#57646) 2026-03-31 09:05:34 +09:00
Gustavo Madeira Santana
bd957a3a8b Matrix: trim test import breadth 2026-03-30 19:54:40 -04:00
Gustavo Madeira Santana
fa2e051bb6 Maintainer: tighten PR workflow script
Reduce prep and merge friction in the PR wrapper by keeping rebases explicit, reusing doc-only gate results, and making review output terminal-first.

Also add clearer baseline-noise guidance for unrelated local gate failures plus worktree listing and cleanup helpers.
2026-03-30 19:48:46 -04:00
Gustavo Madeira Santana
e11b5d584c Tests: isolate Matrix extension hotspots 2026-03-30 19:36:29 -04:00
Gustavo Madeira Santana
ca6432b0d9 Skills: harden heap snapshot diffing 2026-03-30 19:36:12 -04:00
Gustavo Madeira Santana
bbd495ed63 plugins: quiet scoped manifest id warnings 2026-03-30 19:35:09 -04:00
end
2b2edaa01d fix(matrix): correct DM classification with three-tier is_direct logic and 2-member guard (#57124)
Merged via squash.

Prepared head SHA: e2ff0d5e96
Co-authored-by: w-sss <204439273+w-sss@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
2026-03-30 18:56:00 -04:00
scoootscooob
dd9d0bdd8e fix(exec): harden shell-side approval guardrails (#57839)
* fix(exec): harden approval handling

* fix(exec): tighten approval guardrails

* fix(exec): reject prefixed approval commands

* fix(exec): isolate shell approval guardrails

* fix(exec): recurse through wrapped approval commands

* fix(exec): restore allowlist wrapper import

* fix(exec): strip env wrappers before approval detection

* fix(exec): inspect nested shell wrapper options
2026-03-30 15:49:24 -07:00
scoootscooob
9ff57ac479 refactor(exec): unify channel approvals and restore routing/auth (#57838)
* fix(exec): add shared approval runtime

* fix(exec): harden shared approval runtime

* fix(exec): guard approval expiration callbacks

* fix(exec): handle approval runtime races

* fix(exec): clean up failed approval deliveries

* fix(exec): restore channel approval routing

* fix(exec): scope telegram legacy approval fallback

* refactor(exec): centralize native approval delivery

* fix(exec): harden approval auth and account routing

* test(exec): align telegram approval auth assertions

* fix(exec): align approval rebase followups

* fix(exec): clarify plugin approval not-found errors

* fix(exec): fall back to session-bound telegram accounts

* fix(exec): detect structured telegram approval misses

* test(exec): align discord approval auth coverage

* fix(exec): ignore discord dm origin channel routes

* fix(telegram): skip self-authored message echoes

* fix(exec): keep implicit approval auth non-explicit
2026-03-30 15:49:02 -07:00
Gustavo Madeira Santana
e7e15b92bd Chore: remove orphaned agent workflow 2026-03-30 18:43:14 -04:00
Gustavo Madeira Santana
b9f5d02f04 fix(matrix): restore E2EE for one-off CLI sends (#57936)
Merged via squash.

Prepared head SHA: 4b79fbea22
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
2026-03-30 17:28:33 -04:00
mappel-nv
5cc0bc936c Gateway: open config files without shell interpolation (#57921)
* Gateway: open config files without shell interpolation

Co-authored-by: peteryuqin <peter.yuqin@gmail.com>

* Gateway: align config opener review fixes

* Gateway: tidy config opener logging

* Gateway: simplify config opener error path

* Gateway: cover Windows config opener test path

* Gateway: use literal Windows config open path

---------

Co-authored-by: peteryuqin <peter.yuqin@gmail.com>
2026-03-30 15:21:25 -06:00
Dinakar Sarbada
62d6cfedee fix(doctor/plugins): skip unused Matrix inspector loads and honor enabledByDefault startup plugins (#57931)
Merged via squash.

Prepared head SHA: 634794b954
Co-authored-by: dinakars777 <250428393+dinakars777@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
2026-03-30 17:06:04 -04:00
Gustavo Madeira Santana
9a94578d47 Diffs: fall back on invalid language hints (#57902)
Merged via squash.

Prepared head SHA: 567ca3a56f
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
2026-03-30 16:30:05 -04:00
Gustavo Madeira Santana
66777e140e Diffs: return schema-shaped plugin config (#57904)
Merged via squash.

Prepared head SHA: df95f53aaa
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
2026-03-30 16:27:11 -04:00
Gustavo Madeira Santana
07900facf6 Diffs: skip unused render targets (#57909)
Merged via squash.

Prepared head SHA: 9972f3029f
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
2026-03-30 16:21:08 -04:00
Agustin Rivera
30a1690323 fix(diffs): harden viewer proxy access (#57912)
* fix(diffs): harden viewer proxy access

* fix(diffs): restore mapped loopback access
2026-03-30 14:17:27 -06:00
Altay
910134b702 fix(memory): stabilize qmd collection scoping 2026-03-30 22:41:21 +03:00
Altay
9c25544e6c test(ci): fix stale regression expectations (#57899) 2026-03-30 22:31:13 +03:00
Gustavo Madeira Santana
4a6267bfe1 Diffs: preserve base paths for viewer assets 2026-03-30 15:28:16 -04:00
Gustavo Madeira Santana
b96b1efc69 Changelog: restore Matrix history entry 2026-03-30 15:14:53 -04:00
chain710
943163a419 feat(matrix): add group chat history context for agent triggers (#57022)
Merged via squash.

Prepared head SHA: b6f88b72e8
Co-authored-by: chain710 <486539+chain710@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
2026-03-30 15:10:43 -04:00
Jacob Tomlinson
8deb9522f3 Guard marketplace and Ollama network requests (#57850)
* Plugins: guard marketplace and Ollama fetches

* Ollama: pin guarded host allowlist
2026-03-30 20:08:38 +01:00
Jacob Tomlinson
e277a37f89 Infra: block compiler env overrides (#57832) 2026-03-30 20:06:32 +01:00
Jacob Tomlinson
cfe1445953 Sandbox: sanitize SSH subprocess env (#57848)
* Sandbox: sanitize SSH subprocess env

* Sandbox: add sanitize env undefined test
2026-03-30 20:05:57 +01:00
Jacob Tomlinson
f0af186726 gateway: ignore bearer-declared HTTP operator scopes (#57783)
* gateway: ignore bearer-declared HTTP operator scopes

* gateway: key HTTP bearer guards to auth mode

* gateway: refresh rebased HTTP regression expectations

* gateway: honor resolved HTTP auth method

* gateway: remove duplicate openresponses owner flags
2026-03-30 20:04:33 +01:00
Jacob Tomlinson
2a75416634 CLI: reset remote URL after trust decline (#57828)
Co-authored-by: zsxsoft <git@zsxsoft.com>
2026-03-30 20:03:06 +01:00
Jacob Tomlinson
ad77666054 fix(voice-call): canonicalize Telnyx replay request keys (#57829) 2026-03-30 20:01:43 +01:00
Agustin Rivera
e65c265e89 Security: block exec approval shell carrier targets (#57871)
* Security: block exec approval shell carrier targets

* Tests: tighten exec approval carrier regression assertions
2026-03-30 12:35:04 -06:00
Mariano
9d9cf0d8ff Tasks: route one-task emergence through parent flows (#57874) 2026-03-30 20:25:01 +02:00
Mariano
7590c22db7 Tasks: add minimal flow registry scaffold (#57865) 2026-03-30 19:57:26 +02:00
Devin Robison
8c83128fc3 Discord: fix Group DM component interaction routing and auth (#57763)
* Discord: fix Group DM component interaction routing and auth

* Update tests
2026-03-30 11:17:53 -06:00
Devin Robison
8fdb19676a Fix Discord native commands bypassing group DM channel allowlist (#57735)
* Fix Discord native commands bypassing group DM channel allowlist

* Fix linting

* Update tests
2026-03-30 11:17:36 -06:00
Gustavo Madeira Santana
dd17dae3e5 Matrix: drop unused MatrixClient constructor params 2026-03-30 13:17:02 -04:00
Gustavo Madeira Santana
1ea85a5d0b Matrix: remove stale monitor mention regex param 2026-03-30 13:17:02 -04:00
Shakker
e8b0d57eb6 test: isolate browser navigation tests from host proxy env 2026-03-30 18:10:08 +01:00
Shakker
8746e2e216 fix: restore cli registry side-effect option 2026-03-30 18:10:08 +01:00
Shakker
ba7c98ab51 fix: align outbound media root tests with config-derived tmp paths 2026-03-30 18:10:08 +01:00
Ayaan Zaidi
1b557ffe65 fix(plugins): keep snapshot hook loads isolated 2026-03-30 22:00:54 +05:30
joelnishanth
f849b8de97 hooks: default hooks.internal.enabled to true so bundled hooks load on fresh installs
Made-with: Cursor
2026-03-30 22:00:54 +05:30
Jacob Tomlinson
3886b65ef2 fix(gateway): require node pairing before enabling node commands (#57777)
* Gateway: require node pairing for node commands

* Gateway: request node pairing on initial connect

* Gateway: filter pending node pairing commands
2026-03-30 17:29:28 +01:00
Jacob Tomlinson
6b38815f86 fix(gateway): tighten tools invoke HTTP guardrails (#57771)
* fix(gateway): tighten tools invoke HTTP guardrails

Co-authored-by: Brian Mendonca <208517100+bmendonca3@users.noreply.github.com>

* fix(security): centralize gateway HTTP deny defaults

* fix(gateway): drop duplicate scope guard after rebase

---------

Co-authored-by: Brian Mendonca <208517100+bmendonca3@users.noreply.github.com>
2026-03-30 17:16:33 +01:00
Jacob Tomlinson
1ca4261d7e fix(media): keep local roots configuration-derived (#57770)
* fix(media): keep local roots configuration-derived

Co-authored-by: Jacob Tomlinson <jtomlinson@nvidia.com>

* fix(media): simplify local root lookup

* fix(media): keep legacy local roots export
2026-03-30 17:15:03 +01:00
Shakker
aff6883f93 fix: avoid over-sharding single include-file test batches 2026-03-30 17:14:02 +01:00
Shakker
c22edbb8ee test: align ci regression stubs with production behavior 2026-03-30 17:11:06 +01:00
Shakker
555a4d896c test: stabilize media attachment cache path assertions 2026-03-30 17:11:06 +01:00
Shakker
4c45fc3575 test: remove telegram extension dependency from reply command tests 2026-03-30 17:11:06 +01:00
Jacob Tomlinson
17d0be02f2 fix(gateway): bind OpenResponses HTTP ingress as non-owner (#57778)
* fix(gateway): bind OpenResponses HTTP ingress as non-owner

Co-authored-by: bmendonca3 <208517100+bmendonca3@users.noreply.github.com>

* test(gateway): cover streaming OpenResponses non-owner ingress

---------

Co-authored-by: bmendonca3 <208517100+bmendonca3@users.noreply.github.com>
2026-03-30 17:05:29 +01:00
Jacob Tomlinson
1a75906a6f Exec approvals: prevent interpreter allow-always persistence (#57772)
* Exec approvals: block interpreter allow-always persistence

* Exec approvals: normalize interpreter allowlist formatting

* Exec approvals: normalize interpreter allowlist wrapping

* Exec approvals: tighten awk regression coverage

* Exec approvals: harden awk interpreter coverage
2026-03-30 17:03:54 +01:00
pgondhi987
b7b46ad185 fix(skills): replace readFileSync with symlink-safe, root-confined skill file loader (#57519)
* fix: replace readFileSync with symlink-safe, root-confined skill file loader

* fix(skills): preserve directory-name fallback when frontmatter omits name

* fix: harden skill loader path containment

---------

Co-authored-by: Jacob Tomlinson <jacobtomlinson@users.noreply.github.com>
2026-03-30 17:03:05 +01:00
Jacob Tomlinson
7a5c5f33d0 Infra: block auth env vars from workspace dotenv (#57767)
* Infra: block auth env vars from workspace dotenv

* Infra: block workspace dotenv auth key variants

* Infra: block workspace dotenv live auth keys
2026-03-30 17:01:22 +01:00
Jacob Tomlinson
29cb1e3c7e Gateway: tighten HTTP tool invoke authorization (#57773)
* Gateway: harden HTTP tool invoke access

* Gateway: strengthen HTTP tools invoke regression coverage

* Gateway: keep owner-only tools off HTTP
2026-03-30 16:59:40 +01:00
Jacob Tomlinson
ae703ab0e7 infra: harden identifier entropy and delay jitter (#57744)
* infra: harden identifier entropy and delay jitter

* test: make randomness hardening deterministic in CI
2026-03-30 16:57:30 +01:00
Jacob Tomlinson
32a4a47d60 Agents: pin apply-patch workspace mutations (#56016)
* Agents: pin apply-patch file ops to workspace

* Agents: resolve apply-patch review feedback

* Infra: fallback pinned path helper spawn failures
2026-03-30 16:49:49 +01:00
pgondhi987
6d341cf366 fix(auto-reply): thread per-agent tools.exec defaults into reply directives (#57689)
* fix(auto-reply): thread per-agent tools.exec defaults into exec overrides

* test(auto-reply): add session-override and inline-directive priority tests for exec agent defaults
2026-03-30 16:46:54 +01:00
samzong
09bb93c6e0 fix(subagents): correct duration display showing 5-6x inflated runtime (#57739)
Merged via squash.

Prepared head SHA: 018bbbca4d
Co-authored-by: samzong <13782141+samzong@users.noreply.github.com>
Co-authored-by: frankekn <4488090+frankekn@users.noreply.github.com>
Reviewed-by: @frankekn
2026-03-30 23:44:36 +08:00
Jacob Tomlinson
f011d0be28 fix(gateway): treat OpenAI HTTP ingress as non-owner (#57769)
Co-authored-by: Brian Mendonca <208517100+bmendonca3@users.noreply.github.com>
2026-03-30 16:26:53 +01:00
Sean
c6f2db1506 fix: prevent gateway attachment offload regressions (#55513) (thanks @Syysean)
* feat(gateway): implement claim check pattern to prevent OOM on large attachments

* fix: sanitize mediaId, refine trimEnd, remove warn log, add threshold and absolute path

* fix: enforce maxBytes before decoding and use dynamic path from saveMediaBuffer

* fix: enforce absolute maxBytes limit before Buffer allocation and preserve file extensions

* fix: align saveMediaBuffer arguments and satisfy oxfmt linter

* chore: strictly enforce linting rules (curly braces, unused vars, and error typing)

* fix: restrict offload to mainstream mimes to avoid extension-loss bug in store.ts for BMP/TIFF

* fix: restrict offload to mainstream mimes to bypass store.ts extension-loss bug

* chore: document bmp/tiff exclusion from offload whitelist in MIME_TO_EXT

* feat: implement agent-side resolver for opaque media URIs and finalize contract

* fix: support unicode media URIs and allow consecutive dots in safe IDs based on Codex review

* fix(gateway): enforce strict fail-fast for oversized media to prevent OOM bypass

* refactor(gateway): harden media offload with performance and security optimizations

This update refines the Claim Check pattern with industrial-grade guards:

- Performance: Implemented sampled Base64 validation for large payloads (>4KB) to prevent event loop blocking.
- Security: Added null-byte (\u0000) detection and reinforced path traversal guards.
- I18n: Updated media-uri regex to a blacklist-based character class for Unicode/Chinese filename support, with oxlint bypass for intentional control regex.
- Robustness: Enhanced error diagnostics with JSON-serialized IDs.

* fix: add HEIC/HEIF to offload allowlist and pass maxBytes to saveMediaBuffer

* fix(gateway): clean up offloaded media files on attachment parse failure

Address Codex review feedback: track saved media IDs and implement best-effort cleanup via deleteMediaBuffer if subsequent attachments fail validation, preventing orphaned files on disk.

* fix(gateway): enforce full base64 validation to prevent whitespace padding bypass

Address Codex review feedback: remove early return in isValidBase64 so padded payloads cannot bypass offload thresholds and reintroduce memory pressure. Updated related comments.

* fix(gateway): preserve offloaded media metadata and fix validation error mapping

Address Codex review feedback:
- Add \offloadedRefs\ to \ParsedMessageWithImages\ to expose structured metadata for offloaded attachments, preventing transcript media loss.
- Move \erifyDecodedSize\ outside the storage try-catch block to correctly surface client base64 validation failures as 4xx errors instead of 5xx \MediaOffloadError\.
- Add JSDoc TODOs indicating that upstream callers (chat.ts, agent.ts, server-node-events.ts) must explicitly pass the \supportsImages\ flag.

* fix(agents): explicitly allow media store dir when loading offloaded images

Address Codex review feedback: Pass getMediaDir() to loadWebMedia's localRoots for media-uri refs to prevent legacy path resolution mismatches from silently dropping large attachments.

* fix(gateway): resolve attachment offload regressions and error mapping

Address Codex review feedback:
- Pass \supportsImages\ dynamically in \chat.ts\ and \gent.ts\ based on model catalog, and explicitly in \server-node-events.ts\.
- Persist \offloadedRefs\ into the transcript pipeline in \chat.ts\ to preserve media metadata for >2MB attachments.
- Correctly map \MediaOffloadError\ to 5xx (UNAVAILABLE) to differentiate server storage faults from 4xx client validation errors.

* fix(gateway): dynamically compute supportsImages for overrides and node events

Address follow-up Codex review feedback:

- Use effective model (including overrides) to compute \supportsImages\ in \gent.ts\.

- Move session load earlier in \server-node-events.ts\ to dynamically compute \supportsImages\ rather than hardcoding true.

* fix(gateway): resolve capability edge cases reported by codex

Address final Codex edge cases:
- Refactor \gent.ts\ to compute \supportsImages\ even when no session key is present, ensuring text-only override requests without sessions safely drop attachments.
- Update catalog lookups in \chat.ts\, \gent.ts\, and \server-node-events.ts\ to strictly match both \id\ and \provider\ to prevent cross-provider model collisions.

* fix(agents): restore before_install hook for skill installs

Restore the plugin scanner security hook that was accidentally dropped during merge conflict resolution.

* fix: resolve attachment pathing, defer parsing after auth gates, and clean up node-event mocks

* fix: resolve syntax errors in test-env, fix missing helper imports, and optimize parsing sequence in node events

* fix(gateway): re-enforce message length limit after attachment parsing

Adds a secondary check to ensure the 20,000-char cap remains effective even after media markers are appended during the offload flow.

* fix(gateway): prevent dropping valid small images and clean up orphaned media on size rejection

* fix(gateway): share attachment image capability checks

* fix(gateway): preserve mixed attachment order

* fix: fail closed on unknown image capability (#55513) (thanks @Syysean)

* fix: classify offloaded attachment refs explicitly (#55513) (thanks @Syysean)

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-03-30 20:54:40 +05:30
Shakker
3ad747e25f style: apply formatter cleanups 2026-03-30 16:20:27 +01:00
Shakker
ab141df4b5 Skills: tighten env path guidance 2026-03-30 16:10:13 +01:00
Shakker
a3de1f5f55 Skills: prefer active OpenClaw paths 2026-03-30 16:10:13 +01:00
Ayaan Zaidi
08d365f481 test: pin android explicit setup auth selection 2026-03-30 20:39:20 +05:30
Ayaan Zaidi
fa150f8828 fix: use explicit setup auth for android gateway connect 2026-03-30 20:39:20 +05:30
Ayaan Zaidi
deead11dcd fix(android): restore setup-code operator bootstrap connect 2026-03-30 20:39:19 +05:30
Ayaan Zaidi
2dced6b4a0 fix: allow setup-code bootstrap auth for operator pairing 2026-03-30 20:39:19 +05:30
Ayaan Zaidi
e0281849c0 fix: unblock android onboarding after bootstrap pairing 2026-03-30 20:39:19 +05:30
Ayaan Zaidi
fec329ce8d fix: handle android bootstrap-only setup codes 2026-03-30 20:39:19 +05:30
Robin Waslander
4d369a3400 harden session-status tool visibility guard for all callers 2026-03-30 16:48:12 +02:00
Jacob Tomlinson
5cca380840 msteams: filter thread history by sender allowlist (#57723)
* msteams: filter thread history by sender allowlist

* tests: merge msteams thread authz coverage

* msteams: preserve thread allowlist fallback matching
2026-03-30 15:38:26 +01:00
Jacob Tomlinson
7e08669715 synology-chat: add webhook in-flight guard (#57722)
* synology-chat: add webhook in-flight guard

* tests: clarify synology in-flight limit assertion

* synology-chat: scope webhook in-flight budget per account
2026-03-30 15:37:02 +01:00
Jacob Tomlinson
7a953a5227 Plugins: block install when source scan fails (#57729)
* Plugins: block unsafe install scan fallthrough

* Tests: normalize install scanner formatting

* Plugins: avoid duplicate scan failure messaging

* Plugins: preserve hook install block codes
2026-03-30 15:36:08 +01:00
Jacob Tomlinson
8db20c1965 sandbox: block sensitive external bind sources (#56024)
* sandbox: block sensitive external bind sources

* sandbox: cache blocked bind paths

* sandbox: harden blocked bind path aliases

* sandbox: block os-home bind secrets

* sandbox: refresh blocked bind path aliases
2026-03-30 15:34:53 +01:00
Jacob Tomlinson
3216df7923 gateway: enforce embeddings HTTP write scope (#57721) 2026-03-30 15:32:03 +01:00
Robin Waslander
85647949a4 tighten phone-control scope helper extraction 2026-03-30 16:17:17 +02:00
Jacob Tomlinson
c5c10adc02 gateway: trim control UI bootstrap payload (#57727) 2026-03-30 15:08:19 +01:00
Robin Waslander
847912f3e2 harden phone-control command scope checks 2026-03-30 15:52:55 +02:00
Jacob Tomlinson
3b9dab0ece OpenShell: harden mirror sync boundaries (#57693)
* OpenShell: harden mirror sync boundaries

* OpenShell: polish mirror hardening tests

* OpenShell: preserve trusted mirror symlinks

* OpenShell: bound mirror fs work globally
2026-03-30 14:51:44 +01:00
Robin Waslander
a4e447a16e harden talk-voice config persistence scope checks 2026-03-30 15:38:37 +02:00
Jacob Tomlinson
ee52f64226 Discord: gate audio preflight on member access (#57695)
* Discord: gate audio preflight on member access

* Discord: trim unauthorized sender logging

* CI: retrigger after review follow-up

* Discord: document blocked-sender log privacy
2026-03-30 14:38:22 +01:00
Jacob Tomlinson
a77928b108 Gateway: harden node event trust boundaries (#57691)
* Gateway: harden node event trust boundaries

* Gateway: preserve trusted summary prefixes

* Gateway: prefix multiline channel summaries
2026-03-30 14:22:15 +01:00
Ayaan Zaidi
9d5c5230c5 fix: restore default HTTP operator scopes (#57596) (thanks @openperf) 2026-03-30 18:51:13 +05:30
openperf
3d659fd356 refactor(gateway ): remove unreachable null check in resolveGatewayRequestedOperatorScopes 2026-03-30 18:51:13 +05:30
openperf
fe2eb185ff fix(gateway ): restore default operator scopes for pure HTTP token auth 2026-03-30 18:51:13 +05:30
Jacob Tomlinson
8b88b927cb gateway: clear unbound scopes for trusted-proxy auth (#57692)
* gateway: clear unbound scopes for trusted-proxy auth

* gateway: isolate trusted-proxy scope test branch
2026-03-30 14:19:00 +01:00
Jacob Tomlinson
566fb73d9d reply: enforce ACP attachment roots (#57690)
* reply: enforce ACP attachment roots

* media: harden local attachment cache reads

* reply: clarify ACP attachment skip logs

* reply: keep ACP attachments path-only
2026-03-30 14:04:02 +01:00
Jacob Tomlinson
3834d47099 MS Teams: validate webhook auth before JSON parsing (#57686) 2026-03-30 13:46:40 +01:00
pgondhi987
bc3b05dce4 fix(infra): block BROWSER, GIT_EDITOR, GIT_SEQUENCE_EDITOR from inherited host env (#57559) 2026-03-30 12:31:04 +01:00
pgondhi987
c4fa8635d0 fix(telegram): gate audio preflight transcription on sender authorization (#57566)
* Telegram: gate audio preflight transcription on sender authorization

* fix: honor telegram audio preflight command auth
2026-03-30 12:19:31 +01:00
Vincent Koc
348b094fe8 fix(test): satisfy telegram pairing seam 2026-03-30 20:05:29 +09:00
Kunal Karmakar
34b0a19a16 fix: use azure-openai-responses for Azure custom providers (#50851) (thanks @kunalk16)
* Add azure-openai-responses

* Unit tests update for updated API

* Add entry for PR #50851

* Add comma to address PR comment

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* Address PR comment on sanitization of output

* Address review comment

* Revert commits

* Revert commit

* Update changelog stating Azure OpenAI only

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* Add references

* Address PR comment on sanitization of output

* Address review comment

* Revert commits

* Revert commit

* Address PR comment on sanitization of output

* Address review comment

* Revert commits

* Revert commit

* Fix generated file

* Add azure openai responses to OPENAI_RESPONSES_APIS

* Add azure openai responses to createParallelToolCallsWrapper

* Adding azure openai responses to attempt.ts

* Add azure openai responses to google.ts

* Address PR comment on sanitization of output

* Revert commit

* Address PR comment on sanitization of output

* Revert commit

* Address PR comment on sanitization of output

* Revert commit

* Fix changelog

* Fix linting

* fix: cover azure responses wrapper path (#50851) (thanks @kunalk16)

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-03-30 16:17:03 +05:30
Vincent Koc
2fbd5e3f5f fix(test): trim telegram command registry imports 2026-03-30 19:34:47 +09:00
Vincent Koc
69916e8082 fix(twitch): align markdown table mode type 2026-03-30 19:32:14 +09:00
Vincent Koc
b7de04f23f fix(memory): preserve shared qmd collection names (#57628)
* fix(memory): preserve shared qmd collection names

* fix(memory): canonicalize qmd path containment
2026-03-30 19:29:35 +09:00
Vincent Koc
85f3136cfc fix(test): use plugin public surfaces in reply command tests 2026-03-30 19:28:10 +09:00
Vincent Koc
54f7221465 fix(slack): restore table block mode seam (#57591)
* fix(slack): restore table block mode seam

Restore the shared markdown/config seam needed for Slack Block Kit table support, while coercing non-Slack block mode back to code.

* fix(slack): narrow table block seam defaults

Keep Slack table block mode opt-in in this seam-only PR, clamp collected placeholder offsets, and align fallback-table rendering with Slack block limits.

* fix(slack): bound table fallback rendering

Avoid spread-based maxima and bound Slack table fallback rendering by row, column, cell-width, and total-output limits to prevent resource exhaustion.

* fix(slack): keep block mode inactive in seam PR

Keep markdown table block mode schema-valid but runtime-resolved to code until the Slack send path is wired to emit table attachments.

* fix(slack): normalize configured block mode safely

Accept configured markdown table block mode at parse time, then normalize it back to code during runtime resolution so seam-only branches do not drop table content.
2026-03-30 19:25:01 +09:00
Vincent Koc
56be744a7a docs: simplify automation decision flowchart to linear path 2026-03-30 19:22:56 +09:00
Vincent Koc
b0738210ff docs: replace ASCII decision tree with mermaid flowchart on automation hub 2026-03-30 19:20:13 +09:00
4025 changed files with 336073 additions and 103175 deletions

View File

@@ -1,380 +0,0 @@
---
description: Update OpenClaw from upstream when branch has diverged (ahead/behind)
---
# OpenClaw Upstream Sync Workflow
Use this workflow when your fork has diverged from upstream (e.g., "18 commits ahead, 29 commits behind").
## Quick Reference
```bash
# Check divergence status
git fetch upstream && git rev-list --left-right --count main...upstream/main
# Full sync (rebase preferred)
git fetch upstream && git rebase upstream/main && pnpm install && pnpm build && ./scripts/restart-mac.sh
# Check for Swift 6.2 issues after sync
grep -r "FileManager\.default\|Thread\.isMainThread" src/ apps/ --include="*.swift"
```
---
## Step 1: Assess Divergence
```bash
git fetch upstream
git log --oneline --left-right main...upstream/main | head -20
```
This shows:
- `<` = your local commits (ahead)
- `>` = upstream commits you're missing (behind)
**Decision point:**
- Few local commits, many upstream → **Rebase** (cleaner history)
- Many local commits or shared branch → **Merge** (preserves history)
---
## Step 2A: Rebase Strategy (Preferred)
Replays your commits on top of upstream. Results in linear history.
```bash
# Ensure working tree is clean
git status
# Rebase onto upstream
git rebase upstream/main
```
### Handling Rebase Conflicts
```bash
# When conflicts occur:
# 1. Fix conflicts in the listed files
# 2. Stage resolved files
git add <resolved-files>
# 3. Continue rebase
git rebase --continue
# If a commit is no longer needed (already in upstream):
git rebase --skip
# To abort and return to original state:
git rebase --abort
```
### Common Conflict Patterns
| File | Resolution |
| ---------------- | ------------------------------------------------ |
| `package.json` | Take upstream deps, keep local scripts if needed |
| `pnpm-lock.yaml` | Accept upstream, regenerate with `pnpm install` |
| `*.patch` files | Usually take upstream version |
| Source files | Merge logic carefully, prefer upstream structure |
---
## Step 2B: Merge Strategy (Alternative)
Preserves all history with a merge commit.
```bash
git merge upstream/main --no-edit
```
Resolve conflicts same as rebase, then:
```bash
git add <resolved-files>
git commit
```
---
## Step 3: Rebuild Everything
After sync completes:
```bash
# Install dependencies (regenerates lock if needed)
pnpm install
# Build TypeScript
pnpm build
# Build UI assets
pnpm ui:build
# Run diagnostics
pnpm clawdbot doctor
```
---
## Step 4: Rebuild macOS App
```bash
# Full rebuild, sign, and launch
./scripts/restart-mac.sh
# Or just package without restart
pnpm mac:package
```
### Install to /Applications
```bash
# Kill running app
pkill -x "OpenClaw" || true
# Move old version
mv /Applications/OpenClaw.app /tmp/OpenClaw-backup.app
# Install new build
cp -R dist/OpenClaw.app /Applications/
# Launch
open /Applications/OpenClaw.app
```
---
## Step 4A: Verify macOS App & Agent
After rebuilding the macOS app, always verify it works correctly:
```bash
# Check gateway health
pnpm clawdbot health
# Verify no zombie processes
ps aux | grep -E "(clawdbot|gateway)" | grep -v grep
# Test agent functionality by sending a verification message
pnpm clawdbot agent --message "Verification: macOS app rebuild successful - agent is responding." --session-id YOUR_TELEGRAM_SESSION_ID
# Confirm the message was received on Telegram
# (Check your Telegram chat with the bot)
```
**Important:** Always wait for the Telegram verification message before proceeding. If the agent doesn't respond, troubleshoot the gateway or model configuration before pushing.
---
## Step 5: Handle Swift/macOS Build Issues (Common After Upstream Sync)
Upstream updates may introduce Swift 6.2 / macOS 26 SDK incompatibilities. Use analyze-mode for systematic debugging:
### Analyze-Mode Investigation
```bash
# Gather context with parallel agents
morph-mcp_warpgrep_codebase_search search_string="Find deprecated FileManager.default and Thread.isMainThread usages in Swift files" repo_path="/Volumes/Main SSD/Developer/clawdis"
morph-mcp_warpgrep_codebase_search search_string="Locate Peekaboo submodule and macOS app Swift files with concurrency issues" repo_path="/Volumes/Main SSD/Developer/clawdis"
```
### Common Swift 6.2 Fixes
**FileManager.default Deprecation:**
```bash
# Search for deprecated usage
grep -r "FileManager\.default" src/ apps/ --include="*.swift"
# Replace with proper initialization
# OLD: FileManager.default
# NEW: FileManager()
```
**Thread.isMainThread Deprecation:**
```bash
# Search for deprecated usage
grep -r "Thread\.isMainThread" src/ apps/ --include="*.swift"
# Replace with modern concurrency check
# OLD: Thread.isMainThread
# NEW: await MainActor.run { ... } or DispatchQueue.main.sync { ... }
```
### Peekaboo Submodule Fixes
```bash
# Check Peekaboo for concurrency issues
cd src/canvas-host/a2ui
grep -r "Thread\.isMainThread\|FileManager\.default" . --include="*.swift"
# Fix and rebuild submodule
cd /Volumes/Main SSD/Developer/clawdis
pnpm canvas:a2ui:bundle
```
### macOS App Concurrency Fixes
```bash
# Check macOS app for issues
grep -r "Thread\.isMainThread\|FileManager\.default" apps/macos/ --include="*.swift"
# Clean and rebuild after fixes
cd apps/macos && rm -rf .build .swiftpm
./scripts/restart-mac.sh
```
### Model Configuration Updates
If upstream introduced new model configurations:
```bash
# Check for OpenRouter API key requirements
grep -r "openrouter\|OPENROUTER" src/ --include="*.ts" --include="*.js"
# Update openclaw.json with fallback chains
# Add model fallback configurations as needed
```
---
## Step 6: Verify & Push
```bash
# Verify everything works
pnpm clawdbot health
pnpm test
# Push (force required after rebase)
git push origin main --force-with-lease
# Or regular push after merge
git push origin main
```
---
## Troubleshooting
### Build Fails After Sync
```bash
# Clean and rebuild
rm -rf node_modules dist
pnpm install
pnpm build
```
### Type Errors (Bun/Node Incompatibility)
Common issue: `fetch.preconnect` type mismatch. Fix by using `FetchLike` type instead of `typeof fetch`.
### macOS App Crashes on Launch
Usually resource bundle mismatch. Full rebuild required:
```bash
cd apps/macos && rm -rf .build .swiftpm
./scripts/restart-mac.sh
```
### Patch Failures
```bash
# Check patch status
pnpm install 2>&1 | grep -i patch
# If patches fail, they may need updating for new dep versions
# Check patches/ directory against package.json patchedDependencies
```
### Swift 6.2 / macOS 26 SDK Build Failures
**Symptoms:** Build fails with deprecation warnings about `FileManager.default` or `Thread.isMainThread`
**Search-Mode Investigation:**
```bash
# Exhaustive search for deprecated APIs
morph-mcp_warpgrep_codebase_search search_string="Find all Swift files using deprecated FileManager.default or Thread.isMainThread" repo_path="/Volumes/Main SSD/Developer/clawdis"
```
**Quick Fix Commands:**
```bash
# Find all affected files
find . -name "*.swift" -exec grep -l "FileManager\.default\|Thread\.isMainThread" {} \;
# Replace FileManager.default with FileManager()
find . -name "*.swift" -exec sed -i '' 's/FileManager\.default/FileManager()/g' {} \;
# For Thread.isMainThread, need manual review of each usage
grep -rn "Thread\.isMainThread" --include="*.swift" .
```
**Rebuild After Fixes:**
```bash
# Clean all build artifacts
rm -rf apps/macos/.build apps/macos/.swiftpm
rm -rf src/canvas-host/a2ui/.build
# Rebuild Peekaboo bundle
pnpm canvas:a2ui:bundle
# Full macOS rebuild
./scripts/restart-mac.sh
```
---
## Automation Script
Save as `scripts/sync-upstream.sh`:
```bash
#!/usr/bin/env bash
set -euo pipefail
echo "==> Fetching upstream..."
git fetch upstream
echo "==> Current divergence:"
git rev-list --left-right --count main...upstream/main
echo "==> Rebasing onto upstream/main..."
git rebase upstream/main
echo "==> Installing dependencies..."
pnpm install
echo "==> Building..."
pnpm build
pnpm ui:build
echo "==> Running doctor..."
pnpm clawdbot doctor
echo "==> Rebuilding macOS app..."
./scripts/restart-mac.sh
echo "==> Verifying gateway health..."
pnpm clawdbot health
echo "==> Checking for Swift 6.2 compatibility issues..."
if grep -r "FileManager\.default\|Thread\.isMainThread" src/ apps/ --include="*.swift" --quiet; then
echo "⚠️ Found potential Swift 6.2 deprecated API usage"
echo " Run manual fixes or use analyze-mode investigation"
else
echo "✅ No obvious Swift deprecation issues found"
fi
echo "==> Testing agent functionality..."
# Note: Update YOUR_TELEGRAM_SESSION_ID with actual session ID
pnpm clawdbot agent --message "Verification: Upstream sync and macOS rebuild completed successfully." --session-id YOUR_TELEGRAM_SESSION_ID || echo "Warning: Agent test failed - check Telegram for verification message"
echo "==> Done! Check Telegram for verification message, then run 'git push --force-with-lease' when ready."
```

View File

@@ -16,6 +16,7 @@ Use this skill for Parallels guest workflows and smoke interpretation. Do not lo
- Pass `--json` for machine-readable summaries.
- Per-phase logs land under `/tmp/openclaw-parallels-*`.
- Do not run local and gateway agent turns in parallel on the same fresh workspace or session.
- If `main` is moving under active multi-agent work, prefer a detached worktree pinned to one commit for long Parallels suites. The smoke scripts now verify the packed tgz commit instead of live `git rev-parse HEAD`, but a pinned worktree still avoids noisy rebuild/version drift during reruns.
- For `prlctl exec`, pass the VM name before `--current-user` (`prlctl exec "$VM" --current-user ...`), not the other way around.
- If the workflow installs OpenClaw from a repo checkout instead of the site installer/npm release, finish by installing a real guest CLI shim and verifying it in a fresh guest shell. `pnpm openclaw ...` inside the repo is not enough for handoff parity.
- On macOS guests, prefer a user-global install plus a stable PATH-visible shim:
@@ -28,10 +29,13 @@ Use this skill for Parallels guest workflows and smoke interpretation. Do not lo
- Preferred entrypoint: `pnpm test:parallels:npm-update`
- Flow: fresh snapshot -> install npm package baseline -> smoke -> install current main tgz on the same guest -> smoke again.
- Same-guest update verification should set the default model explicitly to `openai/gpt-5.4` before the agent turn and use a fresh explicit `--session-id` so old session model state does not leak into the check.
- The aggregate npm-update wrapper must resolve the Linux VM with the same Ubuntu fallback policy as `parallels-linux-smoke.sh` before both fresh and update lanes. On Peter's current host, missing `Ubuntu 24.04.3 ARM64` should fall back to `Ubuntu 25.10`.
- The aggregate npm-update wrapper must resolve the Linux VM with the same Ubuntu fallback policy as `parallels-linux-smoke.sh` before both fresh and update lanes. Treat any Ubuntu guest with major version `>= 24` as acceptable when the exact default VM is missing, preferring the closest version match. On Peter's current host today, missing `Ubuntu 24.04.3 ARM64` should fall back to `Ubuntu 25.10`.
- On Windows same-guest update checks, restart the gateway after the npm upgrade before `gateway status` / `agent`; in-place global npm updates can otherwise leave stale hashed `dist/*` module imports alive in the running service.
- For Windows same-guest update checks, prefer the done-file/log-drain PowerShell runner pattern over one long-lived `prlctl exec ... powershell -EncodedCommand ...` transport. The guest can finish successfully while the outer `prlctl exec` still hangs.
- The Windows same-guest update helper should write stage markers to its log before long steps like tgz download and `npm install -g` so the outer progress monitor does not sit on `waiting for first log line` during healthy but quiet installs.
- Linux same-guest update verification should also export `HOME=/root`, pass `OPENAI_API_KEY` via `prlctl exec ... /usr/bin/env`, and use `openclaw agent --local`; the fresh Linux baseline does not rely on persisted gateway credentials.
- The npm-update wrapper now prints per-lane progress from the nested log files. If a lane still looks stuck, inspect the nested logs in `runDir` first (`macos-fresh.log`, `windows-fresh.log`, `linux-fresh.log`, `macos-update.log`, `windows-update.log`, `linux-update.log`) instead of assuming the outer wrapper hung.
- If the wrapper fails a lane, read the auto-dumped tail first, then the full nested lane log under `/tmp/openclaw-parallels-npm-update.*`.
## CLI invocation footgun
@@ -43,6 +47,7 @@ Use this skill for Parallels guest workflows and smoke interpretation. Do not lo
- Default to the snapshot closest to `macOS 26.3.1 latest`.
- On Peter's Tahoe VM, `fresh-latest-march-2026` can hang in `prlctl snapshot-switch`; if restore times out there, rerun with `--snapshot-hint 'macOS 26.3.1 latest'` before blaming auth or the harness.
- The macOS smoke should include a dashboard load phase after gateway health: resolve the tokenized URL with `openclaw dashboard --no-open`, verify the served HTML contains the Control UI title/root shell, then open Safari and require an established localhost TCP connection from Safari to the gateway port.
- If a packaged install regresses with `500` on `/`, `/healthz`, or `__openclaw/control-ui-config.json` after `fresh.install-main` or `upgrade.install-main`, suspect bundled plugin runtime deps resolving from the package root `node_modules` rather than `dist/extensions/*/node_modules`. Repro quickly with a real `npm pack`/global install lane before blaming dashboard auth or Safari.
- `prlctl exec` is fine for deterministic repo commands, but use the guest Terminal or `prlctl enter` when installer parity or shell-sensitive behavior matters.
- Multi-word `openclaw agent --message ...` checks should go through a guest shell wrapper (`guest_current_user_sh` / `guest_current_user_cli` or `/bin/sh -lc ...`), not raw `prlctl exec ... node openclaw.mjs ...`, or the message can be split into extra argv tokens and Commander reports `too many arguments for 'agent'`.
- When ref-mode onboarding stores `OPENAI_API_KEY` as an env secret ref, the post-onboard agent verification should also export `OPENAI_API_KEY` for the guest command. The gateway can still reject with pairing-required and fall back to embedded execution, and that fallback needs the env-backed credential available in the shell.
@@ -62,7 +67,8 @@ Use this skill for Parallels guest workflows and smoke interpretation. Do not lo
- Windows global `npm install -g` phases can stay quiet for a minute or more even when healthy; inspect the phase log before calling it hung, and only treat it as a regression once the retry wrapper or timeout trips.
- Fresh Windows ref-mode onboard should use the same background PowerShell runner plus done-file/log-drain pattern as the npm-update helper, including startup materialization checks, host-side timeouts on short poll `prlctl exec` calls, and retry-on-poll-failure behavior for transient transport flakes.
- Fresh Windows ref-mode agent verification should set `OPENAI_API_KEY` in the PowerShell environment before invoking `openclaw.cmd agent`, for the same pairing-required fallback reason as macOS.
- The Windows upgrade smoke lane should restart the managed gateway after `upgrade.install-main` and before `upgrade.onboard-ref`, or the old process can keep the previous gateway token and fail `gateway-health` with `unauthorized: gateway token mismatch`.
- The standalone Windows upgrade smoke lane should stop the managed gateway after `upgrade.install-main` and before `upgrade.onboard-ref`. Restarting before onboard can leave the old process alive on the pre-onboard token while onboard rewrites `~/.openclaw/openclaw.json`, which then fails `gateway-health` with `unauthorized: gateway token mismatch`.
- If standalone Windows upgrade fails with a gateway token mismatch but `pnpm test:parallels:npm-update` passes, trust the mismatch as a standalone ref-onboard ordering bug first; the npm-update helper does not re-run ref-mode onboard on the same guest.
- Keep onboarding and status output ASCII-clean in logs; fancy punctuation becomes mojibake in current capture paths.
- If you hit an older run with `rc=255` plus an empty `fresh.install-main.log` or `upgrade.install-main.log`, treat it as a likely `prlctl exec` transport drop after guest start-up, not immediate proof of an npm/package failure.
@@ -70,13 +76,13 @@ Use this skill for Parallels guest workflows and smoke interpretation. Do not lo
- Preferred entrypoint: `pnpm test:parallels:linux`
- Use the snapshot closest to fresh `Ubuntu 24.04.3 ARM64`.
- If that exact VM is missing on the host, fall back to the closest Ubuntu guest with a fresh poweroff snapshot. On Peter's host today, that is `Ubuntu 25.10`.
- If that exact VM is missing on the host, any Ubuntu guest with major version `>= 24` is acceptable; prefer the closest versioned Ubuntu guest with a fresh poweroff snapshot. On Peter's host today, that is `Ubuntu 25.10`.
- Use plain `prlctl exec`; `--current-user` is not the right transport on this snapshot.
- Fresh snapshots may be missing `curl`, and `apt-get update` can fail on clock skew. Bootstrap with `apt-get -o Acquire::Check-Date=false update` and install `curl ca-certificates`.
- Fresh `main` tgz smoke still needs the latest-release installer first because the snapshot has no Node or npm before bootstrap.
- This snapshot does not have a usable `systemd --user` session; managed daemon install is unsupported.
- `prlctl exec` reaps detached Linux child processes on this snapshot, so detached background gateway runs are not trustworthy smoke signals.
- Treat `gateway=skipped-no-detached-linux-gateway` plus `daemon=systemd-user-unavailable` as baseline on that Linux lane, not a regression.
- The Linux smoke now falls back to a manual `setsid openclaw gateway run --bind loopback --port 18789 --force` launch with `HOME=/root` and the provider secret exported, then verifies `gateway status --deep --require-rpc` when available.
- If Linux gateway bring-up fails, inspect `/tmp/openclaw-parallels-linux-gateway.log` in the guest phase logs first; the common failure mode is a missing provider secret in the launched gateway environment.
## Discord roundtrip

View File

@@ -17,7 +17,7 @@ Use this skill for release and publish-time workflow. Keep ordinary development
## Keep release channel naming aligned
- `stable`: tagged releases only, published to npm `latest` and then mirrored onto npm `beta` unless `beta` already points at a newer prerelease
- `stable`: tagged releases only, published to npm `beta` by default; operators may target npm `latest` explicitly or promote later
- `beta`: prerelease tags like `vYYYY.M.D-beta.N`, with npm dist-tag `beta`
- Prefer `-beta.N`; do not mint new `-1` or `-2` beta suffixes
- `dev`: moving head on `main`
@@ -64,7 +64,8 @@ Use this skill for release and publish-time workflow. Keep ordinary development
Before tagging or publishing, run:
```bash
node --import tsx scripts/release-check.ts
pnpm build
pnpm ui:build
pnpm release:check
pnpm test:install:smoke
```
@@ -92,7 +93,7 @@ node --import tsx scripts/openclaw-npm-postpublish-verify.ts <published-version>
- Default release checks:
- `pnpm check`
- `pnpm build`
- `node --import tsx scripts/release-check.ts`
- `pnpm ui:build`
- `pnpm release:check`
- `OPENCLAW_INSTALL_SMOKE_SKIP_NONROOT=1 pnpm test:install:smoke`
- Check all release-related build surfaces touched by the release, not only the npm package.
@@ -115,10 +116,19 @@ node --import tsx scripts/openclaw-npm-postpublish-verify.ts <published-version>
## Use the right auth flow
- OpenClaw publish uses GitHub trusted publishing.
- Stable npm promotion from `beta` to `latest` is an explicit mode on
`.github/workflows/openclaw-npm-release.yml`, but it still needs a valid
`NPM_TOKEN` because `npm dist-tag` management is separate from trusted
publishing.
- The publish run must be started manually with `workflow_dispatch`.
- The npm workflow and the private mac publish workflow accept
`preflight_only=true` to run validation/build/package steps without uploading
public release assets.
- Real npm publish requires a prior successful npm preflight run id so the
publish job promotes the prepared tarball instead of rebuilding it.
- Real private mac publish requires a prior successful private mac preflight
run id so the publish job promotes the prepared artifacts instead of
rebuilding or renotarizing them again.
- The private mac workflow also accepts `smoke_test_only=true` for branch-safe
workflow smoke tests that use ad-hoc signing, skip notarization, skip shared
appcast generation, and do not prove release readiness.
@@ -129,17 +139,23 @@ node --import tsx scripts/openclaw-npm-postpublish-verify.ts <published-version>
workflow change before merge.
- `.github/workflows/macos-release.yml` in `openclaw/openclaw` is now a
public validation-only handoff. It validates the tag/release state and points
operators to the private repo; it does not build or publish macOS artifacts.
operators to the private repo. It still rebuilds the JS outputs needed for
release validation, but it does not sign, notarize, or publish macOS
artifacts.
- `openclaw/releases-private/.github/workflows/openclaw-macos-validate.yml`
is the required private mac validation lane for `swift test`; keep it green
before any real mac publish run starts.
- Real mac preflight and real mac publish both use
`openclaw/releases-private/.github/workflows/openclaw-macos-publish.yml`.
- The private mac workflow runs on GitHub's xlarge macOS runner and uses a
SwiftPM cache because the Swift build/test/package path is CPU-heavy.
- The private mac validation lane runs on GitHub's standard macOS runner.
- The private mac preflight path runs on GitHub's xlarge macOS runner and uses
a SwiftPM cache because the build/sign/notarize/package path is CPU-heavy.
- Private mac preflight uploads notarized build artifacts as workflow artifacts
instead of uploading public GitHub release assets.
- Private smoke-test runs upload ad-hoc, non-notarized build artifacts as
workflow artifacts and intentionally skip stable `appcast.xml` generation.
- npm preflight, public mac validation, and private mac preflight must all pass
before any real publish run starts.
- npm preflight, public mac validation, private mac validation, and private mac
preflight must all pass before any real publish run starts.
- Real publish runs must be dispatched from `main`; branch-dispatched publish
attempts should fail before the protected environment is reached.
- The release workflows stay tag-based; rely on the documented release sequence
@@ -147,8 +163,8 @@ node --import tsx scripts/openclaw-npm-postpublish-verify.ts <published-version>
- The `npm-release` environment must be approved by `@openclaw/openclaw-release-managers` before publish continues.
- Mac publish uses
`openclaw/releases-private/.github/workflows/openclaw-macos-publish.yml` for
build, signing, notarization, packaged mac artifact generation, and
stable-feed `appcast.xml` artifact generation.
private mac preflight artifact preparation and real publish artifact
promotion.
- Real private mac publish uploads the packaged `.zip`, `.dmg`, and
`.dSYM.zip` assets to the existing GitHub release in `openclaw/openclaw`
automatically when `OPENCLAW_PUBLIC_REPO_RELEASE_TOKEN` is present in the
@@ -206,31 +222,45 @@ node --import tsx scripts/openclaw-npm-postpublish-verify.ts <published-version>
7. Create and push the git tag.
8. Create or refresh the matching GitHub release.
9. Start `.github/workflows/openclaw-npm-release.yml` with `preflight_only=true`
and wait for it to pass.
and choose the intended `npm_dist_tag` (`beta` default; `latest` only for
an intentional direct stable publish). Wait for it to pass. Save that run id
because the real publish requires it to reuse the prepared npm tarball.
10. Start `.github/workflows/macos-release.yml` in `openclaw/openclaw` and wait
for the public validation-only run to pass.
11. Start
`openclaw/releases-private/.github/workflows/openclaw-macos-validate.yml`
with the same tag and wait for the private mac validation lane to pass.
12. Start
`openclaw/releases-private/.github/workflows/openclaw-macos-publish.yml`
with `preflight_only=true` and wait for it to pass.
12. If any preflight or validation run fails, fix the issue on a new commit,
with `preflight_only=true` and wait for it to pass. Save that run id because
the real publish requires it to reuse the notarized mac artifacts.
13. If any preflight or validation run fails, fix the issue on a new commit,
delete the tag and matching GitHub release, recreate them from the fixed
commit, and rerun all relevant preflights from scratch before continuing.
Never reuse old preflight results after the commit changes.
13. Start `.github/workflows/openclaw-npm-release.yml` with the same tag for
the real publish.
14. Wait for `npm-release` approval from `@openclaw/openclaw-release-managers`.
15. Start
14. Start `.github/workflows/openclaw-npm-release.yml` with the same tag for
the real publish, choose `npm_dist_tag` (`beta` default, `latest` only when
you intentionally want direct stable publish), keep it the same as the
preflight run, and pass the successful npm `preflight_run_id`.
15. Wait for `npm-release` approval from `@openclaw/openclaw-release-managers`.
16. If the stable release was published to `beta`, start
`.github/workflows/openclaw-npm-release.yml` again after beta validation
passes with the same stable tag, `promote_beta_to_latest=true`,
`preflight_only=false`, empty `preflight_run_id`, and `npm_dist_tag=beta`,
then verify `latest` now points at that version.
17. Start
`openclaw/releases-private/.github/workflows/openclaw-macos-publish.yml`
for the real publish and wait for success.
16. Verify the successful real private mac run uploaded the `.zip`, `.dmg`,
for the real publish with the successful private mac `preflight_run_id` and
wait for success.
18. Verify the successful real private mac run uploaded the `.zip`, `.dmg`,
and `.dSYM.zip` artifacts to the existing GitHub release in
`openclaw/openclaw`.
17. For stable releases, download `macos-appcast-<tag>` from the successful
19. For stable releases, download `macos-appcast-<tag>` from the successful
private mac run, update `appcast.xml` on `main`, and verify the feed.
18. For beta releases, publish the mac assets but expect no shared production
20. For beta releases, publish the mac assets but expect no shared production
`appcast.xml` artifact and do not update the shared production feed unless a
separate beta feed exists.
19. After publish, verify npm and the attached release artifacts.
21. After publish, verify npm and the attached release artifacts.
## GHSA advisory work

View File

@@ -1,11 +1,11 @@
---
name: openclaw-test-heap-leaks
description: Investigate `pnpm test` memory growth, Vitest worker OOMs, and suspicious RSS increases in OpenClaw using the `scripts/test-parallel.mjs` heap snapshot tooling. Use when Codex needs to reproduce test-lane memory growth, collect repeated `.heapsnapshot` files, compare snapshots from the same worker PID, distinguish transformed-module retention from real data leaks, and fix or reduce the impact by patching cleanup logic or isolating hotspot tests.
description: Investigate `pnpm test` memory growth, Vitest worker OOMs, and suspicious RSS increases in OpenClaw using the `scripts/test-parallel.mjs` heap snapshot tooling. Use when Codex needs to reproduce test-lane memory growth, collect repeated `.heapsnapshot` files, compare snapshots from the same worker PID, triage likely transformed-module retention versus likely runtime leaks, and fix or reduce the impact by patching cleanup logic or isolating hotspot tests.
---
# OpenClaw Test Heap Leaks
Use this skill for test-memory investigations. Do not guess from RSS alone when heap snapshots are available.
Use this skill for test-memory investigations. Do not guess from RSS alone when heap snapshots are available. Treat snapshot-name deltas as triage evidence, not proof, until retainers or dominators support the call.
## Workflow
@@ -14,19 +14,23 @@ Use this skill for test-memory investigations. Do not guess from RSS alone when
- `pnpm canvas:a2ui:bundle && OPENCLAW_TEST_MEMORY_TRACE=1 OPENCLAW_TEST_HEAPSNAPSHOT_INTERVAL_MS=60000 OPENCLAW_TEST_HEAPSNAPSHOT_DIR=.tmp/heapsnap OPENCLAW_TEST_WORKERS=2 OPENCLAW_TEST_MAX_OLD_SPACE_SIZE_MB=6144 pnpm test`
- Keep `OPENCLAW_TEST_MEMORY_TRACE=1` enabled so the wrapper prints per-file RSS summaries alongside the snapshots.
- If the report is about a specific shard or worker budget, preserve that shape.
- Before you analyze snapshots, identify the real lane names from `[test-parallel] start ...` lines or `pnpm test --plan`. Do not assume a single `unit-fast` lane; local plans often split into `unit-fast-batch-*`.
2. Wait for repeated snapshots before concluding anything.
- Take at least two intervals from the same lane.
- Compare snapshots from the same PID inside one lane directory such as `.tmp/heapsnap/unit-fast/`.
- Use `scripts/heapsnapshot-delta.mjs` to compare either two files directly or the earliest/latest pair per PID in one lane directory.
- Compare snapshots from the same PID inside the real lane directory such as `.tmp/heapsnap/unit-fast-batch-2/`.
- Use `.agents/skills/openclaw-test-heap-leaks/scripts/heapsnapshot-delta.mjs` to compare either two files directly or the earliest/latest pair per PID in one lane directory.
- If the helper suggests transformed-module retention, confirm the top entries in DevTools retainers/dominators before calling it solved.
3. Classify the growth before choosing a fix.
- If growth is dominated by Vite/Vitest transformed source strings, `Module`, `system / Context`, bytecode, descriptor arrays, or property maps, treat it as retained module graph growth in long-lived workers.
- If growth is dominated by Vite/Vitest transformed source strings, `Module`, `system / Context`, bytecode, descriptor arrays, or property maps, treat it as likely retained module graph growth in long-lived workers.
- If growth is dominated by app objects, caches, buffers, server handles, timers, mock state, sqlite state, or similar runtime objects, treat it as a likely cleanup or lifecycle leak.
- If the names are ambiguous, stop short of a confident label and inspect retainers/dominators in DevTools for the top deltas.
4. Fix the right layer.
- For retained transformed-module growth in shared workers:
- Move hotspot files out of `unit-fast` by updating `test/fixtures/test-parallel.behavior.json`.
- For likely retained transformed-module growth in shared workers:
- Prefer timing and hotspot-driven scheduling fixes first. Check whether the file is already represented in `test/fixtures/test-timings.unit.json` and whether `scripts/test-update-memory-hotspots.mjs` should refresh the measured hotspot manifest before hand-editing behavior overrides.
- Move hotspot files out of the real shared lane by updating `test/fixtures/test-parallel.behavior.json` only when timing-driven peeling is insufficient.
- Prefer `singletonIsolated` for files that are safe alone but inflate shared worker heaps.
- If the file should already have been peeled out by timings but is absent from `test/fixtures/test-timings.unit.json`, call that out explicitly. Missing timings are a scheduling blind spot.
- For real leaks:
@@ -40,24 +44,24 @@ Use this skill for test-memory investigations. Do not guess from RSS alone when
## Heuristics
- Do not call everything a leak. In this repo, large `unit-fast` growth can be a worker-lifetime problem rather than an application object leak.
- Do not call everything a leak. In this repo, large `unit-fast` or `unit-fast-batch-*` growth can be a worker-lifetime problem rather than an application object leak.
- `scripts/test-parallel.mjs` and `scripts/test-parallel-memory.mjs` are the primary control points for wrapper diagnostics.
- The lane names printed by `[test-parallel] start ...` and `[test-parallel][mem] summary ...` tell you where to focus.
- When one or two files account for most of the delta and they are missing from timings, reducing impact by isolating them is usually the first pragmatic fix.
- When the same retained object families grow across multiple intervals in the same worker PID, trust the snapshots over intuition.
- When the same retained object families grow across multiple intervals in the same worker PID, trust the snapshots over intuition, then confirm ambiguous calls with retainer evidence.
## Snapshot Comparison
- Direct comparison:
- `node .agents/skills/openclaw-test-heap-leaks/scripts/heapsnapshot-delta.mjs before.heapsnapshot after.heapsnapshot`
- Auto-select earliest/latest snapshots per PID within one lane:
- `node .agents/skills/openclaw-test-heap-leaks/scripts/heapsnapshot-delta.mjs --lane-dir .tmp/heapsnap/unit-fast`
- `node .agents/skills/openclaw-test-heap-leaks/scripts/heapsnapshot-delta.mjs --lane-dir .tmp/heapsnap/unit-fast-batch-2`
- Useful flags:
- `--top 40`
- `--min-kb 32`
- `--pid 16133`
Read the top positive deltas first. Large positive growth in module-transform artifacts suggests lane isolation; large positive growth in runtime objects suggests a real leak.
Read the top positive deltas first. Large positive growth in module-transform artifacts suggests lane isolation; large positive growth in runtime objects suggests a real leak. If the names alone do not settle it, open the same snapshot pair in DevTools and inspect retainers/dominators for the top rows before declaring root cause.
## Output Expectations
@@ -66,6 +70,6 @@ When using this skill, report:
- The exact reproduce command.
- Which lane and PID were compared.
- The dominant retained object families from the snapshot delta.
- Whether the issue is a real leak or shared-worker retained module growth.
- Whether the issue is a likely real leak or likely shared-worker retained module growth, plus whether retainers/dominators confirmed it.
- The concrete fix or impact-reduction patch.
- What you verified, and what snapshot overhead prevented you from verifying.

View File

@@ -64,6 +64,243 @@ function parseArgs(argv) {
return options;
}
class JsonStreamScanner {
constructor(filePath) {
this.stream = fs.createReadStream(filePath, {
encoding: "utf8",
highWaterMark: 1024 * 1024,
});
this.iterator = this.stream[Symbol.asyncIterator]();
this.buffer = "";
this.offset = 0;
this.done = false;
}
compactBuffer() {
if (this.offset > 65536) {
this.buffer = this.buffer.slice(this.offset);
this.offset = 0;
}
}
async ensureAvailable(count = 1) {
while (!this.done && this.buffer.length - this.offset < count) {
const next = await this.iterator.next();
if (next.done) {
this.done = true;
break;
}
this.buffer += next.value;
}
}
async peek() {
await this.ensureAvailable(1);
return this.buffer[this.offset] ?? null;
}
async next() {
await this.ensureAvailable(1);
if (this.offset >= this.buffer.length) {
return null;
}
const char = this.buffer[this.offset];
this.offset += 1;
this.compactBuffer();
return char;
}
async skipWhitespace() {
while (true) {
const char = await this.peek();
if (char === null || !/\s/u.test(char)) {
return;
}
await this.next();
}
}
async expectChar(expected) {
const char = await this.next();
if (char !== expected) {
fail(`Expected ${expected} but found ${char ?? "<eof>"}`);
}
}
async find(sequence) {
let matched = 0;
while (true) {
const char = await this.next();
if (char === null) {
fail(`Could not find ${sequence}`);
}
if (char === sequence[matched]) {
matched += 1;
if (matched === sequence.length) {
return;
}
continue;
}
matched = char === sequence[0] ? 1 : 0;
if (matched === sequence.length) {
return;
}
}
}
async readBalancedObject() {
const start = await this.next();
if (start !== "{") {
fail(`Expected { but found ${start ?? "<eof>"}`);
}
let text = "{";
let depth = 1;
let inString = false;
let escaped = false;
while (depth > 0) {
const char = await this.next();
if (char === null) {
fail("Unexpected EOF while reading JSON object");
}
text += char;
if (inString) {
if (escaped) {
escaped = false;
} else if (char === "\\") {
escaped = true;
} else if (char === '"') {
inString = false;
}
continue;
}
if (char === '"') {
inString = true;
} else if (char === "{") {
depth += 1;
} else if (char === "}") {
depth -= 1;
}
}
return text;
}
async parseNumberArray(onValue) {
await this.skipWhitespace();
await this.expectChar("[");
await this.skipWhitespace();
if ((await this.peek()) === "]") {
await this.next();
return;
}
let token = "";
let index = 0;
const flush = () => {
if (token.length === 0) {
fail("Unexpected empty number token");
}
const value = Number.parseInt(token, 10);
if (!Number.isFinite(value)) {
fail(`Invalid numeric token: ${token}`);
}
onValue(value, index);
index += 1;
token = "";
};
while (true) {
const char = await this.next();
if (char === null) {
fail("Unexpected EOF while reading number array");
}
if (char === "]") {
flush();
return;
}
if (char === ",") {
flush();
continue;
}
if (/\s/u.test(char)) {
continue;
}
token += char;
}
}
async readJsonString() {
await this.expectChar('"');
let value = "";
while (true) {
const char = await this.next();
if (char === null) {
fail("Unexpected EOF while reading JSON string");
}
if (char === '"') {
return value;
}
if (char !== "\\") {
value += char;
continue;
}
const escaped = await this.next();
if (escaped === null) {
fail("Unexpected EOF while reading JSON string escape");
}
if (escaped === "u") {
let hex = "";
for (let index = 0; index < 4; index += 1) {
const hexChar = await this.next();
if (hexChar === null) {
fail("Unexpected EOF while reading JSON unicode escape");
}
hex += hexChar;
}
value += String.fromCharCode(Number.parseInt(hex, 16));
continue;
}
value +=
escaped === "b"
? "\b"
: escaped === "f"
? "\f"
: escaped === "n"
? "\n"
: escaped === "r"
? "\r"
: escaped === "t"
? "\t"
: escaped;
}
}
async parseStringArray(onValue) {
await this.skipWhitespace();
await this.expectChar("[");
await this.skipWhitespace();
if ((await this.peek()) === "]") {
await this.next();
return;
}
let index = 0;
while (true) {
const value = await this.readJsonString();
onValue(value, index);
index += 1;
await this.skipWhitespace();
const separator = await this.next();
if (separator === "]") {
return;
}
if (separator !== ",") {
fail(`Expected , or ] but found ${separator ?? "<eof>"}`);
}
await this.skipWhitespace();
}
}
}
function parseHeapFilename(filePath) {
const base = path.basename(filePath);
const match = base.match(
@@ -151,38 +388,89 @@ function resolvePair(options) {
};
}
function loadSummary(filePath) {
const data = JSON.parse(fs.readFileSync(filePath, "utf8"));
const meta = data.snapshot?.meta;
async function parseSnapshotMeta(scanner) {
await scanner.find('"snapshot":');
await scanner.skipWhitespace();
const metaObjectText = await scanner.readBalancedObject();
const parsed = JSON.parse(metaObjectText);
return parsed?.meta ?? null;
}
async function buildSummary(filePath) {
const scanner = new JsonStreamScanner(filePath);
const meta = await parseSnapshotMeta(scanner);
if (!meta) {
fail(`Invalid heap snapshot: ${filePath}`);
}
const nodeFieldCount = meta.node_fields.length;
const typeNames = meta.node_types[0];
const strings = data.strings;
const typeIndex = meta.node_fields.indexOf("type");
const nameIndex = meta.node_fields.indexOf("name");
const selfSizeIndex = meta.node_fields.indexOf("self_size");
if (typeIndex === -1 || nameIndex === -1 || selfSizeIndex === -1) {
fail(`Unsupported heap snapshot schema: ${filePath}`);
}
const summary = new Map();
for (let offset = 0; offset < data.nodes.length; offset += nodeFieldCount) {
const type = typeNames[data.nodes[offset + typeIndex]];
const name = strings[data.nodes[offset + nameIndex]];
const selfSize = data.nodes[offset + selfSizeIndex];
const key = `${type}\t${name}`;
const current = summary.get(key) ?? {
type,
name,
const summaryByIndex = new Map();
let nodeCount = 0;
let currentTypeId = 0;
let currentNameId = 0;
let currentSelfSize = 0;
await scanner.find('"nodes":');
await scanner.parseNumberArray((value, index) => {
const fieldIndex = index % nodeFieldCount;
if (fieldIndex === typeIndex) {
currentTypeId = value;
return;
}
if (fieldIndex === nameIndex) {
currentNameId = value;
return;
}
if (fieldIndex === selfSizeIndex) {
currentSelfSize = value;
}
if (fieldIndex !== nodeFieldCount - 1) {
return;
}
const key = `${currentTypeId}\t${currentNameId}`;
const current = summaryByIndex.get(key) ?? {
typeId: currentTypeId,
nameId: currentNameId,
selfSize: 0,
count: 0,
};
current.selfSize += selfSize;
current.selfSize += currentSelfSize;
current.count += 1;
summary.set(key, current);
summaryByIndex.set(key, current);
nodeCount += 1;
});
const requiredNameIds = new Set(
Array.from(summaryByIndex.values(), (entry) => entry.nameId).filter((value) => value >= 0),
);
const nameStrings = new Map();
await scanner.find('"strings":');
await scanner.parseStringArray((value, index) => {
if (requiredNameIds.has(index)) {
nameStrings.set(index, value);
}
});
const summary = new Map();
for (const entry of summaryByIndex.values()) {
const key = `${typeNames[entry.typeId] ?? "unknown"}\t${nameStrings.get(entry.nameId) ?? ""}`;
summary.set(key, {
type: typeNames[entry.typeId] ?? "unknown",
name: nameStrings.get(entry.nameId) ?? "",
selfSize: entry.selfSize,
count: entry.count,
});
}
return {
nodeCount: data.snapshot.node_count,
nodeCount,
summary,
};
}
@@ -205,11 +493,11 @@ function truncate(text, maxLength) {
return text.length <= maxLength ? text : `${text.slice(0, maxLength - 1)}`;
}
function main() {
async function main() {
const options = parseArgs(process.argv.slice(2));
const pair = resolvePair(options);
const before = loadSummary(pair.before);
const after = loadSummary(pair.after);
const before = await buildSummary(pair.before);
const after = await buildSummary(pair.after);
const minBytes = options.minKb * 1024;
const rows = [];
@@ -262,4 +550,4 @@ function main() {
}
}
main();
await main();

0
.codex Normal file
View File

View File

@@ -33,6 +33,8 @@ node_modules
**/.next
coverage
**/coverage
docs/.generated
**/.generated
*.log
tmp
**/tmp

9
.github/labeler.yml vendored
View File

@@ -59,6 +59,11 @@
- any-glob-to-any-file:
- "extensions/nostr/**"
- "docs/channels/nostr.md"
"channel: qqbot":
- changed-files:
- any-glob-to-any-file:
- "extensions/qqbot/**"
- "docs/channels/qqbot.md"
"channel: signal":
- changed-files:
- any-glob-to-any-file:
@@ -241,6 +246,10 @@
- changed-files:
- any-glob-to-any-file:
- "extensions/deepseek/**"
"extensions: stepfun":
- changed-files:
- any-glob-to-any-file:
- "extensions/stepfun/**"
"extensions: anthropic":
- changed-files:
- any-glob-to-any-file:

View File

@@ -1,110 +0,0 @@
name: CI Bun
on:
push:
branches: [main]
concurrency:
group: ci-bun-push-${{ github.run_id }}
cancel-in-progress: false
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
jobs:
preflight:
runs-on: blacksmith-16vcpu-ubuntu-2404
timeout-minutes: 20
outputs:
run_bun_checks: ${{ steps.manifest.outputs.run_bun_checks }}
bun_checks_matrix: ${{ steps.manifest.outputs.bun_checks_matrix }}
steps:
- name: Checkout
uses: actions/checkout@v6
with:
submodules: false
- name: Setup Node environment
uses: ./.github/actions/setup-node-env
with:
install-bun: "false"
install-deps: "false"
use-sticky-disk: "false"
- name: Build Bun CI manifest
id: manifest
env:
OPENCLAW_CI_DOCS_ONLY: "false"
OPENCLAW_CI_DOCS_CHANGED: "false"
OPENCLAW_CI_RUN_NODE: "true"
OPENCLAW_CI_RUN_MACOS: "false"
OPENCLAW_CI_RUN_ANDROID: "false"
OPENCLAW_CI_RUN_WINDOWS: "false"
OPENCLAW_CI_RUN_SKILLS_PYTHON: "false"
OPENCLAW_CI_HAS_CHANGED_EXTENSIONS: "false"
OPENCLAW_CI_CHANGED_EXTENSIONS_MATRIX: '{"include":[]}'
run: node scripts/ci-write-manifest-outputs.mjs --workflow ci-bun
build-bun-artifacts:
needs: [preflight]
if: needs.preflight.outputs.run_bun_checks == 'true'
runs-on: blacksmith-16vcpu-ubuntu-2404
timeout-minutes: 20
steps:
- name: Checkout
uses: actions/checkout@v6
with:
submodules: false
- name: Setup Node environment
uses: ./.github/actions/setup-node-env
with:
install-bun: "false"
use-sticky-disk: "false"
- name: Build A2UI bundle
run: pnpm canvas:a2ui:bundle
- name: Upload A2UI bundle artifact
uses: actions/upload-artifact@v7
with:
name: canvas-a2ui-bundle
path: src/canvas-host/a2ui/
include-hidden-files: true
retention-days: 1
bun-checks:
name: ${{ matrix.check_name }}
needs: [preflight, build-bun-artifacts]
if: needs.preflight.outputs.run_bun_checks == 'true'
runs-on: blacksmith-16vcpu-ubuntu-2404
timeout-minutes: 60
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.preflight.outputs.bun_checks_matrix) }}
steps:
- name: Checkout
uses: actions/checkout@v6
with:
submodules: false
- name: Setup Node environment
uses: ./.github/actions/setup-node-env
with:
install-bun: "true"
use-sticky-disk: "false"
- name: Download A2UI bundle artifact
uses: actions/download-artifact@v8
with:
name: canvas-a2ui-bundle
path: src/canvas-host/a2ui/
- name: Run Bun test shard
env:
SHARD_COUNT: ${{ matrix.shard_count }}
SHARD_INDEX: ${{ matrix.shard_index }}
shell: bash
run: |
set -euo pipefail
OPENCLAW_TEST_ISOLATE=1 bunx vitest run --config vitest.unit.config.ts --shard "$SHARD_INDEX/$SHARD_COUNT"

View File

@@ -17,8 +17,8 @@ env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
jobs:
# Preflight: establish routing truth and planner-owned matrices once, then let
# real work fan out from a single source of truth.
# Preflight: establish routing truth and job matrices once, then let real
# work fan out from a single source of truth.
preflight:
if: github.event_name != 'pull_request' || !github.event.pull_request.draft
runs-on: blacksmith-16vcpu-ubuntu-2404
@@ -36,7 +36,8 @@ jobs:
changed_extensions_matrix: ${{ steps.manifest.outputs.changed_extensions_matrix }}
run_build_artifacts: ${{ steps.manifest.outputs.run_build_artifacts }}
run_checks_fast: ${{ steps.manifest.outputs.run_checks_fast }}
checks_fast_matrix: ${{ steps.manifest.outputs.checks_fast_matrix }}
checks_fast_core_matrix: ${{ steps.manifest.outputs.checks_fast_core_matrix }}
checks_fast_extensions_matrix: ${{ steps.manifest.outputs.checks_fast_extensions_matrix }}
run_checks: ${{ steps.manifest.outputs.run_checks }}
checks_matrix: ${{ steps.manifest.outputs.checks_matrix }}
run_extension_fast: ${{ steps.manifest.outputs.run_extension_fast }}
@@ -103,7 +104,7 @@ jobs:
run: |
node --input-type=module <<'EOF'
import { appendFileSync } from "node:fs";
import { listChangedExtensionIds } from "./scripts/test-extension.mjs";
import { listChangedExtensionIds } from "./scripts/lib/changed-extensions.mjs";
const extensionIds = listChangedExtensionIds({
base: process.env.BASE_SHA,
@@ -129,7 +130,150 @@ jobs:
OPENCLAW_CI_RUN_SKILLS_PYTHON: ${{ steps.changed_scope.outputs.run_skills_python || 'false' }}
OPENCLAW_CI_HAS_CHANGED_EXTENSIONS: ${{ steps.changed_extensions.outputs.has_changed_extensions || 'false' }}
OPENCLAW_CI_CHANGED_EXTENSIONS_MATRIX: ${{ steps.changed_extensions.outputs.changed_extensions_matrix || '{"include":[]}' }}
run: node scripts/ci-write-manifest-outputs.mjs --workflow ci
run: |
node --input-type=module <<'EOF'
import { appendFileSync } from "node:fs";
import {
createExtensionTestShards,
DEFAULT_EXTENSION_TEST_SHARD_COUNT,
} from "./scripts/lib/extension-test-plan.mjs";
const parseBoolean = (value, fallback = false) => {
if (value === undefined) return fallback;
const normalized = value.trim().toLowerCase();
if (normalized === "true" || normalized === "1") return true;
if (normalized === "false" || normalized === "0" || normalized === "") return false;
return fallback;
};
const parseJson = (value, fallback) => {
try {
return value ? JSON.parse(value) : fallback;
} catch {
return fallback;
}
};
const createMatrix = (include) => ({ include });
const outputPath = process.env.GITHUB_OUTPUT;
const eventName = process.env.GITHUB_EVENT_NAME ?? "pull_request";
const isPush = eventName === "push";
const docsOnly = parseBoolean(process.env.OPENCLAW_CI_DOCS_ONLY);
const docsChanged = parseBoolean(process.env.OPENCLAW_CI_DOCS_CHANGED);
const runNode = parseBoolean(process.env.OPENCLAW_CI_RUN_NODE) && !docsOnly;
const runMacos = parseBoolean(process.env.OPENCLAW_CI_RUN_MACOS) && !docsOnly;
const runAndroid = parseBoolean(process.env.OPENCLAW_CI_RUN_ANDROID) && !docsOnly;
const runWindows = parseBoolean(process.env.OPENCLAW_CI_RUN_WINDOWS) && !docsOnly;
const runSkillsPython = parseBoolean(process.env.OPENCLAW_CI_RUN_SKILLS_PYTHON) && !docsOnly;
const hasChangedExtensions =
parseBoolean(process.env.OPENCLAW_CI_HAS_CHANGED_EXTENSIONS) && !docsOnly;
const changedExtensionsMatrix = hasChangedExtensions
? parseJson(process.env.OPENCLAW_CI_CHANGED_EXTENSIONS_MATRIX, { include: [] })
: { include: [] };
const extensionShardMatrix = createMatrix(
runNode
? createExtensionTestShards({
shardCount: DEFAULT_EXTENSION_TEST_SHARD_COUNT,
}).map((shard) => ({
check_name: shard.checkName,
extensions_csv: shard.extensionIds.join(","),
shard_index: shard.index + 1,
task: "extensions-batch",
}))
: [],
);
const manifest = {
docs_only: docsOnly,
docs_changed: docsChanged,
run_node: runNode,
run_macos: runMacos,
run_android: runAndroid,
run_skills_python: runSkillsPython,
run_windows: runWindows,
has_changed_extensions: hasChangedExtensions,
changed_extensions_matrix: changedExtensionsMatrix,
run_build_artifacts: runNode,
run_checks_fast: runNode,
checks_fast_core_matrix: createMatrix(
runNode
? [
{ check_name: "checks-fast-bundled", runtime: "node", task: "bundled" },
{
check_name: "checks-fast-contracts-protocol",
runtime: "node",
task: "contracts-protocol",
},
]
: [],
),
checks_fast_extensions_matrix: extensionShardMatrix,
run_checks: runNode,
checks_matrix: createMatrix(
runNode
? [
{ check_name: "checks-node-test", runtime: "node", task: "test" },
{ check_name: "checks-node-channels", runtime: "node", task: "channels" },
...(isPush
? [
{
check_name: "checks-node-compat-node22",
runtime: "node",
task: "compat-node22",
node_version: "22.x",
cache_key_suffix: "node22",
},
]
: []),
]
: [],
),
run_extension_fast: hasChangedExtensions,
extension_fast_matrix: createMatrix(
hasChangedExtensions
? (changedExtensionsMatrix.include ?? []).map((entry) => ({
check_name: `extension-fast-${entry.extension}`,
extension: entry.extension,
}))
: [],
),
run_check: runNode,
run_check_additional: runNode,
run_build_smoke: runNode,
run_check_docs: docsChanged,
run_skills_python_job: runSkillsPython,
run_checks_windows: runWindows,
checks_windows_matrix: createMatrix(
runWindows
? [{ check_name: "checks-windows-node-test", runtime: "node", task: "test" }]
: [],
),
run_macos_node: runMacos,
macos_node_matrix: createMatrix(
runMacos ? [{ check_name: "macos-node", runtime: "node", task: "test" }] : [],
),
run_macos_swift: runMacos,
run_android_job: runAndroid,
android_matrix: createMatrix(
runAndroid
? [
{ check_name: "android-test-play", task: "test-play" },
{ check_name: "android-test-third-party", task: "test-third-party" },
{ check_name: "android-build-play", task: "build-play" },
{ check_name: "android-build-third-party", task: "build-third-party" },
]
: [],
),
};
for (const [key, value] of Object.entries(manifest)) {
appendFileSync(
outputPath,
`${key}=${typeof value === "string" ? value : JSON.stringify(value)}\n`,
"utf8",
);
}
EOF
# Run the fast security/SCM checks in parallel with scope detection so the
# main Node jobs do not have to wait for Python/pre-commit setup.
@@ -277,7 +421,7 @@ jobs:
include-hidden-files: true
retention-days: 1
checks-fast:
checks-fast-core:
name: ${{ matrix.check_name }}
needs: [preflight]
if: needs.preflight.outputs.run_checks_fast == 'true'
@@ -285,7 +429,7 @@ jobs:
timeout-minutes: 60
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.preflight.outputs.checks_fast_matrix) }}
matrix: ${{ fromJson(needs.preflight.outputs.checks_fast_core_matrix) }}
steps:
- name: Checkout
uses: actions/checkout@v6
@@ -306,8 +450,8 @@ jobs:
run: |
set -euo pipefail
case "$TASK" in
extensions)
pnpm test:extensions
bundled)
pnpm test:bundled
;;
contracts|contracts-protocol)
pnpm build
@@ -320,6 +464,49 @@ jobs:
;;
esac
checks-fast-extensions-shard:
name: ${{ matrix.check_name }}
needs: [preflight]
if: needs.preflight.outputs.run_checks_fast == 'true'
runs-on: blacksmith-16vcpu-ubuntu-2404
timeout-minutes: 60
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.preflight.outputs.checks_fast_extensions_matrix) }}
steps:
- name: Checkout
uses: actions/checkout@v6
with:
persist-credentials: false
submodules: false
- name: Setup Node environment
uses: ./.github/actions/setup-node-env
with:
install-bun: "false"
use-sticky-disk: "false"
- name: Run extension shard
env:
OPENCLAW_EXTENSION_BATCH: ${{ matrix.extensions_csv }}
run: pnpm test:extensions:batch -- "$OPENCLAW_EXTENSION_BATCH"
checks-fast-extensions:
name: checks-fast-extensions
needs: [preflight, checks-fast-extensions-shard]
if: always() && needs.preflight.outputs.run_checks_fast == 'true'
runs-on: blacksmith-16vcpu-ubuntu-2404
timeout-minutes: 5
steps:
- name: Verify extension shards
env:
SHARD_RESULT: ${{ needs.checks-fast-extensions-shard.result }}
run: |
if [ "$SHARD_RESULT" != "success" ]; then
echo "Extension shard checks failed: $SHARD_RESULT" >&2
exit 1
fi
checks:
name: ${{ matrix.check_name }}
needs: [preflight, build-artifacts]
@@ -354,21 +541,12 @@ jobs:
if: (github.event_name != 'pull_request' || matrix.task != 'compat-node22') && matrix.runtime == 'node' && (matrix.task == 'test' || matrix.task == 'channels' || matrix.task == 'compat-node22')
env:
TASK: ${{ matrix.task }}
SHARD_COUNT: ${{ matrix.shard_count || '' }}
SHARD_INDEX: ${{ matrix.shard_index || '' }}
run: |
# `pnpm test` runs `scripts/test-parallel.mjs`, which spawns multiple Node processes.
# Default heap limits have been too low on Linux CI (V8 OOM near 4GB).
echo "OPENCLAW_TEST_WORKERS=2" >> "$GITHUB_ENV"
echo "OPENCLAW_TEST_MAX_OLD_SPACE_SIZE_MB=6144" >> "$GITHUB_ENV"
echo "OPENCLAW_VITEST_MAX_WORKERS=2" >> "$GITHUB_ENV"
if [ "$TASK" = "channels" ]; then
echo "OPENCLAW_TEST_WORKERS=1" >> "$GITHUB_ENV"
echo "OPENCLAW_VITEST_MAX_WORKERS=1" >> "$GITHUB_ENV"
echo "OPENCLAW_TEST_ISOLATE=1" >> "$GITHUB_ENV"
fi
if [ -n "$SHARD_COUNT" ] && [ -n "$SHARD_INDEX" ]; then
echo "OPENCLAW_TEST_SHARDS=$SHARD_COUNT" >> "$GITHUB_ENV"
echo "OPENCLAW_TEST_SHARD_INDEX=$SHARD_INDEX" >> "$GITHUB_ENV"
fi
- name: Download dist artifact
if: matrix.task == 'test'
@@ -388,6 +566,7 @@ jobs:
if: github.event_name != 'pull_request' || matrix.task != 'compat-node22'
env:
TASK: ${{ matrix.task }}
NODE_OPTIONS: --max-old-space-size=6144
shell: bash
run: |
set -euo pipefail
@@ -459,6 +638,8 @@ jobs:
use-sticky-disk: "false"
- name: Check types and lint and oxfmt
env:
OPENCLAW_LOCAL_CHECK: "0"
run: pnpm check
- name: Strict TS build smoke
@@ -538,6 +719,11 @@ jobs:
continue-on-error: true
run: pnpm run lint:web-search-provider-boundaries
- name: Run web fetch provider boundary guard
id: web_fetch_provider_boundary
continue-on-error: true
run: pnpm run lint:web-fetch-provider-boundaries
- name: Run extension src boundary guard
id: extension_src_outside_plugin_sdk_boundary
continue-on-error: true
@@ -585,6 +771,7 @@ jobs:
NO_EXTENSION_TEST_CORE_IMPORTS_OUTCOME: ${{ steps.no_extension_test_core_imports.outcome }}
PLUGIN_SDK_SUBPATHS_EXPORTED_OUTCOME: ${{ steps.plugin_sdk_subpaths_exported.outcome }}
WEB_SEARCH_PROVIDER_BOUNDARY_OUTCOME: ${{ steps.web_search_provider_boundary.outcome }}
WEB_FETCH_PROVIDER_BOUNDARY_OUTCOME: ${{ steps.web_fetch_provider_boundary.outcome }}
EXTENSION_SRC_OUTSIDE_PLUGIN_SDK_BOUNDARY_OUTCOME: ${{ steps.extension_src_outside_plugin_sdk_boundary.outcome }}
EXTENSION_PLUGIN_SDK_INTERNAL_BOUNDARY_OUTCOME: ${{ steps.extension_plugin_sdk_internal_boundary.outcome }}
EXTENSION_RELATIVE_OUTSIDE_PACKAGE_BOUNDARY_OUTCOME: ${{ steps.extension_relative_outside_package_boundary.outcome }}
@@ -604,6 +791,7 @@ jobs:
"lint:plugins:no-extension-test-core-imports|$NO_EXTENSION_TEST_CORE_IMPORTS_OUTCOME" \
"lint:plugins:plugin-sdk-subpaths-exported|$PLUGIN_SDK_SUBPATHS_EXPORTED_OUTCOME" \
"web-search-provider-boundary|$WEB_SEARCH_PROVIDER_BOUNDARY_OUTCOME" \
"web-fetch-provider-boundary|$WEB_FETCH_PROVIDER_BOUNDARY_OUTCOME" \
"extension-src-outside-plugin-sdk-boundary|$EXTENSION_SRC_OUTSIDE_PLUGIN_SDK_BOUNDARY_OUTCOME" \
"extension-plugin-sdk-internal-boundary|$EXTENSION_PLUGIN_SDK_INTERNAL_BOUNDARY_OUTCOME" \
"extension-relative-outside-package-boundary|$EXTENSION_RELATIVE_OUTSIDE_PACKAGE_BOUNDARY_OUTCOME" \
@@ -720,8 +908,7 @@ jobs:
env:
NODE_OPTIONS: --max-old-space-size=6144
# Keep total concurrency predictable on the 32 vCPU runner.
# Windows shard 2 has shown intermittent instability at 2 workers.
OPENCLAW_TEST_WORKERS: 1
OPENCLAW_VITEST_MAX_WORKERS: 1
defaults:
run:
shell: bash
@@ -794,15 +981,6 @@ jobs:
# caches can skip repeated rebuild/download work on later shards/runs.
pnpm install --frozen-lockfile --prefer-offline --ignore-scripts=false --config.engine-strict=false --config.enable-pre-post-scripts=true --config.side-effects-cache=true || pnpm install --frozen-lockfile --prefer-offline --ignore-scripts=false --config.engine-strict=false --config.enable-pre-post-scripts=true --config.side-effects-cache=true
- name: Configure test shard (Windows)
if: matrix.task == 'test'
env:
SHARD_COUNT: ${{ matrix.shard_count }}
SHARD_INDEX: ${{ matrix.shard_index }}
run: |
echo "OPENCLAW_TEST_SHARDS=$SHARD_COUNT" >> "$GITHUB_ENV"
echo "OPENCLAW_TEST_SHARD_INDEX=$SHARD_INDEX" >> "$GITHUB_ENV"
- name: Download dist artifact
if: matrix.task == 'test'
uses: actions/download-artifact@v8
@@ -866,17 +1044,10 @@ jobs:
name: canvas-a2ui-bundle
path: src/canvas-host/a2ui/
- name: Configure test shard (macOS)
env:
SHARD_COUNT: ${{ matrix.shard_count }}
SHARD_INDEX: ${{ matrix.shard_index }}
run: |
echo "OPENCLAW_TEST_SHARDS=$SHARD_COUNT" >> "$GITHUB_ENV"
echo "OPENCLAW_TEST_SHARD_INDEX=$SHARD_INDEX" >> "$GITHUB_ENV"
- name: TS tests (macOS)
env:
NODE_OPTIONS: --max-old-space-size=4096
OPENCLAW_VITEST_MAX_WORKERS: 2
TASK: ${{ matrix.task }}
shell: bash
run: |

View File

@@ -67,16 +67,18 @@ jobs:
id: manifest
env:
OPENCLAW_CI_DOCS_ONLY: ${{ steps.docs_scope.outputs.docs_only }}
OPENCLAW_CI_DOCS_CHANGED: "false"
OPENCLAW_CI_RUN_NODE: "false"
OPENCLAW_CI_RUN_MACOS: "false"
OPENCLAW_CI_RUN_ANDROID: "false"
OPENCLAW_CI_RUN_WINDOWS: "false"
OPENCLAW_CI_RUN_SKILLS_PYTHON: "false"
OPENCLAW_CI_HAS_CHANGED_EXTENSIONS: "false"
OPENCLAW_CI_CHANGED_EXTENSIONS_MATRIX: '{"include":[]}'
OPENCLAW_CI_RUN_CHANGED_SMOKE: ${{ steps.changed_scope.outputs.run_changed_smoke || 'false' }}
run: node scripts/ci-write-manifest-outputs.mjs --workflow install-smoke
run: |
docs_only="${OPENCLAW_CI_DOCS_ONLY:-false}"
run_changed_smoke="${OPENCLAW_CI_RUN_CHANGED_SMOKE:-false}"
run_install_smoke=false
if [ "$docs_only" != "true" ] && [ "$run_changed_smoke" = "true" ]; then
run_install_smoke=true
fi
{
echo "docs_only=$docs_only"
echo "run_install_smoke=$run_install_smoke"
} >> "$GITHUB_OUTPUT"
install-smoke:
needs: [preflight]

View File

@@ -82,11 +82,12 @@ jobs:
{
echo "## Public macOS validation only"
echo
echo "This workflow no longer builds, signs, notarizes, or uploads macOS assets."
echo "This workflow validates the public release handoff and still builds JS artifacts needed for release checks."
echo "It does not sign, notarize, or upload macOS assets."
echo
echo "Next step:"
echo "- Run \`openclaw/releases-private/.github/workflows/openclaw-macos-publish.yml\` with tag \`${RELEASE_TAG}\`."
echo "- Use \`preflight_only=true\` there for the full private mac preflight."
echo "- For the real publish path, the private run uploads the packaged \`.zip\`, \`.dmg\`, and \`.dSYM.zip\` files to the existing GitHub release in \`openclaw/openclaw\` automatically."
echo "- Run \`openclaw/releases-private/.github/workflows/openclaw-macos-validate.yml\` with tag \`${RELEASE_TAG}\` and wait for the private mac validation lane to pass."
echo "- Run \`openclaw/releases-private/.github/workflows/openclaw-macos-publish.yml\` with tag \`${RELEASE_TAG}\` and \`preflight_only=true\` for the full private mac preflight."
echo "- For the real publish path, run the same private mac publish workflow from \`main\` with the successful private preflight \`preflight_run_id\` so it promotes the prepared artifacts instead of rebuilding them."
echo "- For stable releases, also download \`macos-appcast-${RELEASE_TAG}\` from the successful private run and commit \`appcast.xml\` back to \`main\` in \`openclaw/openclaw\`."
} >> "$GITHUB_STEP_SUMMARY"

View File

@@ -12,9 +12,26 @@ on:
required: true
default: false
type: boolean
preflight_run_id:
description: Existing successful preflight workflow run id to promote without rebuilding
required: false
type: string
npm_dist_tag:
description: npm dist-tag to publish to for stable releases
required: true
default: beta
type: choice
options:
- beta
- latest
promote_beta_to_latest:
description: Skip publish and promote the stable version already on npm beta to latest
required: true
default: false
type: boolean
concurrency:
group: openclaw-npm-release-${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref }}
group: openclaw-npm-release-${{ github.event_name == 'workflow_dispatch' && format('{0}-{1}-{2}', inputs.tag, inputs.npm_dist_tag, inputs.promote_beta_to_latest) || github.ref }}
cancel-in-progress: false
env:
@@ -24,6 +41,7 @@ env:
jobs:
preflight_openclaw_npm:
if: ${{ inputs.preflight_only && !inputs.promote_beta_to_latest }}
runs-on: ubuntu-latest
permissions:
contents: read
@@ -31,12 +49,23 @@ jobs:
- name: Validate tag input format
env:
RELEASE_TAG: ${{ inputs.tag }}
RELEASE_NPM_DIST_TAG: ${{ inputs.npm_dist_tag }}
run: |
set -euo pipefail
if [[ ! "${RELEASE_TAG}" =~ ^v[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*((-beta\.[1-9][0-9]*)|(-[1-9][0-9]*))?$ ]]; then
echo "Invalid release tag format: ${RELEASE_TAG}"
exit 1
fi
if [[ "${RELEASE_TAG}" == *"-beta."* && "${RELEASE_NPM_DIST_TAG}" != "beta" ]]; then
echo "Beta prerelease tags must publish to npm dist-tag beta."
exit 1
fi
- name: Forbid preflight artifact promotion on validation-only runs
if: ${{ inputs.preflight_only && inputs.preflight_run_id != '' }}
run: |
echo "preflight_run_id is only valid for real publish runs."
exit 1
- name: Checkout
uses: actions/checkout@v6
@@ -71,6 +100,8 @@ jobs:
echo "Publishing openclaw@${PACKAGE_VERSION}"
- name: Check
env:
OPENCLAW_LOCAL_CHECK: "0"
run: pnpm check
- name: Build
@@ -80,9 +111,12 @@ jobs:
run: pnpm ui:build
- name: Validate release tag and package metadata
if: ${{ inputs.preflight_run_id == '' }}
env:
OPENCLAW_NPM_RELEASE_SKIP_PACK_CHECK: "1"
RELEASE_TAG: ${{ inputs.tag }}
RELEASE_MAIN_REF: origin/main
OPENCLAW_NPM_PUBLISH_TAG: ${{ inputs.npm_dist_tag }}
run: |
set -euo pipefail
RELEASE_SHA=$(git rev-parse HEAD)
@@ -95,8 +129,40 @@ jobs:
- name: Verify release contents
run: pnpm release:check
validate_publish_dispatch_ref:
if: ${{ !inputs.preflight_only }}
- name: Pack prepared npm tarball
id: packed_tarball
env:
OPENCLAW_PREPACK_PREPARED: "1"
RELEASE_TAG: ${{ inputs.tag }}
RELEASE_NPM_DIST_TAG: ${{ inputs.npm_dist_tag }}
run: |
set -euo pipefail
PACK_JSON="$(npm pack --json)"
echo "$PACK_JSON"
PACK_PATH="$(printf '%s\n' "$PACK_JSON" | node -e 'const chunks=[]; process.stdin.on("data", (chunk) => chunks.push(chunk)); process.stdin.on("end", () => { const parsed = JSON.parse(Buffer.concat(chunks).toString("utf8")); const first = Array.isArray(parsed) ? parsed[0] : null; if (!first || typeof first.filename !== "string" || !first.filename) { process.exit(1); } process.stdout.write(first.filename); });')"
if [[ -z "$PACK_PATH" || ! -f "$PACK_PATH" ]]; then
echo "npm pack did not produce a tarball file." >&2
exit 1
fi
RELEASE_SHA="$(git rev-parse HEAD)"
ARTIFACT_DIR="$RUNNER_TEMP/openclaw-npm-preflight"
rm -rf "$ARTIFACT_DIR"
mkdir -p "$ARTIFACT_DIR"
cp "$PACK_PATH" "$ARTIFACT_DIR/"
printf '%s\n' "$RELEASE_TAG" > "$ARTIFACT_DIR/release-tag.txt"
printf '%s\n' "$RELEASE_SHA" > "$ARTIFACT_DIR/release-sha.txt"
printf '%s\n' "$RELEASE_NPM_DIST_TAG" > "$ARTIFACT_DIR/release-npm-dist-tag.txt"
echo "dir=$ARTIFACT_DIR" >> "$GITHUB_OUTPUT"
- name: Upload prepared npm publish bundle
uses: actions/upload-artifact@v7
with:
name: openclaw-npm-preflight-${{ inputs.tag }}
path: ${{ steps.packed_tarball.outputs.dir }}
if-no-files-found: error
validate_publish_request:
if: ${{ !inputs.preflight_only && !inputs.promote_beta_to_latest }}
runs-on: ubuntu-latest
permissions:
contents: read
@@ -111,25 +177,41 @@ jobs:
exit 1
fi
- name: Require preflight artifact promotion on real publish
env:
PREFLIGHT_RUN_ID: ${{ inputs.preflight_run_id }}
run: |
set -euo pipefail
if [[ -z "${PREFLIGHT_RUN_ID}" ]]; then
echo "Real publish requires preflight_run_id from a successful npm preflight run." >&2
exit 1
fi
publish_openclaw_npm:
# npm trusted publishing + provenance requires a GitHub-hosted runner.
needs: [preflight_openclaw_npm, validate_publish_dispatch_ref]
if: ${{ !inputs.preflight_only }}
needs: [validate_publish_request]
if: ${{ !inputs.preflight_only && !inputs.promote_beta_to_latest }}
runs-on: ubuntu-latest
environment: npm-release
permissions:
actions: read
contents: read
id-token: write
steps:
- name: Validate tag input format
env:
RELEASE_TAG: ${{ inputs.tag }}
RELEASE_NPM_DIST_TAG: ${{ inputs.npm_dist_tag }}
run: |
set -euo pipefail
if [[ ! "${RELEASE_TAG}" =~ ^v[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*((-beta\.[1-9][0-9]*)|(-[1-9][0-9]*))?$ ]]; then
echo "Invalid release tag format: ${RELEASE_TAG}"
exit 1
fi
if [[ "${RELEASE_TAG}" == *"-beta."* && "${RELEASE_NPM_DIST_TAG}" != "beta" ]]; then
echo "Beta prerelease tags must publish to npm dist-tag beta."
exit 1
fi
- name: Checkout
uses: actions/checkout@v6
@@ -157,14 +239,28 @@ jobs:
echo "Publishing openclaw@${PACKAGE_VERSION}"
- name: Build
run: pnpm build
- name: Verify preflight run metadata
env:
GH_TOKEN: ${{ github.token }}
PREFLIGHT_RUN_ID: ${{ inputs.preflight_run_id }}
run: |
set -euo pipefail
RUN_JSON="$(gh run view "$PREFLIGHT_RUN_ID" --repo "$GITHUB_REPOSITORY" --json workflowName,headBranch,event,conclusion,url)"
printf '%s' "$RUN_JSON" | node -e 'const fs = require("node:fs"); const run = JSON.parse(fs.readFileSync(0, "utf8")); const checks = [["workflowName", "OpenClaw NPM Release"], ["headBranch", "main"], ["event", "workflow_dispatch"], ["conclusion", "success"]]; for (const [key, expected] of checks) { if (run[key] !== expected) { console.error(`Referenced npm preflight run ${process.env.PREFLIGHT_RUN_ID} must have ${key}=${expected}, got ${run[key] ?? "<missing>"}.`); process.exit(1); } } console.log(`Using npm preflight run ${process.env.PREFLIGHT_RUN_ID}: ${run.url}`);'
- name: Build Control UI
run: pnpm ui:build
- name: Download prepared npm tarball
uses: actions/download-artifact@v8
with:
name: openclaw-npm-preflight-${{ inputs.tag }}
path: preflight-tarball
repository: ${{ github.repository }}
run-id: ${{ inputs.preflight_run_id }}
github-token: ${{ github.token }}
- name: Validate release tag and package metadata
if: ${{ inputs.preflight_run_id == '' }}
env:
OPENCLAW_NPM_RELEASE_SKIP_PACK_CHECK: "1"
RELEASE_TAG: ${{ inputs.tag }}
RELEASE_MAIN_REF: origin/main
run: |
@@ -176,5 +272,153 @@ jobs:
git fetch --no-tags origin +refs/heads/main:refs/remotes/origin/main
pnpm release:openclaw:npm:check
- name: Verify prepared tarball provenance
env:
RELEASE_TAG: ${{ inputs.tag }}
RELEASE_NPM_DIST_TAG: ${{ inputs.npm_dist_tag }}
run: |
set -euo pipefail
EXPECTED_RELEASE_SHA="$(git rev-parse HEAD)"
TAG_FILE="preflight-tarball/release-tag.txt"
SHA_FILE="preflight-tarball/release-sha.txt"
NPM_DIST_TAG_FILE="preflight-tarball/release-npm-dist-tag.txt"
if [[ ! -f "$TAG_FILE" || ! -f "$SHA_FILE" || ! -f "$NPM_DIST_TAG_FILE" ]]; then
echo "Prepared preflight metadata is missing." >&2
ls -la preflight-tarball >&2 || true
exit 1
fi
ARTIFACT_RELEASE_TAG="$(tr -d '\r\n' < "$TAG_FILE")"
ARTIFACT_RELEASE_SHA="$(tr -d '\r\n' < "$SHA_FILE")"
ARTIFACT_RELEASE_NPM_DIST_TAG="$(tr -d '\r\n' < "$NPM_DIST_TAG_FILE")"
if [[ "$ARTIFACT_RELEASE_TAG" != "$RELEASE_TAG" ]]; then
echo "Prepared preflight tag mismatch: expected $RELEASE_TAG, got $ARTIFACT_RELEASE_TAG" >&2
exit 1
fi
if [[ "$ARTIFACT_RELEASE_SHA" != "$EXPECTED_RELEASE_SHA" ]]; then
echo "Prepared preflight SHA mismatch: expected $EXPECTED_RELEASE_SHA, got $ARTIFACT_RELEASE_SHA" >&2
exit 1
fi
if [[ "$ARTIFACT_RELEASE_NPM_DIST_TAG" != "$RELEASE_NPM_DIST_TAG" ]]; then
echo "Prepared preflight npm dist-tag mismatch: expected $RELEASE_NPM_DIST_TAG, got $ARTIFACT_RELEASE_NPM_DIST_TAG" >&2
exit 1
fi
- name: Resolve publish tarball
id: publish_tarball
run: |
set -euo pipefail
TARBALL_PATH="$(find preflight-tarball -type f -name '*.tgz' -print | sort | tail -n 1)"
if [[ -z "$TARBALL_PATH" ]]; then
echo "Prepared preflight tarball not found." >&2
ls -la preflight-tarball >&2 || true
exit 1
fi
echo "path=$TARBALL_PATH" >> "$GITHUB_OUTPUT"
- name: Publish
run: bash scripts/openclaw-npm-publish.sh --publish
env:
OPENCLAW_PREPACK_PREPARED: "1"
OPENCLAW_NPM_PUBLISH_TAG: ${{ inputs.npm_dist_tag }}
run: |
set -euo pipefail
publish_target="${{ steps.publish_tarball.outputs.path }}"
if [[ -n "${publish_target}" ]]; then
publish_target="./${publish_target}"
fi
bash scripts/openclaw-npm-publish.sh --publish "${publish_target}"
promote_beta_to_latest:
if: ${{ inputs.promote_beta_to_latest }}
runs-on: ubuntu-latest
environment: npm-release
permissions:
contents: read
steps:
- name: Require main workflow ref for promotion
env:
WORKFLOW_REF: ${{ github.ref }}
run: |
set -euo pipefail
if [[ "${WORKFLOW_REF}" != "refs/heads/main" ]]; then
echo "Promotion runs must be dispatched from main."
exit 1
fi
- name: Validate promotion inputs
env:
PREFLIGHT_ONLY: ${{ inputs.preflight_only }}
PREFLIGHT_RUN_ID: ${{ inputs.preflight_run_id }}
RELEASE_NPM_DIST_TAG: ${{ inputs.npm_dist_tag }}
run: |
set -euo pipefail
if [[ "${PREFLIGHT_ONLY}" == "true" ]]; then
echo "Promotion mode cannot run with preflight_only=true."
exit 1
fi
if [[ -n "${PREFLIGHT_RUN_ID}" ]]; then
echo "Promotion mode does not use preflight_run_id."
exit 1
fi
if [[ "${RELEASE_NPM_DIST_TAG}" != "beta" ]]; then
echo "Promotion mode expects npm_dist_tag=beta because it moves beta to latest without publishing."
exit 1
fi
- name: Validate stable tag input format
env:
RELEASE_TAG: ${{ inputs.tag }}
run: |
set -euo pipefail
if [[ ! "${RELEASE_TAG}" =~ ^v[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*(-[1-9][0-9]*)?$ ]]; then
echo "Invalid stable release tag format: ${RELEASE_TAG}" >&2
exit 1
fi
echo "RELEASE_VERSION=${RELEASE_TAG#v}" >> "$GITHUB_ENV"
- name: Checkout
uses: actions/checkout@v6
- name: Setup Node environment
uses: ./.github/actions/setup-node-env
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
install-bun: "false"
use-sticky-disk: "false"
install-deps: "false"
- name: Validate npm dist-tags
env:
RELEASE_VERSION: ${{ env.RELEASE_VERSION }}
run: |
set -euo pipefail
beta_version="$(npm view openclaw dist-tags.beta)"
latest_version="$(npm view openclaw dist-tags.latest)"
echo "Current beta dist-tag: ${beta_version}"
echo "Current latest dist-tag: ${latest_version}"
if [[ "${beta_version}" != "${RELEASE_VERSION}" ]]; then
echo "npm beta points at ${beta_version}, expected ${RELEASE_VERSION}." >&2
exit 1
fi
if ! npm view "openclaw@${RELEASE_VERSION}" version >/dev/null 2>&1; then
echo "openclaw@${RELEASE_VERSION} is not published on npm." >&2
exit 1
fi
- name: Promote beta to latest
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
RELEASE_VERSION: ${{ env.RELEASE_VERSION }}
run: |
set -euo pipefail
npm whoami >/dev/null
npm dist-tag add "openclaw@${RELEASE_VERSION}" latest
promoted_latest="$(npm view openclaw dist-tags.latest)"
if [[ "${promoted_latest}" != "${RELEASE_VERSION}" ]]; then
echo "npm latest points at ${promoted_latest}, expected ${RELEASE_VERSION} after promotion." >&2
exit 1
fi
echo "Promoted openclaw@${RELEASE_VERSION} from beta to latest."

View File

@@ -0,0 +1,276 @@
name: Plugin ClawHub Release
on:
workflow_dispatch:
inputs:
publish_scope:
description: Publish the selected plugins or all ClawHub-publishable plugins from the workflow ref
required: true
default: selected
type: choice
options:
- selected
- all-publishable
plugins:
description: Comma-separated plugin package names to publish when publish_scope=selected
required: false
type: string
concurrency:
group: plugin-clawhub-release-${{ github.sha }}
cancel-in-progress: false
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
NODE_VERSION: "24.x"
PNPM_VERSION: "10.23.0"
CLAWHUB_REGISTRY: "https://clawhub.ai"
CLAWHUB_REPOSITORY: "openclaw/clawhub"
# Pinned to a reviewed ClawHub commit so release behavior stays reproducible.
CLAWHUB_REF: "4af2bd50a71465683dbf8aa269af764b9d39bdf5"
jobs:
preview_plugins_clawhub:
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
ref_sha: ${{ steps.ref.outputs.sha }}
has_candidates: ${{ steps.plan.outputs.has_candidates }}
candidate_count: ${{ steps.plan.outputs.candidate_count }}
skipped_published_count: ${{ steps.plan.outputs.skipped_published_count }}
matrix: ${{ steps.plan.outputs.matrix }}
steps:
- name: Checkout
uses: actions/checkout@v6
with:
ref: ${{ github.sha }}
fetch-depth: 0
- name: Setup Node environment
uses: ./.github/actions/setup-node-env
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
install-bun: "false"
use-sticky-disk: "false"
- name: Resolve checked-out ref
id: ref
run: echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
- name: Validate ref is on main
run: |
set -euo pipefail
git fetch --no-tags origin +refs/heads/main:refs/remotes/origin/main
git merge-base --is-ancestor HEAD origin/main
- name: Validate publishable plugin metadata
env:
PUBLISH_SCOPE: ${{ github.event_name == 'workflow_dispatch' && inputs.publish_scope || '' }}
RELEASE_PLUGINS: ${{ github.event_name == 'workflow_dispatch' && inputs.plugins || '' }}
BASE_REF: ${{ github.event_name != 'workflow_dispatch' && github.event.before || '' }}
HEAD_REF: ${{ steps.ref.outputs.sha }}
run: |
set -euo pipefail
if [[ -n "${PUBLISH_SCOPE}" ]]; then
release_args=(--selection-mode "${PUBLISH_SCOPE}")
if [[ -n "${RELEASE_PLUGINS}" ]]; then
release_args+=(--plugins "${RELEASE_PLUGINS}")
fi
pnpm release:plugins:clawhub:check -- "${release_args[@]}"
elif [[ -n "${BASE_REF}" ]]; then
pnpm release:plugins:clawhub:check -- --base-ref "${BASE_REF}" --head-ref "${HEAD_REF}"
else
pnpm release:plugins:clawhub:check
fi
- name: Resolve plugin release plan
id: plan
env:
PUBLISH_SCOPE: ${{ github.event_name == 'workflow_dispatch' && inputs.publish_scope || '' }}
RELEASE_PLUGINS: ${{ github.event_name == 'workflow_dispatch' && inputs.plugins || '' }}
BASE_REF: ${{ github.event_name != 'workflow_dispatch' && github.event.before || '' }}
HEAD_REF: ${{ steps.ref.outputs.sha }}
CLAWHUB_REGISTRY: ${{ env.CLAWHUB_REGISTRY }}
run: |
set -euo pipefail
mkdir -p .local
if [[ -n "${PUBLISH_SCOPE}" ]]; then
plan_args=(--selection-mode "${PUBLISH_SCOPE}")
if [[ -n "${RELEASE_PLUGINS}" ]]; then
plan_args+=(--plugins "${RELEASE_PLUGINS}")
fi
node --import tsx scripts/plugin-clawhub-release-plan.ts "${plan_args[@]}" > .local/plugin-clawhub-release-plan.json
elif [[ -n "${BASE_REF}" ]]; then
node --import tsx scripts/plugin-clawhub-release-plan.ts --base-ref "${BASE_REF}" --head-ref "${HEAD_REF}" > .local/plugin-clawhub-release-plan.json
else
node --import tsx scripts/plugin-clawhub-release-plan.ts > .local/plugin-clawhub-release-plan.json
fi
cat .local/plugin-clawhub-release-plan.json
candidate_count="$(jq -r '.candidates | length' .local/plugin-clawhub-release-plan.json)"
skipped_published_count="$(jq -r '.skippedPublished | length' .local/plugin-clawhub-release-plan.json)"
has_candidates="false"
if [[ "${candidate_count}" != "0" ]]; then
has_candidates="true"
fi
matrix_json="$(jq -c '.candidates' .local/plugin-clawhub-release-plan.json)"
{
echo "candidate_count=${candidate_count}"
echo "skipped_published_count=${skipped_published_count}"
echo "has_candidates=${has_candidates}"
echo "matrix=${matrix_json}"
} >> "$GITHUB_OUTPUT"
echo "Plugin release candidates:"
jq -r '.candidates[]? | "- \(.packageName)@\(.version) [\(.publishTag)] from \(.packageDir)"' .local/plugin-clawhub-release-plan.json
echo "Already published / skipped:"
jq -r '.skippedPublished[]? | "- \(.packageName)@\(.version)"' .local/plugin-clawhub-release-plan.json
- name: Fail manual publish when target versions already exist
if: github.event_name == 'workflow_dispatch' && inputs.publish_scope == 'selected' && steps.plan.outputs.skipped_published_count != '0'
run: |
echo "::error::One or more selected plugin versions already exist on ClawHub. Bump the version before running a real publish."
exit 1
preview_plugin_pack:
needs: preview_plugins_clawhub
if: needs.preview_plugins_clawhub.outputs.has_candidates == 'true'
runs-on: ubuntu-latest
permissions:
contents: read
strategy:
fail-fast: false
matrix:
plugin: ${{ fromJson(needs.preview_plugins_clawhub.outputs.matrix) }}
steps:
- name: Checkout
uses: actions/checkout@v6
with:
ref: ${{ needs.preview_plugins_clawhub.outputs.ref_sha }}
fetch-depth: 1
- name: Setup Node environment
uses: ./.github/actions/setup-node-env
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
install-bun: "true"
use-sticky-disk: "false"
install-deps: "false"
- name: Checkout ClawHub CLI source
uses: actions/checkout@v6
with:
repository: ${{ env.CLAWHUB_REPOSITORY }}
ref: ${{ env.CLAWHUB_REF }}
path: clawhub-source
fetch-depth: 1
- name: Install ClawHub CLI dependencies
working-directory: clawhub-source
run: bun install --frozen-lockfile
- name: Bootstrap ClawHub CLI
run: |
cat > "$RUNNER_TEMP/clawhub" <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
exec bun "$GITHUB_WORKSPACE/clawhub-source/packages/clawhub/src/cli.ts" "$@"
EOF
chmod +x "$RUNNER_TEMP/clawhub"
echo "$RUNNER_TEMP" >> "$GITHUB_PATH"
- name: Preview publish command
env:
CLAWHUB_REGISTRY: ${{ env.CLAWHUB_REGISTRY }}
SOURCE_REPO: ${{ github.repository }}
SOURCE_COMMIT: ${{ needs.preview_plugins_clawhub.outputs.ref_sha }}
SOURCE_REF: ${{ github.ref }}
PACKAGE_TAG: ${{ matrix.plugin.publishTag }}
PACKAGE_DIR: ${{ matrix.plugin.packageDir }}
run: bash scripts/plugin-clawhub-publish.sh --dry-run "${PACKAGE_DIR}"
publish_plugins_clawhub:
needs: [preview_plugins_clawhub, preview_plugin_pack]
if: github.event_name == 'workflow_dispatch' && needs.preview_plugins_clawhub.outputs.has_candidates == 'true'
runs-on: ubuntu-latest
environment: clawhub-plugin-release
permissions:
contents: read
id-token: write
strategy:
fail-fast: false
matrix:
plugin: ${{ fromJson(needs.preview_plugins_clawhub.outputs.matrix) }}
steps:
- name: Checkout
uses: actions/checkout@v6
with:
ref: ${{ needs.preview_plugins_clawhub.outputs.ref_sha }}
fetch-depth: 1
- name: Setup Node environment
uses: ./.github/actions/setup-node-env
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
install-bun: "true"
use-sticky-disk: "false"
install-deps: "false"
- name: Checkout ClawHub CLI source
uses: actions/checkout@v6
with:
repository: ${{ env.CLAWHUB_REPOSITORY }}
ref: ${{ env.CLAWHUB_REF }}
path: clawhub-source
fetch-depth: 1
- name: Install ClawHub CLI dependencies
working-directory: clawhub-source
run: bun install --frozen-lockfile
- name: Bootstrap ClawHub CLI
run: |
cat > "$RUNNER_TEMP/clawhub" <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
exec bun "$GITHUB_WORKSPACE/clawhub-source/packages/clawhub/src/cli.ts" "$@"
EOF
chmod +x "$RUNNER_TEMP/clawhub"
echo "$RUNNER_TEMP" >> "$GITHUB_PATH"
- name: Ensure version is not already published
env:
PACKAGE_NAME: ${{ matrix.plugin.packageName }}
PACKAGE_VERSION: ${{ matrix.plugin.version }}
CLAWHUB_REGISTRY: ${{ env.CLAWHUB_REGISTRY }}
run: |
set -euo pipefail
encoded_name="$(node -e 'console.log(encodeURIComponent(process.env.PACKAGE_NAME ?? ""))')"
encoded_version="$(node -e 'console.log(encodeURIComponent(process.env.PACKAGE_VERSION ?? ""))')"
url="${CLAWHUB_REGISTRY%/}/api/v1/packages/${encoded_name}/versions/${encoded_version}"
status="$(curl --silent --show-error --output /dev/null --write-out '%{http_code}' "${url}")"
if [[ "${status}" =~ ^2 ]]; then
echo "${PACKAGE_NAME}@${PACKAGE_VERSION} is already published on ClawHub."
exit 1
fi
if [[ "${status}" != "404" ]]; then
echo "Unexpected ClawHub response (${status}) for ${PACKAGE_NAME}@${PACKAGE_VERSION}."
exit 1
fi
- name: Publish
env:
CLAWHUB_REGISTRY: ${{ env.CLAWHUB_REGISTRY }}
SOURCE_REPO: ${{ github.repository }}
SOURCE_COMMIT: ${{ needs.preview_plugins_clawhub.outputs.ref_sha }}
SOURCE_REF: ${{ github.ref }}
PACKAGE_TAG: ${{ matrix.plugin.publishTag }}
PACKAGE_DIR: ${{ matrix.plugin.packageDir }}
run: bash scripts/plugin-clawhub-publish.sh --publish "${PACKAGE_DIR}"

View File

@@ -211,4 +211,7 @@ jobs:
fi
- name: Publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
run: bash scripts/plugin-npm-publish.sh --publish "${{ matrix.plugin.packageDir }}"

2
.gitignore vendored
View File

@@ -141,3 +141,5 @@ changelog/fragments/
# Local scratch workspace
.tmp/
test/fixtures/openclaw-vitest-unit-report.json
analysis/

View File

@@ -1,11 +1,6 @@
{
"globs": ["docs/**/*.md", "docs/**/*.mdx", "README.md"],
"ignores": [
"docs/zh-CN/**",
"docs/.i18n/**",
"docs/reference/templates/**",
"**/.local/**"
],
"ignores": ["docs/zh-CN/**", "docs/.i18n/**", "docs/reference/templates/**", "**/.local/**"],
"config": {
"default": true,
@@ -38,6 +33,9 @@
"img",
"a",
"br",
"table",
"tr",
"td",
"details",
"summary",
"p",

View File

@@ -112,6 +112,7 @@
- Type-check/build: `pnpm build`
- TypeScript checks: `pnpm tsgo`
- Lint/format: `pnpm check`
- Local agent/dev shells default to lower-memory `OPENCLAW_LOCAL_CHECK=1` behavior for `pnpm tsgo` and `pnpm lint`; set `OPENCLAW_LOCAL_CHECK=0` in CI/shared runs.
- Format check: `pnpm format` (oxfmt --check)
- Format fix: `pnpm format:fix` (oxfmt --write)
- Terminology:
@@ -179,6 +180,16 @@
- When tests need example Anthropic/OpenAI model constants, prefer `sonnet-4.6` and `gpt-5.4`; update older Anthropic/GPT examples when you touch those tests.
- Run `pnpm test` (or `pnpm test:coverage`) before pushing when you touch logic.
- Write tests to clean up timers, env, globals, mocks, sockets, temp dirs, and module state so `--isolate=false` stays green.
- Test performance guardrail: do not put `vi.resetModules()` plus `await import(...)` in `beforeEach`/per-test loops for heavy modules unless module state truly requires it. Prefer static imports or one-time `beforeAll` imports, then reset mocks/runtime state directly.
- Test performance guardrail: if a test file uses stable `vi.mock(...)` hoists or other static module mocks, do not pair them with `vi.resetModules()` and a fresh `await import(...)` in every `beforeEach`. Import the heavy module once in `beforeAll`, then reset/prime mocks in `beforeEach` so Browser/Matrix-style hotspot tests do not pay the module graph cost per case.
- Test performance guardrail: inside an extension package, prefer a thin local seam (`./api.ts`, `./runtime-api.ts`, or a narrower local `*.runtime-api.ts`) over direct `openclaw/plugin-sdk/*` imports for internal production code. Keep local seams curated and lightweight; only reach for direct `plugin-sdk/*` imports when you are crossing a real package boundary or when no suitable local seam exists yet.
- Test performance guardrail: keep expensive runtime fallback work such as snapshotting, migration, installs, or bootstrap behind dedicated `*.runtime.ts` boundaries so tests can mock the seam instead of accidentally invoking real work.
- Test performance guardrail: for import-only/runtime-wrapper tests, keep the wrapper lazy. Do not eagerly load heavy verification/bootstrap/runtime modules at module top level if the exported function can import them on demand.
- Test performance guardrail: prefer explicit mock factories over `importOriginal()` for broad modules. Reserve `importOriginal()` for narrow modules where partial-real behavior is genuinely needed.
- Test performance guardrail: do not partial-mock broad `openclaw/plugin-sdk/*` barrels in hot tests. Add a plugin-local `*.runtime.ts` seam and mock that seam instead.
- Test performance guardrail: when production code already accepts `deps`, callbacks, or runtime injection, use that seam in tests before adding module-level mocks.
- 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.
- Do not set test workers above 16; tried already.

View File

@@ -4,110 +4,443 @@ Docs: https://docs.openclaw.ai
## Unreleased
### Breaking
- Nodes/exec: remove the duplicated `nodes.run` shell wrapper from the CLI and agent `nodes` tool so node shell execution always goes through `exec host=node`, keeping node-specific capabilities on `nodes invoke` and the dedicated media/location/notify actions.
- Background tasks: replace the old JSON task ledger with the SQLite-backed task store, so undocumented tooling or scripts that read `tasks/runs.json` directly must switch to the supported `openclaw tasks` surfaces instead of depending on state-dir internals. Thanks @vincentkoc and @mbelinky.
### Changes
- LINE/outbound media: add LINE image, video, and audio outbound sends on the LINE-specific delivery path, including explicit preview/tracking handling for videos while keeping generic media sends on the existing image-only route. (#45826) Thanks @masatohoshino.
- WhatsApp/reactions: agents can now react with emoji on incoming WhatsApp messages, enabling more natural conversational interactions like acknowledging a photo with ❤️ instead of typing a reply. Thanks @mcaxtr.
- MCP: add remote HTTP/SSE server support for `mcp.servers` URL configs, including auth headers and safer config redaction for MCP credentials. (#50396) Thanks @dhananjai1729.
- Agents/MCP: materialize bundle MCP tools with provider-safe names (`serverName__toolName`), support optional `streamable-http` transport selection plus per-server connection timeouts, and preserve real tool results from aborted/error turns unless truncation explicitly drops them. (#49505) Thanks @ziomancer.
- Plugins/hooks: add a `before_install` hook with structured request provenance, built-in scan status, and install-target metadata so external security scanners and policy engines can review and block skill, plugin package, plugin bundle, and single-file plugin installs. (#56050) thanks @odysseus0.
- ACP/plugins: add an explicit default-off ACPX plugin-tools MCP bridge config, document the trust boundary, and harden the built-in bridge packaging/logging path so global installs and stdio MCP sessions work reliably. (#56867) Thanks @joe2643.
- Agents/LLM: add a configurable idle-stream timeout for embedded runner requests so stalled model streams abort cleanly instead of hanging until the broader run timeout fires. (#55072) Thanks @liuy.
- OpenAI/Responses: forward configured `text.verbosity` across Responses HTTP and WebSocket transports, surface it in `/status`, and keep per-agent verbosity precedence aligned with runtime behavior. (#47106) Thanks @merc1305 and @vincentkoc.
- Android/notifications: add notification-forwarding controls with package filtering, quiet hours, rate limiting, and safer picker behavior for forwarded notification events. (#40175) Thanks @nimbleenigma.
- Matrix/network: add explicit `channels.matrix.proxy` config for routing Matrix traffic through an HTTP(S) proxy, including account-level overrides and matching probe/runtime behavior. (#56931) thanks @patrick-yingxi-pan.
- Background tasks: turn tasks into a real shared background-run control plane instead of ACP-only bookkeeping by unifying ACP, subagent, cron, and background CLI execution under one SQLite-backed ledger, routing detached lifecycle updates through the executor seam, adding audit/maintenance/status visibility, tightening auto-cleanup and lost-run recovery, improving task awareness in internal status/tool surfaces, and clarifying the split between heartbeat/main-session automation and detached scheduled runs. Thanks @vincentkoc and @mbelinky.
- 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.
- 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.
### Fixes
- Image generation/build: write stable runtime alias files into `dist/` and route provider-auth runtime lookups through those aliases so image-generation providers keep resolving auth/runtime modules after rebuilds instead of crashing on missing hashed chunk files.
- Config/runtime: pin the first successful config load in memory for the running process and refresh that snapshot on successful writes/reloads, so hot paths stop reparsing `openclaw.json` between watcher-driven swaps.
- Config/legacy cleanup: stop probing obsolete alternate legacy config names and service labels during local config/service detection, while keeping the active `~/.openclaw/openclaw.json` path canonical.
- ACP/sessions_spawn: register ACP child runs for completion tracking and lifecycle cleanup, and make registration-failure cleanup explicitly best-effort so callers do not assume an already-started ACP turn was fully aborted. (#40885) Thanks @xaeon2026 and @vincentkoc.
- ACP/tasks: mark cleanly exited ACP runs as blocked when they end on deterministic write or authorization blockers, and wake the parent session with a follow-up instead of falsely reporting success.
- ACPX/runtime: derive the bundled ACPX expected version from the extension package metadata instead of hardcoding a separate literal, so plugin-local ACPX installs stop drifting out of health-check parity after version bumps. (#49089) Thanks @jiejiesks and @vincentkoc.
- Gateway/auth: make local-direct `trusted-proxy` fallback require the configured shared token instead of silently authenticating same-host callers, while keeping same-host reverse proxy identity-header flows on the normal trusted-proxy path. Thanks @zhangning-agent and @vincentkoc.
- Memory/QMD: send MCP `query` collection filters as the upstream `collections` array instead of the legacy singular `collection` field, so mcporter-backed QMD 1.1+ searches still scope correctly after the unified `query` tool migration. (#54728) Thanks @armanddp and @vincentkoc.
- Memory/QMD: keep `qmd embed` active in `search` mode too, so BM25-first setups still build a complete index for later vector and hybrid retrieval. (#54509) Thanks @hnshah and @vincentkoc.
- Memory/QMD: point `QMD_CONFIG_DIR` at the nested `xdg-config/qmd` directory so per-agent collection config resolves correctly. (#39078) Thanks @smart-tinker and @vincentkoc.
- Memory/QMD: include deduplicated default plus per-agent `memorySearch.extraPaths` when building QMD custom collections, so shared and agent-specific extra roots both get indexed consistently. (#57315) Thanks @Vitalcheffe and @vincentkoc.
- Memory/session indexer: include `.jsonl.reset.*` and `.jsonl.deleted.*` transcripts in the memory host session scan while still excluding `.jsonl.bak.*` compaction backups and lock files, so memory search sees archived session history without duplicating stale snapshots. Thanks @hclsys and @vincentkoc.
- Agents/sandbox: honor `tools.sandbox.tools.alsoAllow`, let explicit sandbox re-allows remove matching built-in default-deny tools, and keep sandbox explain/error guidance aligned with the effective sandbox tool policy. (#54492) Thanks @ngutman.
- LINE/ACP: add current-conversation binding and inbound binding-routing parity so `/acp spawn ... --thread here`, configured ACP bindings, and active conversation-bound ACP sessions work on LINE like the other conversation channels.
- LINE/markdown: preserve underscores inside Latin, Cyrillic, and CJK words when stripping markdown, while still removing standalone `_italic_` markers on the shared text-runtime path used by LINE and TTS. (#47465) Thanks @jackjin1997.
- TTS/Microsoft: auto-switch the default Edge voice to Chinese for CJK-dominant text without overriding explicitly selected Microsoft voices. (#52355) Thanks @extrasmall0.
- Agents/context pruning: count supplementary-plane CJK characters with the shared code-point-aware estimator so context pruning stops underestimating Japanese and Chinese text that uses Extension B ideographs. (#39985) Thanks @Edward-Qiang-2024.
- Slack/status reactions: add a reaction lifecycle for queued, thinking, tool, done, and error phases in Slack monitors, with safer cleanup so queued ack reactions stay correct across silent runs, pre-reply failures, and delayed transitions. (#56430) Thanks @hsiaoa.
- macOS/local gateway: stop OpenClaw.app from killing healthy local gateway listeners after startup by recognizing the current `openclaw-gateway` process title and using the current `openclaw gateway` launch shape.
- Gateway/OpenAI compatibility: accept flat Responses API function tool definitions on `/v1/responses` and preserve `strict` when normalizing hosted tools into the embedded runner, so spec-compliant clients like Codex no longer fail validation or silently lose strict tool enforcement. Thanks @malaiwah and @vincentkoc.
- Memory/QMD: resolve slugified `memory_search` file hints back to the indexed filesystem path before returning search hits, so `memory_get` works again for mixed-case and spaced paths. (#50313) Thanks @erra9x.
- OpenAI/Codex fast mode: map `/fast` to priority processing on native OpenAI and Codex Responses endpoints instead of rewriting reasoning settings, and document the exact endpoint and override behavior.
- Memory/QMD: weight CJK-heavy text correctly when estimating chunk sizes, preserve surrogate-pair characters during fine splits, and keep long Latin lines on the old chunk boundaries so memory indexing produces better-sized chunks for CJK notes. (#40271) Thanks @AaronLuo00.
- Security/LINE: make webhook signature validation run the timing-safe compare even when the supplied signature length is wrong, closing a small timing side-channel. (#55663) Thanks @gavyngong.
- LINE/status: stop `openclaw status` from warning about missing credentials when sanitized LINE snapshots are already configured, while still surfacing whether the missing field is the token or secret. (#45701) Thanks @tamaosamu.
- Gateway/health: carry webhook-vs-polling account mode from channel descriptors into runtime snapshots so passive channels like LINE and BlueBubbles skip false stale-socket health failures. (#47488) Thanks @karesansui-u.
- Agents/MCP: reuse bundled MCP runtimes across turns in the same session, while recreating them when MCP config changes and disposing stale runtimes cleanly on session rollover. (#55090) Thanks @allan0509.
- Memory/QMD: honor `memory.qmd.update.embedInterval` even when regular QMD update cadence is disabled or slower by arming a dedicated embed-cadence maintenance timer, while avoiding redundant timers when regular updates are already frequent enough. (#37326) Thanks @barronlroth.
- Memory/QMD: add `memory.qmd.searchTool` as an exact mcporter tool override, so custom QMD MCP tools such as `hybrid_search` can be used without weakening the validated `searchMode` config surface. (#27801) Thanks @keramblock.
- Memory/QMD: keep reset and deleted session transcripts in QMD session export so daily session resets do not silently drop most historical recall from `memory_search`. (#30220) Thanks @pushkarsingh32.
- Memory/QMD: rebind collections when QMD reports a changed pattern but omits path metadata, so config pattern changes stop being silently ignored on restart. (#49897) Thanks @Madruru.
- Memory/QMD: warn explicitly when `memory.backend=qmd` is configured but the `qmd` binary is missing, so doctor and runtime fallback no longer fail as a silent builtin downgrade. (#50439) Thanks @Jimmy-xuzimo and @vincentkoc.
- Memory/QMD: pass a direct-session key on `openclaw memory search` so CLI QMD searches no longer get denied as `session=<none>` under direct-only scope defaults. (#43517) Thanks @waynecc-at and @vincentkoc.
- Memory/QMD: keep `memory_search` session-hit paths roundtrip-safe when exported session markdown lives under the workspace `qmd/` directory, so `memory_get` can read the exact returned path instead of failing on the generic `qmd/sessions/...` alias. (#43519) Thanks @holgergruenhagen and @vincentkoc.
- Agents/memory flush: keep daily memory flush files append-only during embedded attempts so compaction writes do not overwrite earlier notes. (#53725) Thanks @HPluseven.
- Web UI/markdown: stop bare auto-links from swallowing adjacent CJK text while preserving valid mixed-script path and query characters in rendered links. (#48410) Thanks @jnuyao.
- BlueBubbles/iMessage: coalesce URL-only inbound messages with their link-preview balloon again so sharing a bare link no longer drops the URL from agent context. Thanks @vincentkoc.
- Sandbox/browser: install `fonts-noto-cjk` in the sandbox browser image so screenshots render Chinese, Japanese, and Korean text correctly instead of tofu boxes. Fixes #35597. Thanks @carrotRakko and @vincentkoc.
- Memory/FTS: add configurable trigram tokenization plus short-CJK substring fallback so memory search can find Chinese, Japanese, and Korean text without breaking mixed long-and-short queries. Thanks @carrotRakko.
- Hooks/config: accept runtime channel plugin ids in `hooks.mappings[].channel` (for example `feishu`) instead of rejecting non-core channels during config validation. (#56226) Thanks @AiKrai001.
- TUI/chat: keep optimistic outbound user messages visible during active runs by deferring local-run binding until the first gateway chat event reveals the real run id, preventing premature history reloads from wiping pending local sends. (#54722) Thanks @seanturner001.
- TUI/model picker: keep searchable `/model` and `/models` input mode from hijacking `j`/`k` as navigation keys, and harden width bounds under `m`-filtered model lists so search no longer crashes on long rows. (#30156) Thanks @briannicholls.
- Agents/Kimi: preserve already-valid Anthropic-compatible tool call argument objects while still clearing cached repairs when later trailing junk exceeds the repair allowance. (#54491) Thanks @yuanaichi.
- Docker/setup: force BuildKit for local image builds (including sandbox image builds) so `./docker-setup.sh` no longer fails on `RUN --mount=...` when hosts default to Docker's legacy builder. (#56681) Thanks @zhanghui-china.
- Control UI/agents: auto-load agent workspace files on initial Files panel open, and populate overview model/workspace/fallbacks from effective runtime agent metadata so defaulted models no longer show as `Not set`. (#56637) Thanks @dxsx84.
- Control UI/slash commands: make `/steer` and `/redirect` work from the chat command palette with visible pending state for active-run `/steer`, correct redirected-run tracking, and a single canonical `/steer` entry in the command menu. (#54625) Thanks @fuller-stack-dev.
- Exec/runtime: default implicit exec to `host=auto`, resolve that target to sandbox only when a sandbox runtime exists, keep explicit `host=sandbox` fail-closed without sandbox, and show `/exec` effective host state in runtime status/docs.
- Exec: fail closed when the implicit sandbox host has no sandbox runtime, and stop denied async approval followups from reusing prior command output from the same session. (#56800) Thanks @scoootscooob.
- Exec/approvals: infer Discord and Telegram exec approvers from existing owner config when `execApprovals.approvers` is unset, extend the default approval window to 30 minutes, and clarify approval-unavailable guidance so approvals do not appear to silently disappear.
- Exec/node: stop gateway-side workdir fallback from rewriting explicit `host=node` cwd values to the gateway filesystem, so remote node exec approval and runs keep using the intended node-local directory. (#50961) Thanks @openperf.
- Plugins/ClawHub: sanitize temporary archive filenames for scoped package names and slash-containing skill slugs so `openclaw plugins install @scope/name` no longer fails with `ENOENT` during archive download. (#56452) Thanks @soimy.
- Telegram/polling: keep the watchdog from aborting long-running reply delivery by treating recent non-polling API activity as bounded liveness instead of a hard stall. (#56343) Thanks @openperf.
- Memory/FTS: keep provider-less keyword hits visible at the default memory-search threshold, so FTS-only recall works without requiring `--min-score 0`. (#56473) Thanks @opriz.
- Memory/LanceDB: resolve runtime dependency manifest lookup from the bundled `extensions/memory-lancedb` path (including flattened dist chunks) so startup no longer fails with a missing `@lancedb/lancedb` dependency error. (#56623) Thanks @LUKSOAgent.
- Tools/web_search: localize the shared search cache to module scope so same-process global symbol lookups can no longer inspect or mutate cached web-search responses. Thanks @vincentkoc.
- Agents/silent turns: fail closed on silent memory-flush runs so narrated `NO_REPLY` self-talk cannot stream or finalize into external replies even when block streaming is enabled. (#52593)
- Browser/plugins: auto-enable the bundled browser plugin when browser config or browser tool policy already references it, and show a clearer CLI error when `plugins.allow` excludes `browser`.
- Matrix/plugin loading: ship and source-load the crypto bootstrap runtime sidecar correctly so current `main` stops warning about failed Matrix bootstrap loads and `matrix/index` plugin-id mismatches on every invocation. (#53298) thanks @keithce.
- iOS/Live Activities: mark the `ActivityKit` import in `LiveActivityManager.swift` as `@preconcurrency` so Xcode 26.4 / Swift 6 builds stop failing on strict concurrency checks. (#57180) Thanks @ngutman.
- Plugins/Matrix: mirror the Matrix crypto WASM runtime dependency into the root packaged install and enforce root/plugin dependency parity so bundled Matrix E2EE crypto resolves correctly in shipped builds. (#57163) Thanks @gumadeiras.
- Plugins/CLI: add descriptor-backed lazy plugin CLI registration so Matrix can keep its CLI module lazy-loaded without dropping `openclaw matrix ...` from parse-time command registration. (#57165) Thanks @gumadeiras.
- Plugins/CLI: collect root-help plugin descriptors through a dedicated non-activating CLI metadata path so enabled plugins keep validated config semantics without triggering runtime-only plugin registration work, while preserving runtime CLI command registration for legacy channel plugins that still wire commands from full registration. (#57294) thanks @gumadeiras.
- Anthropic/OAuth: inject `/fast` `service_tier` hints for direct `sk-ant-oat-*` requests so OAuth-authenticated Anthropic runs stop missing the same overload-routing signal as API-key traffic. Fixes #55758. Thanks @Cypherm and @vincentkoc.
- Anthropic/service tiers: support explicit `serviceTier` model params for direct Anthropic requests and let them override `/fast` defaults when both are set. (#45453) Thanks @vincentkoc.
- Auto-reply/fast: accept `/fast status` on the directive-only path, align help/status text with the documented `status|on|off` syntax, and keep current-state replies consistent across command surfaces. Fixes #46095. Thanks @weissfl and @vincentkoc.
- Telegram/native commands: prefix native command menu callback payloads and preserve `CommandSource: "native"` when Telegram replays them through callback queries, so `/fast` and other native command menus keep working even when text-command routing is disabled. Thanks @vincentkoc.
- Docs/anchors: fix broken English docs links and make Mint anchor audits run against the English-source docs tree. (#57039) thanks @velvet-shark.
- Cron/announce: preserve all deliverable text payloads for announce mode instead of collapsing to the last chunk, so multi-line cron reports deliver in full to Telegram forum topics.
- Harden async approval followup delivery in webchat-only sessions (#57359) Thanks @joshavant.
- Status: fix cache hit rate exceeding 100% by deriving denominator from prompt-side token fields instead of potentially undersized totalTokens. Fixes #26643.
- Config/update: stop `openclaw doctor` write-backs from persisting plugin-injected channel defaults, so `openclaw update` no longer seeds config keys that later break service refresh validation. (#56834) Thanks @openperf.
- Agents/Anthropic failover: treat Anthropic `api_error` payloads with `An unexpected error occurred while processing the response` as transient so retry/fallback can engage instead of surfacing a terminal failure. (#57441) Thanks @zijiess and @vincentkoc.
- Agents/compaction: keep late compaction-retry rejections handled after the aggregate timeout path wins without swallowing real pre-timeout wait failures, so timed-out retries no longer surface an unhandled rejection on later unsubscribe. (#57451) Thanks @mpz4life and @vincentkoc.
- Matrix/delivery recovery: treat Synapse `User not in room` replay failures as permanent during startup recovery so poisoned queued messages move to `failed/` instead of crash-looping Matrix after restart. (#57426) thanks @dlardo.
- Plugins/facades: guard bundled plugin facade loads with a cache-first sentinel so circular re-entry stops crashing `xai`, `sglang`, and `vllm` during gateway plugin startup. (#57508) Thanks @openperf.
- Agents/MCP: dispose bundled MCP runtimes after one-shot `openclaw agent --local` runs finish, while preserving bundled MCP state across in-run retries so local JSON runs exit cleanly without restarting stateful MCP tools mid-run.
- 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.
- 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.
- 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.
- 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.
- 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.
## 2026.3.28
## 2026.4.2
### Breaking
- Providers/Qwen: remove the deprecated `qwen-portal-auth` OAuth integration for `portal.qwen.ai`; migrate to Model Studio with `openclaw onboard --auth-choice modelstudio-api-key`. (#52709) Thanks @pomelo-nwu.
- Config/Doctor: drop automatic config migrations older than two months; very old legacy keys now fail validation instead of being rewritten on load or by `openclaw doctor`.
- Plugins/xAI: move `x_search` settings from the legacy core `tools.web.x_search.*` path to the plugin-owned `plugins.entries.xai.config.xSearch.*` path, standardize `x_search` auth on `plugins.entries.xai.config.webSearch.apiKey` / `XAI_API_KEY`, and migrate legacy config with `openclaw doctor --fix`. (#59674) Thanks @vincentkoc.
- Plugins/web fetch: move Firecrawl `web_fetch` config from the legacy core `tools.web.fetch.firecrawl.*` path to the plugin-owned `plugins.entries.firecrawl.config.webFetch.*` path, route `web_fetch` fallback through the new fetch-provider boundary instead of a Firecrawl-only core branch, and migrate legacy config with `openclaw doctor --fix`. (#59465) Thanks @vincentkoc.
### Changes
- Tasks/Task Flow: restore the core Task Flow substrate with managed-vs-mirrored sync modes, durable flow state/revision tracking, and `openclaw tasks flow` inspection/recovery primitives so background orchestration can persist and be operated separately from plugin authoring layers. (#58930) Thanks @mbelinky.
- Tasks/Task Flow: add managed child task spawning plus sticky cancel intent, so external orchestrators can stop scheduling immediately and let parent Task Flows settle to `cancelled` once active child tasks finish. (#59610) Thanks @mbelinky.
- Plugins/Task Flow: add a bound `api.runtime.taskFlow` seam so plugins and trusted authoring layers can create and drive managed Task Flows from host-resolved OpenClaw context without passing owner identifiers on each call. (#59622) Thanks @mbelinky.
- Android/assistant: add assistant-role entrypoints plus Google Assistant App Actions metadata so Android can launch OpenClaw from the assistant trigger and hand prompts into the chat composer. (#59596) Thanks @obviyus.
- Exec defaults: make gateway/node host exec default to YOLO mode by requesting `security=full` with `ask=off`, and align host approval-file fallbacks plus docs/doctor reporting with that no-prompt default.
- Providers/runtime: add provider-owned replay hook surfaces for transcript policy, replay cleanup, and reasoning-mode dispatch. (#59143) Thanks @jalehman.
- Plugins/hooks: add `before_agent_reply` so plugins can short-circuit the LLM with synthetic replies after inline actions. (#20067) Thanks @JoshuaLelon.
- Channels/session routing: move provider-specific session conversation grammar into plugin-owned session-key surfaces, preserving Telegram topic routing and Feishu scoped inheritance across bootstrap, model override, restart, and tool-policy paths.
- Feishu/comments: add a dedicated Drive comment-event flow with comment-thread context resolution, in-thread replies, and `feishu_drive` comment actions for document collaboration workflows. (#58497) Thanks @wittam-01.
- Matrix/plugin: emit spec-compliant `m.mentions` metadata across text sends, media captions, edits, poll fallback text, and action-driven edits so Matrix mentions notify reliably in clients like Element. (#59323) Thanks @gumadeiras.
- Diffs: add plugin-owned `viewerBaseUrl` so viewer links can use a stable proxy/public origin without passing `baseUrl` on every tool call. (#59341) Related #59227. Thanks @gumadeiras.
- Agents/compaction: resolve `agents.defaults.compaction.model` consistently for manual `/compact` and other context-engine compaction paths, so engine-owned compaction uses the configured override model across runtime entrypoints. (#56710) Thanks @oliviareid-svg.
- Agents/compaction: add `agents.defaults.compaction.notifyUser` so the `🧹 Compacting context...` start notice is opt-in instead of always being shown. (#54251) Thanks @oguricap0327.
- WhatsApp/reactions: add `reactionLevel` guidance for agent reactions. Thanks @mcaxtr.
- Exec approvals/channels: auto-enable DM-first native chat approvals when supported channels can infer approvers from existing owner config, while keeping channel fanout explicit and clarifying forwarding versus native approval client config.
- Android/assistant: auto-send Google Assistant App Actions prompts once chat is healthy and idle, while keeping bare assistant launches as open-only. (#59721) Thanks @obviyus.
### Fixes
- Providers/transport policy: centralize request auth, proxy, TLS, and header shaping across shared HTTP, stream, and websocket paths, block insecure TLS/runtime transport overrides, and keep proxy-hop TLS separate from target mTLS settings. (#59682) Thanks @vincentkoc.
- Providers/OpenRouter: gate documented OpenRouter attribution to native OpenRouter endpoints or the default route so custom proxy base URLs do not inherit OpenRouter request headers.
- Providers/Copilot: classify native GitHub Copilot API hosts in the shared provider endpoint resolver and harden token-derived proxy endpoint parsing so Copilot base URL routing stays centralized and fails closed on malformed hints. (#59644) Thanks @vincentkoc.
- Providers/streaming headers: centralize default and attribution header merging across OpenAI websocket, embedded-runner, and proxy stream paths so provider-specific headers stay consistent and caller overrides only win where intended. (#59542) Thanks @vincentkoc.
- Providers/media HTTP: centralize base URL normalization, default auth/header injection, and explicit header override handling across shared OpenAI-compatible audio, Deepgram audio, Gemini media/image, and Moonshot video request paths. (#59469) Thanks @vincentkoc.
- Providers/OpenAI-compatible routing: centralize native-vs-proxy request policy so hidden attribution and related OpenAI-family defaults only apply on verified native endpoints across stream, websocket, and shared audio HTTP paths. (#59433) Thanks @vincentkoc.
- Providers/Anthropic routing: centralize native-vs-proxy endpoint classification for direct Anthropic `service_tier` handling so spoofed or proxied hosts do not inherit native Anthropic defaults. (#59608) Thanks @vincentkoc.
- Gateway/exec loopback: restore legacy-role fallback for empty paired-device token maps and allow silent local role upgrades so local exec and node clients stop failing with pairing-required errors after `2026.3.31`. (#59092) Thanks @openperf.
- Agents/subagents: pin admin-only subagent gateway calls to `operator.admin` while keeping `agent` at least privilege, so `sessions_spawn` no longer dies on loopback scope-upgrade pairing with `close(1008) "pairing required"`. (#59555) Thanks @openperf.
- Exec approvals/config: strip invalid `security`, `ask`, and `askFallback` values from `~/.openclaw/exec-approvals.json` during normalization so malformed policy enums fall back cleanly to the documented defaults instead of corrupting runtime policy resolution. (#59112) Thanks @openperf.
- Exec approvals/doctor: report host policy sources from the real approvals file path and ignore malformed host override values when attributing effective policy conflicts. (#59367) Thanks @gumadeiras.
- Exec/runtime: treat `tools.exec.host=auto` as routing-only, keep implicit no-config exec on sandbox when available or gateway otherwise, and reject per-call host overrides that would bypass the configured sandbox or host target. (#58897) Thanks @vincentkoc.
- Slack/mrkdwn formatting: add built-in Slack mrkdwn guidance in inbound context so Slack replies stop falling back to generic Markdown patterns that render poorly in Slack. (#59100) Thanks @jadewon.
- WhatsApp/presence: send `unavailable` presence on connect in self-chat mode so personal-phone users stop losing all push notifications while the gateway is running. (#59410) Thanks @mcaxtr.
- WhatsApp/media: add HTML, XML, and CSS to the MIME map and fall back gracefully for unknown media types instead of dropping the attachment. (#51562) Thanks @bobbyt74.
- Matrix/onboarding: restore guided setup in `openclaw channels add` and `openclaw configure --section channels`, while keeping custom plugin wizards on the shared `setupWizard` seam. (#59462) Thanks @gumadeiras.
- Matrix/streaming: keep live partial previews for the current assistant block while preserving completed block updates as separate messages when `channels.matrix.blockStreaming` is enabled. (#59384) Thanks @gumadeiras.
- Feishu/comment threads: harden document comment-thread delivery so whole-document comments fall back to `add_comment`, delayed reply lookups retry more reliably, and user-visible replies avoid reasoning/planning spillover. (#59129) Thanks @wittam-01.
- MS Teams/streaming: strip already-streamed text from fallback block delivery when replies exceed the 4000-character streaming limit so long responses stop duplicating content. (#59297) Thanks @bradgroux.
- Slack/thread context: filter thread starter and history by the effective conversation allowlist without dropping valid open-room, DM, or group DM context. (#58380) Thanks @jacobtomlinson.
- Mattermost/probes: route status probes through the SSRF guard and honor `allowPrivateNetwork` so connectivity checks stay safe for self-hosted Mattermost deployments. (#58529) Thanks @mappel-nv.
- Zalo/webhook replay: scope replay dedupe key by chat and sender so reused message IDs across different chats or senders no longer collide, and harden metadata reads for partially missing payloads. (#58444)
- QQBot/structured payloads: restrict local file paths to QQ Bot-owned media storage, block traversal outside that root, reduce path leakage in logs, and keep inline image data URLs working. (#58453) Thanks @jacobtomlinson.
- Image generation/providers: route OpenAI, MiniMax, and fal image requests through the shared provider HTTP transport path so custom base URLs, guarded private-network routing, and provider request defaults stay aligned with the rest of provider HTTP. Thanks @vincentkoc.
- Image generation/providers: stop inferring private-network access from configured OpenAI, MiniMax, and fal image base URLs, and cap shared HTTP error-body reads so hostile or misconfigured endpoints fail closed without relaxing SSRF policy or buffering unbounded error payloads. Thanks @vincentkoc.
- Browser/host inspection: keep static Chrome inspection helpers out of the activated browser runtime so `openclaw doctor browser` and related checks do not eagerly load the bundled browser plugin. (#59471) Thanks @vincentkoc.
- Browser/CDP: normalize trailing-dot localhost absolute-form hosts before loopback checks so remote CDP websocket URLs like `ws://localhost.:...` rewrite back to the configured remote host. (#59236) Thanks @mappel-nv.
- Agents/output sanitization: strip namespaced `antml:thinking` blocks from user-visible text so Anthropic-style internal monologue tags do not leak into replies. (#59550) Thanks @obviyus.
- Kimi Coding/tools: normalize Anthropic tool payloads into the OpenAI-compatible function shape Kimi Coding expects so tool calls stop losing required arguments. (#59440) Thanks @obviyus.
- Image tool/paths: resolve relative local media paths against the agent `workspaceDir` instead of `process.cwd()` so inputs like `inbox/receipt.png` pass the local-path allowlist reliably. (#57222) Thanks Priyansh Gupta.
- Podman/launch: remove noisy container output from `scripts/run-openclaw-podman.sh` and align the Podman install guidance with the quieter startup flow. (#59368) Thanks @sallyom.
- Plugins/runtime: keep LINE reply directives and browser-backed cleanup/reset flows working even when those plugins are disabled while tightening bundled plugin activation guards. (#59412) Thanks @vincentkoc.
- ACP/gateway reconnects: keep ACP prompts alive across transient websocket drops while still failing boundedly when reconnect recovery does not complete. (#59473) Thanks @obviyus.
- ACP/gateway reconnects: reject stale pre-ack ACP prompts after reconnect grace expiry so callers fail cleanly instead of hanging indefinitely when the gateway never confirms the run.
- Gateway/session kill: enforce HTTP operator scopes on session kill requests and gate authorization before session lookup so unauthenticated callers cannot probe session existence. (#59128) Thanks @jacobtomlinson.
- MS Teams/logging: format non-`Error` failures with the shared unknown-error helper so logs stop collapsing caught SDK or Axios objects into `[object Object]`. (#59321) Thanks @bradgroux.
- Channels/setup: ignore untrusted workspace channel plugins during setup resolution so a shadowing workspace plugin cannot override built-in channel setup/login flows unless explicitly trusted in config. (#59158) Thanks @mappel-nv.
- Exec/Windows: restore allowlist enforcement with quote-aware `argPattern` matching across gateway and node exec, and surface accurate dynamic pre-approved executable hints in the exec tool description. (#56285) Thanks @kpngr.
- Gateway: prune empty `node-pending-work` state entries after explicit acknowledgments and natural expiry so the per-node state map no longer grows indefinitely. (#58179) Thanks @gavyngong.
- Webhooks/secret comparison: replace ad-hoc timing-safe secret comparisons across BlueBubbles, Feishu, Mattermost, Telegram, Twilio, and Zalo webhook handlers with the shared `safeEqualSecret` helper and reject empty auth tokens in BlueBubbles. (#58432) Thanks @eleqtrizit.
- OpenShell/mirror: constrain `remoteWorkspaceDir` and `remoteAgentWorkspaceDir` to the managed `/sandbox` and `/agent` roots, and keep mirror sync from overwriting or removing user-added shell roots during config synchronization. (#58515) Thanks @eleqtrizit.
- Plugins/activation: preserve explicit, auto-enabled, and default activation provenance plus reason metadata across CLI, gateway bootstrap, and status surfaces so plugin enablement state stays accurate after auto-enable resolution. (#59641) Thanks @vincentkoc.
- Exec/env: block additional host environment override pivots for package roots, language runtimes, compiler include paths, and credential/config locations so request-scoped exec cannot redirect trusted toolchains or config lookups. (#59233) Thanks @drobison00.
- Dotenv/workspace overrides: block workspace `.env` files from overriding `OPENCLAW_PINNED_PYTHON` and `OPENCLAW_PINNED_WRITE_PYTHON` so trusted helper interpreters cannot be redirected by repo-local env injection. (#58473) Thanks @eleqtrizit.
- Plugins/install: accept JSON5 syntax in `openclaw.plugin.json` and bundle `plugin.json` manifests during install/validation, so third-party plugins with trailing commas, comments, or unquoted keys no longer fail to install. (#59084) Thanks @singleGanghood.
- Telegram/exec approvals: rewrite shared `/approve … allow-always` callback payloads to `/approve … always` before Telegram button rendering so plugin approval IDs still fit Telegram's `callback_data` limit and keep the Allow Always action visible. (#59217) Thanks @jameslcowan.
- Cron/exec timeouts: surface timed-out `exec` and `bash` failures in isolated cron runs even when `verbose: off`, including custom session-target cron jobs, so scheduled runs stop failing silently. (#58247) Thanks @skainguyen1412.
- Telegram/exec approvals: fall back to the origin session key for async approval followups and keep resume-failure status delivery sanitized so Telegram followups still land without leaking raw exec metadata. (#59351) Thanks @seonang.
- Node-host/exec approvals: bind `pnpm dlx` invocations through the approval planner's mutable-script path so the effective runtime command is resolved for approval instead of being left unbound. (#58374)
- 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.
## 2026.4.1
### Changes
- macOS/Voice Wake: add the Voice Wake option to trigger Talk Mode. (#58490) Thanks @SmoothExec.
- Tasks/chat: add `/tasks` as a chat-native background task board for the current session, with recent task details and agent-local fallback counts when no linked tasks are visible. Related #54226. Thanks @vincentkoc.
- Web search/SearXNG: add the bundled SearXNG provider plugin for `web_search` with configurable host support. (#57317) Thanks @cgdusek.
- Telegram/errors: add configurable `errorPolicy` and `errorCooldownMs` controls so Telegram can suppress repeated delivery errors per account, chat, and topic without muting distinct failures. (#51914) Thanks @chinar-amrutkar
- Gateway/webchat: make `chat.history` text truncation configurable with `gateway.webchat.chatHistoryMaxChars` and per-request `maxChars`, while preserving silent-reply filtering and existing default payload limits. (#58900)
- Amazon Bedrock/Guardrails: add Bedrock Guardrails support to the bundled provider. (#58588) Thanks @MikeORed.
- ZAI/models: add `glm-5.1` and `glm-5v-turbo` to the bundled Z.AI provider catalog. (#58793) Thanks @tomsun28
- Agents/default params: add `agents.defaults.params` for global default provider parameters. (#58548) Thanks @lpender.
- Agents/failover: cap prompt-side and assistant-side same-provider auth-profile retries for rate-limit failures before cross-provider model fallback, add the `auth.cooldowns.rateLimitedProfileRotations` knob, and document the new fallback behavior. (#58707) Thanks @Forgely3D
- Agents/compaction: resolve `agents.defaults.compaction.model` consistently for manual `/compact` and other context-engine compaction paths, so engine-owned compaction uses the configured override model across runtime entrypoints. (#56710) Thanks @oliviareid-svg
- Cron/tools allowlist: add `openclaw cron --tools` for per-job tool allowlists. (#58504) Thanks @andyk-ms.
### Fixes
- Chat/error replies: stop leaking raw provider/runtime failures into external chat channels, return a friendly retry message instead, and add a specific `/new` hint for Bedrock toolResult/toolUse session mismatches. (#58831) Thanks @ImLukeF.
- Sessions/model switching: keep `/model` changes queued behind busy runs instead of interrupting the active turn, and retarget queued followups so later work picks up the new model as soon as the current turn finishes.
- Web UI/OpenResponses: preserve rewritten stream snapshots in webchat and keep OpenResponses final streamed text aligned when models rewind earlier output. (#58641) Thanks @neeravmakwana
- Discord/inbound media: pass Discord attachment and sticker downloads through the shared idle-timeout and worker-abort path so slow or stuck inbound media fetches stop hanging message processing. (#58593) Thanks @aquaright1
- Telegram/retries: keep non-idempotent sends on the strict safe-send path, retry wrapped pre-connect failures, and preserve `429` / `retry_after` backoff for safe delivery retries. (#51895) Thanks @chinar-amrutkar
- Telegram/exec approvals: route topic-aware exec approval followups through Telegram-owned threading and approval-target parsing, so forum-topic approvals stay in the originating topic instead of falling back to the root chat. (#58783)
- Telegram/local Bot API: preserve media MIME types for absolute-path downloads so local audio files still trigger transcription and other MIME-based handling. (#54603) Thanks @jzakirov
- 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.
## 2026.3.31
### Breaking
- Nodes/exec: remove the duplicated `nodes.run` shell wrapper from the CLI and agent `nodes` tool so node shell execution always goes through `exec host=node`, keeping node-specific capabilities on `nodes invoke` and the dedicated media/location/notify actions.
- Plugin SDK: deprecate the legacy provider compat subpaths plus the older bundled provider setup and channel-runtime compatibility shims, emit migration warnings, and keep the current documented `openclaw/plugin-sdk/*` entrypoints plus local `api.ts` / `runtime-api.ts` barrels as the forward path ahead of a future major-release removal.
- Skills/install and Plugins/install: built-in dangerous-code `critical` findings and install-time scan failures now fail closed by default, so plugin installs and gateway-backed skill dependency installs that previously succeeded may now require an explicit dangerous override such as `--dangerously-force-unsafe-install` to proceed.
- Gateway/auth: `trusted-proxy` now rejects mixed shared-token configs, and local-direct fallback requires the configured token instead of implicitly authenticating same-host callers. Thanks @zhangning-agent, @jacobtomlinson, and @vincentkoc.
- Gateway/node commands: node commands now stay disabled until node pairing is approved, so device pairing alone is no longer enough to expose declared node commands. (#57777) Thanks @jacobtomlinson.
- Gateway/node events: node-originated runs now stay on a reduced trusted surface, so notification-driven or node-triggered flows that previously relied on broader host/session tool access may need adjustment. (#57691) Thanks @jacobtomlinson.
### Changes
- ACP/plugins: add an explicit default-off ACPX plugin-tools MCP bridge config, document the trust boundary, and harden the built-in bridge packaging/logging path so global installs and stdio MCP sessions work reliably. (#56867) Thanks @joe2643.
- Agents/LLM: add a configurable idle-stream timeout for embedded runner requests so stalled model streams abort cleanly instead of hanging until the broader run timeout fires. (#55072) Thanks @liuy.
- Docs/plugins: update the community wecom and qqbot plugin listing to the docs catalog. (#57641) Thanks @sliverp.
- Agents/MCP: materialize bundle MCP tools with provider-safe names (`serverName__toolName`), support optional `streamable-http` transport selection plus per-server connection timeouts, and preserve real tool results from aborted/error turns unless truncation explicitly drops them. (#49505) Thanks @ziomancer.
- Android/notifications: add notification-forwarding controls with package filtering, quiet hours, rate limiting, and safer picker behavior for forwarded notification events. (#40175) Thanks @nimbleenigma.
- Background tasks: turn tasks into a real shared background-run control plane instead of ACP-only bookkeeping by unifying ACP, subagent, cron, and background CLI execution under one SQLite-backed ledger, routing detached lifecycle updates through the executor seam, adding audit/maintenance/status visibility, tightening auto-cleanup and lost-run recovery, improving task awareness in internal status/tool surfaces, and clarifying the split between heartbeat/main-session automation and detached scheduled runs. Thanks @mbelinky and @vincentkoc.
- Background tasks: add the first linear task flow control surface with `openclaw tasks list|show|cancel`, keep manual multi-task flows separate from one-task auto-sync flows, and surface doctor recovery hints for obviously orphaned or broken flow/task linkage. Thanks @mbelinky and @vincentkoc.
- Channels/QQ Bot: add QQ Bot as a bundled channel plugin with multi-account setup, SecretRef-aware credentials, slash commands, reminders, and media send/receive support. (#52986) Thanks @sliverp.
- Diffs: skip unused viewer-versus-file SSR preload work so `diffs` view-only and file-only runs do less render work while keeping mode outputs aligned. (#57909) thanks @gumadeiras.
- Tasks: add a minimal SQLite-backed task flow registry plus task-to-flow linkage scaffolding, so orchestrated work can start gaining a first-class parent record without changing current task delivery behavior. Thanks @mbelinky and @vincentkoc.
- Tasks: persist blocked state on one-task task flows and let the same flow reopen cleanly on retry, so blocked detached work can carry a parent-level reason and continue without fragmenting into a new job. Thanks @mbelinky and @vincentkoc.
- Tasks: route one-task ACP and subagent updates through a parent task-flow owner context, so detached work can emerge back through the intended parent thread/session instead of speaking only as a raw child task. Thanks @mbelinky and @vincentkoc.
- LINE/outbound media: add LINE image, video, and audio outbound sends on the LINE-specific delivery path, including explicit preview/tracking handling for videos while keeping generic media sends on the existing image-only route. (#45826) Thanks @masatohoshino.
- Matrix/history: add optional room history context for Matrix group triggers via `channels.matrix.historyLimit`, with per-agent watermarks and retry-safe snapshots so failed trigger retries do not drift into newer room messages. (#57022) thanks @chain710.
- Matrix/network: add explicit `channels.matrix.proxy` config for routing Matrix traffic through an HTTP(S) proxy, including account-level overrides and matching probe/runtime behavior. (#56931) thanks @patrick-yingxi-pan.
- Matrix/streaming: add draft streaming so partial Matrix replies update the same message in place instead of sending a new message for each chunk. (#56387) Thanks @jrusz.
- Matrix/threads: add per-DM `threadReplies` overrides and keep thread session isolation aligned with the effective room or DM thread policy from the triggering message onward. (#57995) thanks @teconomix.
- MCP: add remote HTTP/SSE server support for `mcp.servers` URL configs, including auth headers and safer config redaction for MCP credentials. (#50396) Thanks @dhananjai1729.
- Memory/QMD: add per-agent `memorySearch.qmd.extraCollections` so agents can opt into cross-agent session search without flattening every transcript collection into one shared QMD namespace. Thanks @vincentkoc.
- Microsoft Teams/member info: add a Graph-backed member info action so Teams automations and tools can resolve channel member details directly from Microsoft Graph. (#57528) Thanks @sudie-codes.
- Nostr/inbound DMs: verify inbound event signatures before pairing or sender-authorization side effects, so forged DM events no longer create pairing requests or trigger reply attempts. Thanks @smaeljaish771 and @vincentkoc.
- OpenAI/Responses: forward configured `text.verbosity` across Responses HTTP and WebSocket transports, surface it in `/status`, and keep per-agent verbosity precedence aligned with runtime behavior. (#47106) Thanks @merc1305 and @vincentkoc.
- Pi/Codex: add native Codex web search support for embedded Pi runs, including config/docs/wizard coverage and managed-tool suppression when native Codex search is active. (#46579) Thanks @Evizero.
- Slack/exec approvals: add native Slack approval routing and approver authorization so exec approval prompts can stay in Slack instead of falling back to the Web UI or terminal. Thanks @vincentkoc.
- TTS: Add structured provider diagnostics and fallback attempt analytics. (#57954) Thanks @joshavant.
- WhatsApp/reactions: agents can now react with emoji on incoming WhatsApp messages, enabling more natural conversational interactions like acknowledging a photo with ❤️ instead of typing a reply. Thanks @mcaxtr.
- Agents/BTW: force `/btw` side questions to disable provider reasoning so Anthropic adaptive-thinking sessions stop failing with `No BTW response generated`. Fixes #55376. Thanks @Catteres and @vincentkoc.
- CLI/onboarding: reset the remote gateway URL prompt to the safe loopback default after declining a discovered endpoint, so onboarding does not keep a previously rejected remote URL. (#57828)
- Agents/exec defaults: honor per-agent `tools.exec` defaults when no inline directive or session override is present, so configured exec host, security, ask, and node settings actually apply. (#57689)
- Sandbox/networking: sanitize SSH subprocess env vars through the shared sandbox policy and route marketplace archive downloads plus Ollama discovery, auth, and pull requests through the guarded fetch path so sandboxed execution and remote fetches follow the repo's trust boundaries. (#57848, #57850)
### Fixes
- Slack: stop retry-driven duplicate replies when draft-finalization edits fail ambiguously, and log configured allowlisted users/channels by readable name instead of raw IDs.
- Agents/OpenAI Responses: normalize raw bundled MCP tool schemas on the WebSocket/Responses path so bare-object, object-ish, and top-level union MCP tools no longer get rejected by OpenAI during tool registration. (#58299) Thanks @yelog.
- ACP/security: replace ACP's dangerous-tool name override with semantic approval classes, so only narrow readonly reads/searches can auto-approve while indirect exec-capable and control-plane tools always require explicit prompt approval. Thanks @vincentkoc.
- ACP: derive owner-only approval classes from the shared tool-policy fallback map so `cron`, `nodes`, and `whatsapp_login` cannot drift out of prompt-required coverage.
- ACP/sessions_spawn: register ACP child runs for completion tracking and lifecycle cleanup, and make registration-failure cleanup explicitly best-effort so callers do not assume an already-started ACP turn was fully aborted. (#40885) Thanks @xaeon2026 and @vincentkoc.
- ACP/tasks: mark cleanly exited ACP runs as blocked when they end on deterministic write or authorization blockers, and wake the parent session with a follow-up instead of falsely reporting success.
- ACPX/runtime: derive the bundled ACPX expected version from the extension package metadata instead of hardcoding a separate literal, so plugin-local ACPX installs stop drifting out of health-check parity after version bumps. (#49089) Thanks @jiejiesks and @vincentkoc.
- Agents/Anthropic failover: treat Anthropic `api_error` payloads with `An unexpected error occurred while processing the response` as transient so retry/fallback can engage instead of surfacing a terminal failure. (#57441) Thanks @zijiess and @vincentkoc.
- Agents/compaction: keep late compaction-retry completions from double-resolving finished compaction futures, so interrupted or timed-out compactions stop surfacing spurious second-completion races. (#57796) Thanks @joshavant.
- Agents/disabled providers: make disabled providers disappear from default model selection and embedded provider fallback, while letting explicitly pinned disabled providers fail with a clear config error instead of silently taking traffic. (#57735) Thanks @rileybrown-dev and @vincentkoc.
- Agents/OAuth output: force exec-host OAuth output readers through the gateway fs policy so embedded gateway runs stop crashing when provider auth writes land outside the current sandbox workspace. (#58249) Thanks @joshavant.
- Agents/system prompt: fix `agent.name` interpolation in the embedded runtime system prompt and make provider/model fallback text reflect the effective runtime selection after start. (#57625) Thanks @StllrSvr and @vincentkoc.
- Android/device info: read the app's version metadata from the package manager instead of hidden APIs so Android 15+ onboarding and device info no longer fail to compile or report placeholder values. (#58126) Thanks @L3ER0Y.
- Android/pairing: stop appending duplicate push receiver entries to `gateway-service.conf` on repeated QR pairing and keep push registration bounded to the current successful pairing, so Android push delivery stays healthy across re-pair and token rotation. (#58256) Thanks @surrealroad.
- App install smoke: pin the latest-release lookup to `latest`, cache the first stable install version across the rerun, and relax prerelease package assertions so the Parallels smoke lane can validate stable-to-main upgrades even when `beta` moves ahead or the guest starts from an older stable. (#58177) Thanks @vincentkoc.
- Auth/profiles: keep the last successful config load in memory for the running process and refresh that snapshot on successful writes/reloads, so hot paths stop reparsing `openclaw.json` between watcher-driven swaps.
- Config/SecretRef + Control UI: harden SecretRef redaction round-trip restore, block unsafe raw fallback (force Form mode when raw is unavailable), and preflight submitted-config SecretRefs before config write RPC persistence. (#58044) Thanks @joshavant.
- Config/update: stop `openclaw doctor` write-backs from persisting plugin-injected channel defaults, so `openclaw update` no longer seeds config keys that later break service refresh validation. (#56834) Thanks @openperf.
- Control UI/agents: auto-load agent workspace files on initial Files panel open, and populate overview model/workspace/fallbacks from effective runtime agent metadata so defaulted models no longer show as `Not set`. (#56637) Thanks @dxsx84.
- Control UI/slash commands: make `/steer` and `/redirect` work from the chat command palette with visible pending state for active-run `/steer`, correct redirected-run tracking, and a single canonical `/steer` entry in the command menu. (#54625) Thanks @fuller-stack-dev.
- Cron/announce: preserve all deliverable text payloads for announce mode instead of collapsing to the last chunk, so multi-line cron reports deliver in full to Telegram forum topics.
- Cron/isolated sessions: carry the full live-session provider, model, and auth-profile selection across retry restarts so cron jobs with model overrides no longer fail or loop on mid-run model-switch requests. (#57972) Thanks @issaba1.
- Diffs/config: preserve schema-shaped plugin config parsing from `diffsPluginConfigSchema.safeParse()`, so direct callers keep `defaults` and `security` sections instead of receiving flattened tool defaults. (#57904) Thanks @gumadeiras.
- Diffs: fall back to plain text when `lang` hints are invalid during diff render and viewer hydration, so bad or stale language values no longer break the diff viewer. (#57902) Thanks @gumadeiras.
- Discord/voice: enforce the same guild channel and member allowlist checks on spoken voice ingress before transcription, so joined voice channels no longer accept speech from users outside the configured Discord access policy. Thanks @cyjhhh and @vincentkoc.
- Docker/setup: force BuildKit for local image builds (including sandbox image builds) so `./docker-setup.sh` no longer fails on `RUN --mount=...` when hosts default to Docker's legacy builder. (#56681) Thanks @zhanghui-china.
- Docs/anchors: fix broken English docs links and make Mint anchor audits run against the English-source docs tree. (#57039) thanks @velvet-shark.
- Doctor/plugins: skip false Matrix legacy-helper warnings when no migration plans exist, and keep bundled `enabledByDefault` plugins in the gateway startup set. (#57931) Thanks @dinakars777.
- Exec approvals/macOS: unwrap `arch` and `xcrun` before deriving shell payloads and allow-always patterns, so wrapper approvals stay bound to the carried command instead of the outer carrier. Thanks @tdjackey and @vincentkoc.
- Exec approvals: unwrap `caffeinate` and `sandbox-exec` before persisting allow-always trust so later shell payload changes still require a fresh approval. Thanks @tdjackey and @vincentkoc.
- Exec/approvals: infer Discord and Telegram exec approvers from existing owner config when `execApprovals.approvers` is unset, extend the default approval window to 30 minutes, and clarify approval-unavailable guidance so approvals do not appear to silently disappear.
- Pi/TUI: flush message-boundary replies at `message_end` so turns stop looking stuck until the next nudge when the final reply was already ready. Thanks @vincentkoc.
- Status/tasks: fall back to same-agent task counts in `/status` when the current session has no linked tasks, keeping the default view useful without exposing other sessions' task details. Thanks @vincentkoc.
- Status/auto-reply: stop status-only turns from replying twice when inline `/status` handling already produced the reply, so Discord and other chat surfaces no longer emit duplicate status cards. Thanks @vincentkoc.
- Exec/approvals: keep `awk` and `sed` family binaries out of the low-risk `safeBins` fast path, and stop doctor profile scaffolding from treating them like ordinary custom filters. Thanks @vincentkoc.
- Exec/env: block proxy, TLS, and Docker endpoint env overrides in host execution so request-scoped commands cannot silently reroute outbound traffic or trust attacker-supplied certificate settings. Thanks @AntAISecurityLab.
- Exec/env: block Python package index override variables from request-scoped host exec environment sanitization so package fetches cannot be redirected through a caller-supplied index. Thanks @nexrin and @vincentkoc.
- Exec/node: stop gateway-side workdir fallback from rewriting explicit `host=node` cwd values to the gateway filesystem, so remote node exec approval and runs keep using the intended node-local directory. (#50961) Thanks @openperf.
- Exec/runtime: default implicit exec to `host=auto`, resolve that target to sandbox only when a sandbox runtime exists, keep explicit `host=sandbox` fail-closed without sandbox, and show `/exec` effective host state in runtime status/docs.
- Exec: fail closed when the implicit sandbox host has no sandbox runtime, and stop denied async approval followups from reusing prior command output from the same session. (#56800) Thanks @scoootscooob.
- Feishu/groups: keep quoted replies and topic bootstrap context aligned with group sender allowlists so only allowlisted thread messages seed agent context. Thanks @AntAISecurityLab and @vincentkoc.
- Gateway/attachments: offload large inbound images without leaking `media://` markers into text-only runs, preserve mixed attachment order for model input/transcripts, and fail closed when model image capability cannot be resolved. (#55513) Thanks @Syysean.
- Gateway/auth: keep shared-auth rate limiting active during WebSocket handshake attempts even when callers also send device-token candidates, so bogus device-token fields no longer suppress shared-secret brute-force tracking. Thanks @kexinoh and @vincentkoc.
- Gateway/auth: reject mismatched browser `Origin` headers on trusted-proxy HTTP operator requests while keeping origin-less headless proxy clients working. Thanks @AntAISecurityLab and @vincentkoc.
- Gateway/device tokens: disconnect active device sessions after token rotation so newly rotated credentials revoke existing live connections immediately instead of waiting for those sockets to close naturally. Thanks @zsxsoft and @vincentkoc.
- Gateway/health: carry webhook-vs-polling account mode from channel descriptors into runtime snapshots so passive channels like LINE and BlueBubbles skip false stale-socket health failures. (#47488) Thanks @karesansui-u.
- Gateway/pairing: restore QR bootstrap onboarding handoff so fresh `/pair qr` iPhone setup can auto-approve the initial node pairing, receive a reusable node device token, and stop retrying with spent bootstrap auth. (#58382) Thanks @ngutman.
- Gateway/OpenAI compatibility: accept flat Responses API function tool definitions on `/v1/responses` and preserve `strict` when normalizing hosted tools into the embedded runner, so spec-compliant clients like Codex no longer fail validation or silently lose strict tool enforcement. Thanks @malaiwah and @vincentkoc.
- Gateway/OpenAI HTTP: restore default operator scopes for bearer-authenticated requests that omit `x-openclaw-scopes`, so headless `/v1/chat/completions` and session-history callers work again after the recent method-scope hardening. (#57596) Thanks @openperf.
- Gateway/plugins: scope plugin-auth HTTP route runtime clients to read-only access and keep gateway-authenticated plugin routes on write scope, so plugin-owned webhook handlers do not inherit write-capable runtime access by default. Thanks @davidluzsilva and @vincentkoc.
- Gateway/SecretRef: resolve restart token drift checks with merged service/runtime env sources and hard-fail unsupported mutable SecretRef plus OAuth-profile combinations so restart warnings and policy enforcement match runtime behavior. (#58141) Thanks @joshavant.
- Gateway/tools HTTP: tighten HTTP tool-invoke authorization so owner-only tools stay off HTTP invoke paths. (#57773) Thanks @jacobtomlinson.
- Harden async approval followup delivery in webchat-only sessions (#57359) Thanks @joshavant.
- Heartbeat/auth: prevent exec-event heartbeat runs from inheriting owner-only tool access from the session delivery target, so node exec output stays on the non-owner tool surface even when the target session belongs to the owner. Thanks @AntAISecurityLab and @vincentkoc.
- Hooks/config: accept runtime channel plugin ids in `hooks.mappings[].channel` (for example `feishu`) instead of rejecting non-core channels during config validation. (#56226) Thanks @AiKrai001.
- Hooks/session routing: rebind hook-triggered `agent:` session keys to the actual target agent before isolated dispatch so dedicated hook agents keep their own session-scoped tool and plugin identity. Thanks @kexinoh and @vincentkoc.
- Host exec/env: block additional request-scoped env overrides that can redirect Docker endpoints, trust roots, compiler include paths, package resolution, or Python environment roots during approved host runs. Thanks @tdjackey and @vincentkoc.
- Image generation/build: write stable runtime alias files into `dist/` and route provider-auth runtime lookups through those aliases so image-generation providers keep resolving auth/runtime modules after rebuilds instead of crashing on missing hashed chunk files.
- iOS/Live Activities: mark the `ActivityKit` import in `LiveActivityManager.swift` as `@preconcurrency` so Xcode 26.4 / Swift 6 builds stop failing on strict concurrency checks. (#57180) Thanks @ngutman.
- LINE/ACP: add current-conversation binding and inbound binding-routing parity so `/acp spawn ... --thread here`, configured ACP bindings, and active conversation-bound ACP sessions work on LINE like the other conversation channels.
- LINE/markdown: preserve underscores inside Latin, Cyrillic, and CJK words when stripping markdown, while still removing standalone `_italic_` markers on the shared text-runtime path used by LINE and TTS. (#47465) Thanks @jackjin1997.
- Agents/failover: make overloaded same-provider retry count and retry delay configurable via `auth.cooldowns`, default to one retry with no delay, and document the model-fallback behavior.
- Ollama/model picker: include configured Ollama models in the opted-in non-PI-native model catalog path so Ollama onboarding shows available models directly after provider selection. (#55290) Thanks @Luckymingxuan.
## 2026.3.31-beta.1
### Fixes
- Agents/compaction: keep late compaction-retry rejections handled after the aggregate timeout path wins without swallowing real pre-timeout wait failures, so timed-out retries no longer surface an unhandled rejection on later unsubscribe. (#57451) Thanks @mpz4life and @vincentkoc.
- Agents/context pruning: count supplementary-plane CJK characters with the shared code-point-aware estimator so context pruning stops underestimating Japanese and Chinese text that uses Extension B ideographs. (#39985) Thanks @Edward-Qiang-2024.
- Agents/Kimi: preserve already-valid Anthropic-compatible tool call argument objects while still clearing cached repairs when later trailing junk exceeds the repair allowance. (#54491) Thanks @yuanaichi.
- Agents/MCP: dispose bundled MCP runtimes after one-shot `openclaw agent --local` runs finish, while preserving bundled MCP state across in-run retries so local JSON runs exit cleanly without restarting stateful MCP tools mid-run.
- Agents/MCP: reuse bundled MCP runtimes across turns in the same session, while recreating them when MCP config changes and disposing stale runtimes cleanly on session rollover. (#55090) Thanks @allan0509.
- Agents/memory flush: keep daily memory flush files append-only during embedded attempts so compaction writes do not overwrite earlier notes. (#53725) Thanks @HPluseven.
- Agents/sandbox: honor `tools.sandbox.tools.alsoAllow`, let explicit sandbox re-allows remove matching built-in default-deny tools, and keep sandbox explain/error guidance aligned with the effective sandbox tool policy. (#54492) Thanks @ngutman.
- Agents/sandbox: make remote FS bridge reads pin the parent path and open the file atomically in the helper so read access cannot race path resolution. Thanks @AntAISecurityLab and @vincentkoc.
- Agents/silent turns: fail closed on silent memory-flush runs so narrated `NO_REPLY` self-talk cannot stream or finalize into external replies even when block streaming is enabled. (#52593)
- Agents/subagents: fix interim subagent runtime display so `/subagents list` and `/subagents info` stop inflating short runtimes and show second-level durations correctly. (#57739) Thanks @samzong.
- Anthropic/OAuth: inject `/fast` `service_tier` hints for direct `sk-ant-oat-*` requests so OAuth-authenticated Anthropic runs stop missing the same overload-routing signal as API-key traffic. Fixes #55758. Thanks @Cypherm and @vincentkoc.
- Anthropic/service tiers: support explicit `serviceTier` model params for direct Anthropic requests and let them override `/fast` defaults when both are set. (#45453) Thanks @vincentkoc.
- Auto-reply/fast: accept `/fast status` on the directive-only path, align help/status text with the documented `status|on|off` syntax, and keep current-state replies consistent across command surfaces. Fixes #46095. Thanks @weissfl and @vincentkoc.
- Azure OpenAI/custom providers: use the `azure-openai-responses` path for Azure custom providers so Azure OpenAI endpoints stay on the correct Responses integration surface. (#50851) Thanks @kunalk16.
- BlueBubbles/iMessage: coalesce URL-only inbound messages with their link-preview balloon again so sharing a bare link no longer drops the URL from agent context. Thanks @vincentkoc.
- Browser/plugins: auto-enable the bundled browser plugin when browser config or browser tool policy already references it, and show a clearer CLI error when `plugins.allow` excludes `browser`.
- CI/dev checks: default local `pnpm check` to a lower-memory typecheck/lint path while keeping CI on the normal parallel path, and harden Telegram test typing/literals around native TypeScript-Go tooling crashes.
- Tasks: add a small task-flow runtime substrate for authoring layers with persisted wait targets and output bags, plus bundled skills/Lobster examples and richer `flows show` / `doctor` recovery hints for multi-task flow state. (#58336) Thanks @mbelinky and @vincentkoc.
- Config/legacy cleanup: stop probing obsolete alternate legacy config names and service labels during local config/service detection, while keeping the active `~/.openclaw/openclaw.json` path canonical.
- Config/runtime: pin the first successful config load in memory for the running process and refresh that snapshot on successful writes/reloads, so hot paths stop reparsing `openclaw.json` between watcher-driven swaps.
- LINE/status: stop `openclaw status` from warning about missing credentials when sanitized LINE snapshots are already configured, while still surfacing whether the missing field is the token or secret. (#45701) Thanks @tamaosamu.
- macOS/local gateway: stop OpenClaw.app from killing healthy local gateway listeners after startup by recognizing the current `openclaw-gateway` process title and using the current `openclaw gateway` launch shape.
- macOS/wide-area discovery: switch gateway discovery to Tailscale MagicDNS names so Mac clients recover more reliably across changing tailnet IPs. (#57833) Thanks @jacobtomlinson.
- Matrix/CLI send: start one-off Matrix send clients before outbound delivery so `openclaw message send --channel matrix` restores E2EE in encrypted rooms instead of sending plain events. (#57936) Thanks @gumadeiras.
- Matrix/context: filter fetched room context by sender allowlists so reply and thread context lookup no longer pulls non-allowlisted messages into agent context. (#58376) Thanks @jacobtomlinson.
- Matrix/delivery recovery: treat Synapse `User not in room` replay failures as permanent during startup recovery so poisoned queued messages move to `failed/` instead of crash-looping Matrix after restart. (#57426) thanks @dlardo.
- Matrix/direct rooms: recover fresh auto-joined 1:1 DMs without eagerly persisting invite-only `m.direct` mappings, while keeping named, aliased, and explicitly configured rooms on the room path. (#58024) Thanks @gumadeiras.
- Matrix/direct rooms: stop trusting remote `is_direct`, honor explicit local `is_direct: false` for discovered DM candidates, and avoid extra member-state lookups for shared rooms so DM routing and repair stay aligned. (#57124) Thanks @w-sss.
- Matrix/DM threads: keep strict unnamed fresh-invite rooms promotable even when Matrix omits the optional direct hint, preserve repair-failed local DM promotions while still revalidating later room metadata, and keep both bound and thread-isolated Matrix sessions reporting the correct route policy. (#58099) Thanks @gumadeiras.
- Matrix/plugin loading: ship and source-load the crypto bootstrap runtime sidecar correctly so current `main` stops warning about failed Matrix bootstrap loads and `matrix/index` plugin-id mismatches on every invocation. (#53298) thanks @keithce.
- Mattermost/websocket: detect stale Mattermost WebSocket sessions after bot disable/enable cycles so monitoring reconnects cleanly instead of silently staying stale. (#53604) Thanks @Qinsam.
- Media/downloads: stop forwarding auth and cookie headers across cross-origin redirects during media saves, while preserving safe request headers for same-origin redirect chains. Thanks @AntAISecurityLab and @vincentkoc.
- Media/images: reject oversized decoded image inputs before metadata and resize backends run, so tiny compressed image bombs fail early instead of exhausting gateway memory. (#58226) Thanks @AntAISecurityLab and @vincentkoc.
- Memory/doctor: probe QMD availability from the agent workspace too, so `openclaw doctor` no longer falsely reports relative `memory.qmd.command` configs as broken while runtime search still works. Thanks @vincentkoc.
- Memory/doctor: suppress the orphan transcript cleanup warning when QMD session indexing is enabled, so doctor no longer suggests deleting transcript history that QMD still uses for recall. (#40584) Thanks @Gyarados4157 and @vincentkoc.
- Memory/FTS: add configurable trigram tokenization plus short-CJK substring fallback so memory search can find Chinese, Japanese, and Korean text without breaking mixed long-and-short queries. Thanks @carrotRakko.
- Memory/FTS: keep provider-less keyword hits visible at the default memory-search threshold, so FTS-only recall works without requiring `--min-score 0`. (#56473) Thanks @opriz.
- Memory/LanceDB: resolve runtime dependency manifest lookup from the bundled `extensions/memory-lancedb` path (including flattened dist chunks) so startup no longer fails with a missing `@lancedb/lancedb` dependency error. (#56623) Thanks @LUKSOAgent.
- Memory/QMD: add `memory.qmd.searchTool` as an exact mcporter tool override, so custom QMD MCP tools such as `hybrid_search` can be used without weakening the validated `searchMode` config surface. (#27801) Thanks @keramblock.
- Memory/QMD: honor `memory.qmd.update.embedInterval` even when regular QMD update cadence is disabled or slower by arming a dedicated embed-cadence maintenance timer, while avoiding redundant timers when regular updates are already frequent enough. (#37326) Thanks @barronlroth.
- Memory/QMD: include deduplicated default plus per-agent `memorySearch.extraPaths` when building QMD custom collections, so shared and agent-specific extra roots both get indexed consistently. (#57315) Thanks @Vitalcheffe and @vincentkoc.
- Memory/QMD: keep `memory_search` session-hit paths roundtrip-safe when exported session markdown lives under the workspace `qmd/` directory, so `memory_get` can read the exact returned path instead of failing on the generic `qmd/sessions/...` alias. (#43519) Thanks @holgergruenhagen and @vincentkoc.
- Memory/QMD: keep `qmd embed` active in `search` mode too, so BM25-first setups still build a complete index for later vector and hybrid retrieval. (#54509) Thanks @hnshah and @vincentkoc.
- Memory/QMD: keep reset and deleted session transcripts in QMD session export so daily session resets do not silently drop most historical recall from `memory_search`. (#30220) Thanks @pushkarsingh32.
- Memory/QMD: pass a direct-session key on `openclaw memory search` so CLI QMD searches no longer get denied as `session=<none>` under direct-only scope defaults. (#43517) Thanks @waynecc-at and @vincentkoc.
- Memory/QMD: point `QMD_CONFIG_DIR` at the nested `xdg-config/qmd` directory so per-agent collection config resolves correctly. (#39078) Thanks @smart-tinker and @vincentkoc.
- Memory/QMD: preserve explicit `start_line` and `end_line` metadata from mcporter query results so `memory search` hits keep the real snippet offsets instead of falling back to the snippet header. (#47960) Thanks @vincentkoc.
- Memory/QMD: rebind collections when QMD reports a changed pattern but omits path metadata, so config pattern changes stop being silently ignored on restart. (#49897) Thanks @Madruru.
- Memory/QMD: resolve slugified `memory_search` file hints back to the indexed filesystem path before returning search hits, so `memory_get` works again for mixed-case and spaced paths. (#50313) Thanks @erra9x.
- Memory/QMD: send MCP `query` collection filters as the upstream `collections` array instead of the legacy singular `collection` field, so mcporter-backed QMD 1.1+ searches still scope correctly after the unified `query` tool migration. (#54728) Thanks @armanddp and @vincentkoc.
- Memory/QMD: serialize cross-process `qmd embed` runs behind a shared lock and stagger periodic embed timers so multi-agent QMD collections stop thundering-herding on startup and every maintenance interval. Thanks @vincentkoc.
- Memory/QMD: stop rewriting Han/CJK BM25 queries before `qmd search`, so OpenClaw search semantics match direct QMD results for mixed and spaced Chinese queries. Thanks @vincentkoc.
- Memory/QMD: surface degraded vector status from `qmd status` so `openclaw memory status --deep` warns when semantic search is unavailable because the index still has `0` vectors. Fixes #28169. Thanks @vincentkoc.
- Memory/QMD: treat null-byte collection corruption the same when QMD surfaces it as `ENOENT`, so managed-collection repair still rebuilds and retries instead of leaving QMD stuck on a broken path. Thanks @vincentkoc.
- Memory/QMD: warn explicitly when `memory.backend=qmd` is configured but the `qmd` binary is missing, so doctor and runtime fallback no longer fail as a silent builtin downgrade. (#50439) Thanks @Jimmy-xuzimo and @vincentkoc.
- Memory/QMD: weight CJK-heavy text correctly when estimating chunk sizes, preserve surrogate-pair characters during fine splits, and keep long Latin lines on the old chunk boundaries so memory indexing produces better-sized chunks for CJK notes. (#40271) Thanks @AaronLuo00.
- Memory/session indexer: include `.jsonl.reset.*` and `.jsonl.deleted.*` transcripts in the memory host session scan while still excluding `.jsonl.bak.*` compaction backups and lock files, so memory search sees archived session history without duplicating stale snapshots. Thanks @hclsys and @vincentkoc.
- Microsoft Teams/threads: filter fetched thread history by sender allowlists so thread context seeding no longer pulls messages from disallowed users. (#57723) Thanks @jacobtomlinson.
- OpenAI/Codex fast mode: map `/fast` to priority processing on native OpenAI and Codex Responses endpoints instead of rewriting reasoning settings, and document the exact endpoint and override behavior.
- Outbound media/local files: piggyback host-local `MEDIA:` reads on the configured fs policy instead of a separate media-root check, so generated files outside the workspace can send when `tools.fs.workspaceOnly=false` while plaintext-like host files stay blocked by the outbound media allowlist.
- Pairing: enforce pending request limits per account instead of per shared channel queue, so one account's outstanding pairing challenges no longer block new pairing on other accounts. Thanks @smaeljaish771 and @vincentkoc.
- Plugins/ClawHub: sanitize temporary archive filenames for scoped package names and slash-containing skill slugs so `openclaw plugins install @scope/name` no longer fails with `ENOENT` during archive download. (#56452) Thanks @soimy.
- Plugins/CLI: add descriptor-backed lazy plugin CLI registration so Matrix can keep its CLI module lazy-loaded without dropping `openclaw matrix ...` from parse-time command registration. (#57165) Thanks @gumadeiras.
- Plugins/CLI: collect root-help plugin descriptors through a dedicated non-activating CLI metadata path so enabled plugins keep validated config semantics without triggering runtime-only plugin registration work, while preserving runtime CLI command registration for legacy channel plugins that still wire commands from full registration. (#57294) thanks @gumadeiras.
- Plugins/facades: guard bundled plugin facade loads with a cache-first sentinel so circular re-entry stops crashing `xai`, `sglang`, and `vllm` during gateway plugin startup. (#57508) Thanks @openperf.
- Plugins/Matrix: mirror the Matrix crypto WASM runtime dependency into the root packaged install and enforce root/plugin dependency parity so bundled Matrix E2EE crypto resolves correctly in shipped builds. (#57163) Thanks @gumadeiras.
- Plugins/startup: block workspace `.env` from overriding `OPENCLAW_BUNDLED_PLUGINS_DIR`, so bundled plugin trust roots only come from inherited runtime env or package resolution instead of repo-local dotenv files. Thanks @nexrin and @vincentkoc.
- Sandbox/browser: install `fonts-noto-cjk` in the sandbox browser image so screenshots render Chinese, Japanese, and Korean text correctly instead of tofu boxes. Fixes #35597. Thanks @carrotRakko and @vincentkoc.
- Security/LINE: make webhook signature validation run the timing-safe compare even when the supplied signature length is wrong, closing a small timing side-channel. (#55663) Thanks @gavyngong.
- Sessions/Feishu: preserve conversation ids that legitimately embed `:topic:` in shared session helper parsing, while keeping Telegram topic session parsing intact. (#58100) Thanks @gumadeiras.
- Slack/status reactions: add a reaction lifecycle for queued, thinking, tool, done, and error phases in Slack monitors, with safer cleanup so queued ack reactions stay correct across silent runs, pre-reply failures, and delayed transitions. (#56430) Thanks @hsiaoa.
- Status/node-only hosts: teach `openclaw status` to handle node-only hosts on current `main` without the old mixed gateway assumptions. (#56718) Thanks @ImLukeF.
- Status: fix cache hit rate exceeding 100% by deriving denominator from prompt-side token fields instead of potentially undersized totalTokens. Fixes #26643.
- Telegram/audio: transcode Telegram voice-note `.ogg` attachments before the local `whisper-cli` auto fallback runs, and keep mention-preflight transcription enabled in auto mode when `tools.media.audio` is unset.
- Telegram/forum topics: restore reply routing to the active topic and keep ACP `sessions_spawn(..., thread=true, mode="session")` bound to that same topic instead of falling back to root chat or losing follow-up routing. (#56060) Thanks @one27001.
- Telegram/media: allow RFC 2544 benchmark-range Telegram CDN resolutions during media downloads, so voice messages, PDFs, and other attachments no longer fail with `Failed to download media`. (#57624) Thanks @MoerAI.
- Telegram/native commands: prefix native command menu callback payloads and preserve `CommandSource: "native"` when Telegram replays them through callback queries, so `/fast` and other native command menus keep working even when text-command routing is disabled. Thanks @vincentkoc.
- Telegram/polling: keep the watchdog from aborting long-running reply delivery by treating recent non-polling API activity as bounded liveness instead of a hard stall. (#56343) Thanks @openperf.
- Tools/web_fetch: add an explicit trusted env-proxy path for proxy-only installs while keeping strict SSRF fetches on the pinned direct path, so trusted proxy routing does not weaken strict destination binding. (#50650) Thanks @kkav004.
- Tools/web_search: localize the shared search cache to module scope so same-process global symbol lookups can no longer inspect or mutate cached web-search responses. Thanks @vincentkoc.
- TTS/Microsoft: auto-switch the default Edge voice to Chinese for CJK-dominant text without overriding explicitly selected Microsoft voices. (#52355) Thanks @extrasmall0.
- TTS: Restore 3.28 schema compatibility and fallback observability. (#57953) Thanks @joshavant.
- TUI/chat: keep optimistic outbound user messages visible during active runs by deferring local-run binding until the first gateway chat event reveals the real run id, preventing premature history reloads from wiping pending local sends. (#54722) Thanks @seanturner001.
- TUI/model picker: keep searchable `/model` and `/models` input mode from hijacking `j`/`k` as navigation keys, and harden width bounds under `m`-filtered model lists so search no longer crashes on long rows. (#30156) Thanks @briannicholls.
- Discord/exec approvals: stop mixing native interactive approvals with extra plain `/approve` narration, so Discord approval prompts stay button-driven instead of showing both manual and interactive flows. Thanks @vincentkoc.
- Telegram/exec approvals: normalize 1:1 approval routing and suppress duplicate approval prompts, so Telegram direct chats show a single working interactive approval instead of multiple conflicting approval messages. Thanks @vincentkoc.
- Voice Call/media stream: cap inbound WebSocket frame size before `start` validation so oversized pre-start frames are dropped before JSON parsing. Thanks @Kazamayc and @vincentkoc.
- Voice call/Plivo: pin stored callback bases to the configured public webhook URL so later call-control redirects stay on the intended origin even if webhook transport metadata differs. Thanks @zsxsoft and @vincentkoc.
- Web UI/markdown: stop bare auto-links from swallowing adjacent CJK text while preserving valid mixed-script path and query characters in rendered links. (#48410) Thanks @jnuyao.
- Approvals/UI: keep the newest pending approval at the front of the Control UI queue so approving one request does not accidentally target an older expired id. Thanks @vincentkoc.
- Auth profiles/OAuth: refresh runtime auth snapshots when saving rotated credentials so OAuth providers do not reuse consumed refresh tokens after the first token rotation. Fixes #55389. Thanks @sam26880 and @vincentkoc.
- Browser/screenshot: use `fromSurface: false` in raw CDP screenshots to avoid a Chromium compositor bug that drops cross-origin image textures (QR codes, CDN assets), and preserve pre-existing device emulation state across full-page viewport expansion. (#54358) Thanks @FMLS.
- ClawDock/docs: move the helper scripts to `scripts/clawdock`, publish ClawDock as a first-class docs page on the docs site, and document reinstalling local helper copies from the new raw GitHub path. (#23912) thanks @Olshansk.
- Control UI/gateway: clear queued browser connect timeouts on client stop so aborted or replaced gateway clients do not send delayed connect requests after shutdown. (#57338) thanks @gumadeiras.
- Control UI/gateway: reconnect the browser client when gateway event sequence gaps are detected, so stale non-chat state recovers automatically instead of only telling the user to refresh. (#23912) thanks @Olshansk.
- Exec approvals/channels: unify Discord and Telegram exec approval runtime handling, move approval buttons onto the shared interactive reply model, and fix Telegram approval buttons and typed `/approve` commands so configured approvers can resolve requests reliably again. (#57516) Thanks @scoootscooob.
- Gateway/SQLite transient handling: keep unhandled `SQLITE_CANTOPEN`, `SQLITE_BUSY`, `SQLITE_LOCKED`, and `SQLITE_IOERR` failures non-fatal in the global rejection handler so macOS LaunchAgent restarts do not enter a crash-throttle loop. (#57018)
- Hooks/plugins/skills: block workspace `.env` overrides for bundled root directories so workspace startup cannot redirect bundled trust roots away from the packaged defaults. Thanks @nexrin and @vincentkoc.
- LINE/webhooks: cap shared concurrent pre-verify webhook body reads so excess requests are rejected before entering the LINE body handler. Thanks @nexrin and @vincentkoc.
- Memory/QMD: preserve explicit custom collection names for shared paths outside the agent workspace so `memory_search` stops appending `-<agentId>` to externally managed QMD collections. (#52539) Thanks @lobsrice and @vincentkoc.
- Memory/builtin: keep memory-file indexing active in FTS-only mode (no embedding provider) so forced reindexes no longer swap in an empty index and wipe existing memory chunks. (#42714) Thanks @asamimei.
- Nostr/config: redact `channels.nostr.privateKey` in config snapshots and Control UI config views, so Nostr signing keys no longer appear in plain text. Thanks @ccreater222.
- Plugin approvals: accept unique short approval-id prefixes on `plugin.approval.resolve`, matching exec approvals and restoring `/approve` fallback flows on chat approval surfaces. Thanks @vincentkoc.
- SSH sandbox/upload: reject workspace symlinks that resolve outside the uploaded tree before syncing to the remote sandbox, so later agent writes cannot be redirected through escaped links. Thanks @AntAISecurityLab and @vincentkoc.
- Slack/interactive replies: resolve Slack Block Kit reply delivery from both authored `channelData.slack.blocks` payloads and directive-generated interactive replies, and keep Slack button styles mapped onto valid Block Kit button rendering so interactive replies stop dropping on Slack-specific delivery paths. Thanks @vincentkoc.
- Subagents/announcements: preserve the requester agent id for inline deterministic tool spawns so named agents without channel bindings can still announce completions through the correct owner session. (#55437) Thanks @kAIborg24.
- Telegram/Anthropic streaming: replace raw invalid stream-order provider errors with a safe retry message so internal `message_start/message_stop` failures do not leak into chats. (#55408) Thanks @imydal.
- Tlon/media: route inbound image downloads through the shared media store, cap each download at 6 MB, and stop after 8 images per message so large Tlon posts no longer balloon local media storage. Thanks @AntAISecurityLab and @vincentkoc.
- Agents/live switch: stop transient cron and subagent model overrides from being misread as persisted live-session switches, so isolated runs no longer fail with `LiveSessionModelSwitchError`. Thanks @vincentkoc.
- Agents/tool-call repair: recover malformed Kimi/OpenRouter tool-call argument streams when provider preambles appear before JSON payloads, and fail closed on non-tool leading text so fragment strings do not leak into filesystem path arguments during sub-agent runs. (#56560) Thanks @Originalwhite.
- Gateway/startup: keep configured primary-model warmup on the static model path so container boots do not snapshot-load provider runtime graphs just to validate a configured model. Thanks @vincentkoc.
- OpenAI/Responses: omit disabled reasoning payloads across OpenAI, Codex, and Azure OpenAI request builders so GPT-5 endpoints no longer reject `reasoning.effort: "none"`. (#58208) Thanks @jalehman.
- WhatsApp/outbound: restore runtime send/action routing and outbound compatibility after the recent channel seam refactor so outbound sends, reactions, and related media actions keep reaching the active session.
- xAI/Responses: normalize image-bearing tool results for xAI responses payloads, including OpenResponses-style `input_image.source` parts, so image tool replays no longer 422 on the follow-up turn. (#58017) Thanks @neeravmakwana.
- Zalo/webhooks: scope replay dedupe to the authenticated target so one configured account can no longer cause same-id inbound events for another target to be dropped. Thanks @smaeljaish771 and @vincentkoc.
- Plugins/prompt build: preserve the highest-priority `systemPrompt` when merging `before_prompt_build` hook results instead of letting lower-priority hooks overwrite it. (#58375)
- Tlon/settings migration: preserve explicit empty-array settings during migration so restart-time reseeding no longer restores older file-config values. (#58370)
- Plugins/marketplace: harden marketplace archive installs by surfacing guarded download failures as structured install errors and deriving temp filenames from the final resolved URL. (#58267)
- UI/DOM safety: build the chat delete-confirm popover and Canvas Host fallback status line with DOM nodes instead of injected HTML strings. (#58269, #58266)
- Media/file handling: create image temp work directories under the shared OpenClaw temp root and stop widening allowed local media roots from reply or tool source paths, keeping local media access limited to configured and agent-scoped roots. (#58270, #57770)
- Telegram/security: migrate legacy pairing `allowFrom` state to the `default` account only and gate group voice-message preflight transcription on sender authorization so unauthorized senders cannot trigger paid transcription before being dropped. (#58165, #57566)
- Exec approvals: stop shell init-file flags from matching persisted script-path approvals, detect command-carrier inline eval in tools like `awk`, `find`, `xargs`, `make`, and `sed`, and treat the awk family as interpreter-like so allow-always decisions no longer persist interpreter paths. (#58369, #57842, #57772)
- Voice call/Telnyx: canonicalize verified webhook signatures before deriving replay keys so equivalent Base64 and Base64URL encodings dedupe correctly. (#57829)
- Gateway/HTTP trust boundaries: ignore self-declared bearer scopes, deny dangerous HTTP tool-invoke paths by default, require write scope on `/v1/embeddings`, and bind OpenAI-compatible `/v1/chat/completions` plus `/v1/responses` ingress as non-owner so external HTTP callers cannot self-upgrade access. (#57783, #57771, #57721, #57769, #57778)
- Gateway/trusted config and bootstrap: clear self-declared scopes for device-less `trusted-proxy` WebSocket connects and trim the Control UI bootstrap payload to only the fields needed before the normal handshake. (#57692, #57727)
- Skills and workspace safety: replace raw skill-file reads with a symlink-safe, root-confined loader and pin workspace-only `apply_patch` delete and directory-creation mutations to verified workspace roots so path rebind races fail closed. (#57519, #56016)
- Env and filesystem hardening: block credential and gateway auth env vars from workspace `.env` files, always strip dangerous inherited host env vars such as `BROWSER` and `GIT_EDITOR`, and keep sensitive host paths and OpenClaw state roots blocked even when external sandbox bind sources are allowed. (#57767, #57559, #56024)
- OpenShell and ACP file boundaries: skip symlinks and preserve trusted host-only directories during OpenShell mirror sync, and route ACP attachment reads through the shared attachment cache so local files outside allowed roots are no longer forwarded. (#57693, #57690)
- Channel/webhook authorization: skip Discord audio preflight transcription for unauthorized guild senders, add a shared pre-auth in-flight guard to Synology Chat webhooks, and validate Microsoft Teams bearer auth before JSON body parsing. (#57695, #57722, #57686)
- Infra/randomness: replace `Math.random()` in affected identifier and delay-jitter paths with shared secure-random helpers, including UI UUID generation. (#57744)
## 2026.3.28
### Changes
@@ -128,12 +461,11 @@ Docs: https://docs.openclaw.ai
- Memory/plugins: move the pre-compaction memory flush plan behind the active memory plugin contract so `memory-core` owns flush prompts and target-path policy instead of hardcoded core logic.
- MiniMax: trim model catalog to M2.7 only, removing legacy M2, M2.1, M2.5, and VL-01 models. (#54487) Thanks @liyuan97.
- Plugins/runtime: expose `runHeartbeatOnce` in the plugin runtime `system` namespace so plugins can trigger a single heartbeat cycle with an explicit delivery target override (e.g. `heartbeat: { target: "last" }`). (#40299) Thanks @loveyana.
- Background tasks: keep durable lifecycle records for ACP/subagent spawned work and deliver ACP completion/failure updates through the real requester chat path instead of session-only stream events.
- Background tasks: keep durable lifecycle records for ACP/subagent spawned work and deliver ACP completion/failure updates through the real requester chat path instead of session-only stream events. Thanks @mbelinky and @vincentkoc.
- Agents/compaction: preserve the post-compaction AGENTS refresh on stale-usage preflight compaction for both immediate replies and queued followups. (#49479) Thanks @jared596.
- Agents/compaction: surface safeguard-specific cancel reasons and relabel benign manual `/compact` no-op cases as skipped instead of failed. (#51072) Thanks @afurm.
- Docs: add `pnpm docs:check-links:anchors` for Mintlify anchor validation while keeping `scripts/docs-link-audit.mjs` as the stable link-audit entrypoint. (#55912) Thanks @velvet-shark.
- Tavily: mark outbound API requests with `X-Client-Source: openclaw` so Tavily can attribute OpenClaw-originated traffic. (#55335) Thanks @lakshyaag-tavily.
- Matrix/streaming: add `streaming: "partial"` draft replies that stay on a single editable preview message, stop preview streaming once text no longer fits one Matrix event, and clear stale previews before media-only finals. (#56387) thanks @jrusz.
### Fixes
@@ -145,8 +477,7 @@ Docs: https://docs.openclaw.ai
- Telegram/splitting: replace proportional text estimate with verified HTML-length search so long messages split at word boundaries instead of mid-word; gracefully degrade when tag overhead exceeds the limit. (#56595)
- Telegram/delivery: skip whitespace-only and hook-blanked text replies in bot delivery to prevent GrammyError 400 empty-text crashes. (#56620)
- Telegram/send: validate `replyToMessageId` at all four API sinks with a shared normalizer that rejects non-numeric, NaN, and mixed-content strings. (#56587)
- Approvals/UI: keep the newest pending approval at the front of the Control UI queue so approving one request does not accidentally target an older expired id. Thanks @vincentkoc.
- Plugin approvals: accept unique short approval-id prefixes on `plugin.approval.resolve`, matching exec approvals and restoring `/approve` fallback flows on chat approval surfaces. Thanks @vincentkoc.
- Telegram/cron topics: route announce target parsing through the Telegram extension seam and carry explicit `delivery.threadId` through cron delivery resolution, so legacy `group:` routes and topic-targeted cron sends keep their forum topic destination. (#58489) Thanks @cwmine.
- Mistral: normalize OpenAI-compatible request flags so official Mistral API runs no longer fail with remaining `422 status code (no body)` chat errors.
- Control UI/config: keep sensitive raw config hidden by default, replace the blank blocked editor with an explicit reveal-to-edit state, and restore raw JSON editing without auto-exposing secrets. Fixes #55322.
- CLI/zsh: defer `compdef` registration until `compinit` is available so zsh completion loads cleanly with plugin managers and manual setups. (#56555)
@@ -162,8 +493,6 @@ Docs: https://docs.openclaw.ai
- iMessage: stop leaking inline `[[reply_to:...]]` tags into delivered text by sending `reply_to` as RPC metadata and stripping stray directive tags from outbound messages. (#39512) Thanks @mvanhorn.
- CLI/plugins: make routed commands use the same auto-enabled bundled-channel snapshot as gateway startup, so configured bundled channels like Slack load without requiring a prior config rewrite. (#54809) Thanks @neeravmakwana.
- CLI/message send: write manual `openclaw message send` deliveries into the resolved agent session transcript again by always threading the default CLI agent through outbound mirroring. (#54187) Thanks @KevInTheCloud5617.
- Agents/live switch: stop transient cron and subagent model overrides from being misread as persisted live-session switches, so isolated runs no longer fail with `LiveSessionModelSwitchError`. Thanks @vincentkoc.
- Gateway/startup: keep configured primary-model warmup on the static model path so container boots do not snapshot-load provider runtime graphs just to validate a configured model. Thanks @vincentkoc.
- CLI/onboarding: show the Kimi Code API key option again in the Moonshot setup menu so the interactive picker includes all Kimi setup paths together. Fixes #54412 Thanks @sparkyrider
- Agents/status: use provider-aware context window lookup for fresh Anthropic 4.6 model overrides so `/status` shows the correct 1.0m window instead of an underreported shared-cache minimum. (#54796) Thanks @neeravmakwana.
- OpenAI/WebSocket: preserve reasoning replay metadata and tool-call item ids on WebSocket tool turns, and start a fresh response chain when full-context resend is required. (#53856) Thanks @xujingchen1996.
@@ -185,8 +514,6 @@ Docs: https://docs.openclaw.ai
- Message tool/buttons: keep the shared `buttons` schema optional in merged tool definitions so plain `action=send` calls stop failing validation when no buttons are provided. (#54418) Thanks @adzendo.
- Agents/openai-compatible tool calls: deduplicate repeated tool call ids across live assistant messages and replayed history so OpenAI-compatible backends no longer reject duplicate `tool_call_id` values with HTTP 400. (#40996) Thanks @xaeon2026.
- Models/openai-completions: default non-native OpenAI-compatible providers to omit tool-definition `strict` fields unless users explicitly opt back in, so tool calling keeps working on providers that reject that option. (#45497) Thanks @sahancava.
- Subagents/announcements: preserve the requester agent id for inline deterministic tool spawns so named agents without channel bindings can still announce completions through the correct owner session. (#55437) Thanks @kAIborg24.
- Telegram/Anthropic streaming: replace raw invalid stream-order provider errors with a safe retry message so internal `message_start/message_stop` failures do not leak into chats. (#55408) Thanks @imydal.
- Plugins/context engines: retry strict legacy `assemble()` calls without the new `prompt` field when older engines reject it, preserving prompt-aware retrieval compatibility for pre-prompt plugins. (#50848) thanks @danhdoan.
- CLI/update status: explicitly say `up to date` when the local version already matches npm latest, while keeping the availability logic unchanged. (#51409) Thanks @dongzhenye.
- Daemon/Linux: stop flagging non-gateway systemd services as duplicate gateways just because their unit files mention OpenClaw, reducing false-positive doctor/log noise. (#45328) Thanks @gregretkowski.
@@ -195,7 +522,6 @@ Docs: https://docs.openclaw.ai
- Control UI/Skills: open skill detail dialogs with the browser modal lifecycle so clicking a skill row keeps the panel centered instead of rendering it off-screen at the bottom of the page.
- Matrix/replies: include quoted poll question/options in inbound reply context so the agent sees the original poll content when users reply to Matrix poll messages. (#55056) Thanks @alberthild.
- Matrix/plugins: keep plugin bootstrap from crashing when built runtime mixes bare and deep `matrix-js-sdk` entrypoints, so unrelated channels do not get taken down during plugin load. (#56273) Thanks @aquaright1.
- Agents/sandbox: honor `tools.sandbox.tools.alsoAllow`, let explicit sandbox re-allows remove matching built-in default-deny tools, and keep sandbox explain/error guidance aligned with the effective sandbox tool policy. (#54492) Thanks @ngutman.
- Agents/sandbox: make blocked-tool guidance glob-aware again, redact/sanitize session-specific explain hints for safer copy-paste, and avoid leaking control-character session keys in those hints. (#54684) Thanks @ngutman.
- Agents/compaction: trigger timeout recovery compaction before retrying high-context LLM timeouts so embedded runs stop repeating oversized requests. (#46417) thanks @joeykrug.
- Agents/compaction: reconcile `sessions.json.compactionCount` after a late embedded auto-compaction success so persisted session counts catch up once the handler reports completion. (#45493) Thanks @jackal092927.
@@ -229,7 +555,6 @@ Docs: https://docs.openclaw.ai
- Plugins/diffs: load bundled Pierre themes without JSON module imports so diff rendering keeps working on newer Node builds. (#45869) thanks @NickHood1984.
- Plugins/uninstall: remove owned `channels.<id>` config when uninstalling channel plugins, and keep the uninstall preview aligned with explicit channel ownership so built-in channels and shared keys stay intact. (#35915) Thanks @wbxl2000.
- Plugins/Matrix: prefer explicit DM signals when choosing outbound direct rooms and routing unmapped verification summaries, so strict 2-person fallback rooms do not outrank the real DM. (#56076) thanks @gumadeiras
- Slack/interactive replies: resolve Slack Block Kit reply delivery from both authored `channelData.slack.blocks` payloads and directive-generated interactive replies, and keep Slack button styles mapped onto valid Block Kit button rendering so interactive replies stop dropping on Slack-specific delivery paths. Thanks @vincentkoc.
- Plugins/Matrix: resolve env-backed `accessToken` and `password` SecretRefs against the active Matrix config env path during startup, and officially accept SecretRef `accessToken` config values. (#54980) thanks @kakahu2015.
- Microsoft Teams/proactive DMs: prefer the freshest personal conversation reference for `user:<aadObjectId>` sends when multiple stored references exist, so replies stop targeting stale DM threads. (#54702) Thanks @gumclaw.
- Gateway/plugins: reuse the session workspace when building HTTP `/tools/invoke` tool lists and harden tool construction to infer the session agent workspace by default, so workspace plugins do not re-register on repeated HTTP tool calls. (#56101) thanks @neeravmakwana
@@ -242,18 +567,14 @@ 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.
- Memory/builtin: keep memory-file indexing active in FTS-only mode (no embedding provider) so forced reindexes no longer swap in an empty index and wipe existing memory chunks. (#42714) Thanks @asamimei.
- CLI/status: detect node-only hosts in `openclaw status` and `openclaw status --all`, show the configured remote gateway target instead of a false local `ECONNREFUSED`, and suppress contradictory local-gateway diagnosis output.
- Gateway/SQLite transient handling: keep unhandled `SQLITE_CANTOPEN`, `SQLITE_BUSY`, `SQLITE_LOCKED`, and `SQLITE_IOERR` failures non-fatal in the global rejection handler so macOS LaunchAgent restarts do not enter a crash-throttle loop. (#57018)
- Control UI/gateway: reconnect the browser client when gateway event sequence gaps are detected, so stale non-chat state recovers automatically instead of only telling the user to refresh. (#23912) thanks @Olshansk.
- ClawDock/docs: move the helper scripts to `scripts/clawdock`, publish ClawDock as a first-class docs page on the docs site, and document reinstalling local helper copies from the new raw GitHub path. (#23912) thanks @Olshansk.
- Control UI/gateway: clear queued browser connect timeouts on client stop so aborted or replaced gateway clients do not send delayed connect requests after shutdown. (#57338) thanks @gumadeiras.
- Mattermost: detect stale websocket sessions after bot disable/enable cycles by polling the bot account `update_at` and forcing a reconnect when it changes. (#53604) Thanks @Qinsam.
## 2026.3.24
### Breaking
- Providers/Qwen: remove the deprecated `qwen-portal-auth` OAuth integration for `portal.qwen.ai`; migrate to Model Studio with `openclaw onboard --auth-choice modelstudio-api-key`. (#52709) Thanks @pomelo-nwu.
- Config/Doctor: drop automatic config migrations older than two months; very old legacy keys now fail validation instead of being rewritten on load or by `openclaw doctor`.
## 2026.3.24
### Changes
- Gateway/OpenAI compatibility: add `/v1/models` and `/v1/embeddings`, and forward explicit model overrides through `/v1/chat/completions` and `/v1/responses` for broader client and RAG compatibility. Thanks @vincentkoc.
@@ -274,8 +595,6 @@ Docs: https://docs.openclaw.ai
- Control UI/agents: add a "Not set" placeholder to the default agent model selector dropdown. (#53411) Thanks @BunsDev.
- Runtime/install: lower the supported Node 22 floor to `22.14+` while continuing to recommend Node 24, so npm installs and self-updates do not strand Node 22.14 users on older releases.
- CLI/update: preflight the target npm package `engines.node` before `openclaw update` runs a global package install, so outdated Node runtimes fail with a clear upgrade message instead of attempting an unsupported latest release.
- Agents/BTW: force `/btw` side questions to disable provider reasoning so Anthropic adaptive-thinking sessions stop failing with `No BTW response generated`. Fixes #55376. Thanks @Catteres and @vincentkoc.
- Auth profiles/OAuth: refresh runtime auth snapshots when saving rotated credentials so OAuth providers do not reuse consumed refresh tokens after the first token rotation. Fixes #55389. Thanks @sam26880 and @vincentkoc.
### Fixes
@@ -303,60 +622,17 @@ Docs: https://docs.openclaw.ai
- Security/path resolution: prefer non-user-writable absolute helper binaries for OpenClaw CLI, ffmpeg, and OpenSSL resolution so PATH hijacks cannot replace trusted helpers with attacker-controlled executables.
- Security/gateway command scopes: require `operator.admin` before Telegram target writeback and Talk Voice `/voice set` config writes persist through gateway message flows.
- Security/OpenShell mirror: exclude workspace `hooks/` from mirror sync so untrusted sandbox files cannot become trusted host hooks on gateway startup.
- Exec approvals/channels: unify Discord and Telegram exec approval runtime handling, move approval buttons onto the shared interactive reply model, and fix Telegram approval buttons and typed `/approve` commands so configured approvers can resolve requests reliably again. (#57516) Thanks @scoootscooob.
## 2026.3.24-beta.2
### Breaking
### Changes
### Fixes
- Outbound media/local files: align outbound media access with the configured fs policy so host-local files and inbound-media paths keep sending when `workspaceOnly` is off, while strict workspace-only agents remain sandboxed.
- Runtime/install: lower the supported Node 22 floor to `22.14+` while continuing to recommend Node 24, so npm installs and self-updates do not strand Node 22.14 users on older releases.
- CLI/update: preflight the target npm package `engines.node` before `openclaw update` runs a global package install, so outdated Node runtimes fail with a clear upgrade message instead of attempting an unsupported latest release.
- Tests/security audit: isolate audit-test home and personal skill resolution so local `~/.agents/skills` installs no longer make maintainer prep runs fail nondeterministically. (#54473) thanks @huntharo
## 2026.3.24-beta.1
### Breaking
### Changes
- Gateway/OpenAI compatibility: add `/v1/models` and `/v1/embeddings`, and forward explicit model overrides through `/v1/chat/completions` and `/v1/responses` for broader client and RAG compatibility. Thanks @vincentkoc.
- Agents/tools: make `/tools` show the tools the current agent can actually use right now, add a compact default view with an optional detailed mode, and add a live "Available Right Now" section in the Control UI so it is easier to see what will work before you ask.
- Microsoft Teams: migrate to the official Teams SDK and add AI-agent UX best practices including streaming 1:1 replies, welcome cards with prompt starters, feedback/reflection, informative status updates, typing indicators, and native AI labeling. (#51808)
- Microsoft Teams: add message edit and delete support for sent messages, including in-thread fallbacks when no explicit target is provided. (#49925)
- Skills/install metadata: add one-click install recipes to bundled skills (coding-agent, gh-issues, openai-whisper-api, session-logs, tmux, trello, weather) so the CLI and Control UI can offer dependency installation when requirements are missing. (#53411) Thanks @BunsDev.
- Control UI/skills: add status-filter tabs (All / Ready / Needs Setup / Disabled) with counts, replace inline skill cards with a click-to-detail dialog showing requirements, toggle switch, install action, API key entry, source metadata, and homepage link. (#53411) Thanks @BunsDev.
- Slack/interactive replies: restore rich reply parity for direct deliveries, auto-render simple trailing `Options:` lines as buttons/selects, improve Slack interactive setup defaults, and isolate reply controls from plugin interactive handlers. (#53389) Thanks @vincentkoc.
- CLI/containers: add `--container` and `OPENCLAW_CONTAINER` to run `openclaw` commands inside a running Docker or Podman OpenClaw container. (#52651) Thanks @sallyom.
- Discord/auto threads: add optional `autoThreadName: "generated"` naming so new auto-created threads can be renamed asynchronously with concise LLM-generated titles while keeping the existing message-based naming as the default. (#43366) Thanks @davidguttman.
- Plugins/hooks: add `before_dispatch` with canonical inbound metadata and route handled replies through the normal final-delivery path, preserving TTS and routed delivery semantics. (#50444) Thanks @gfzhx.
- Control UI/agents: convert agent workspace file rows to expandable `<details>` with lazy-loaded inline markdown preview, and add comprehensive `.sidebar-markdown` styles for headings, lists, code blocks, tables, blockquotes, and details/summary elements. (#53411) Thanks @BunsDev.
- Control UI/markdown preview: restyle the agent workspace file preview dialog with a frosted backdrop, sized panel, and styled header, and integrate `@create-markdown/preview` v2 system theme for rich markdown rendering (headings, tables, code blocks, callouts, blockquotes) that auto-adapts to the app's light/dark design tokens. (#53411) Thanks @BunsDev.
- macOS app/config: replace horizontal pill-based subsection navigation with a collapsible tree sidebar using disclosure chevrons and indented subsection rows. (#53411) Thanks @BunsDev.
- CLI/skills: soften missing-requirements label from "missing" to "needs setup" and surface API key setup guidance (where to get a key, CLI save command, storage path) in `openclaw skills info` output. (#53411) Thanks @BunsDev.
- macOS app/skills: add "Get your key" homepage link and storage-path hint to the API key editor dialog, and show the config path in save confirmation messages. (#53411) Thanks @BunsDev.
- Control UI/agents: add a "Not set" placeholder to the default agent model selector dropdown. (#53411) Thanks @BunsDev.
### Fixes
- Security/sandbox media dispatch: close the `mediaUrl`/`fileUrl` alias bypass so outbound tool and message actions cannot escape media-root restrictions. (#54034)
- Gateway/restart sentinel: wake the interrupted agent session via heartbeat after restart instead of only sending a best-effort restart note, retry outbound delivery once on transient failure, and preserve explicit thread/topic routing through the wake path so replies land in the correct Telegram topic or Slack thread. (#53940) Thanks @VACInc.
- Docker/setup: avoid the pre-start `openclaw-cli` shared-network namespace loop by routing setup-time onboard/config writes through `openclaw-gateway`, so fresh Docker installs stop failing before the gateway comes up. (#53385) Thanks @amsminn.
- Gateway/channels: keep channel startup sequential while isolating per-channel boot failures, so one broken channel no longer blocks later channels from starting. (#54215) Thanks @JonathanJing.
- Embedded runs/secrets: stop unresolved `SecretRef` config from crashing embedded agent runs by falling back to the resolved runtime snapshot when needed. Fixes #45838.
- WhatsApp/groups: track recent gateway-sent message IDs and suppress only matching group echoes, preserving owner `/status`, `/new`, and `/activation` commands from linked-account `fromMe` traffic. (#53624) Thanks @w-sss.
- WhatsApp/reply-to-bot detection: restore implicit group reply detection by unwrapping `botInvokeMessage` payloads and reading `selfLid` from `creds.json`, so reply-based mentions reach the bot again in linked-account group chats.
- Telegram/forum topics: recover `#General` topic `1` routing when Telegram omits forum metadata, including native commands, interactive callbacks, inbound message context, and fallback error replies. (#53699) thanks @huntharo
- Discord/gateway supervision: centralize gateway error handling behind a lifetime-owned supervisor so early, active, and late-teardown Carbon gateway errors stay classified consistently and stop surfacing as process-killing teardown crashes.
- Discord/timeouts: send a visible timeout reply when the inbound Discord worker times out before a final reply starts, including created auto-thread targets and queued-run ordering. (#53823) Thanks @Kimbo7870.
- ACP/direct chats: always deliver a terminal ACP result when final TTS does not yield audio, even if block text already streamed earlier, and skip redundant empty-text final synthesis. (#53692) Thanks @w-sss.
- Telegram/outbound errors: preserve actionable 403 membership/block/kick details and treat `bot not a member` as a permanent delivery failure so Telegram sends stop retrying doomed chats. (#53635) Thanks @w-sss.
- Telegram/photos: preflight Telegram photo dimension and aspect-ratio rules, and fall back to document sends when image metadata is invalid or unavailable so photo uploads stop failing with `PHOTO_INVALID_DIMENSIONS`. (#52545) Thanks @hnshah.
- Slack/runtime defaults: trim Slack DM reply overhead, restore Codex auto transport, and tighten Slack/web-search runtime defaults around DM preview threading, cache scoping, warning dedupe, and explicit web-search opt-in. (#53957) Thanks @vincentkoc.
- Doctor/image generation: seed migrated legacy Nano Banana Google provider config with the `/v1beta` API root and an empty model list so `openclaw doctor --fix` completes and the migrated native Google image path keeps hitting the correct endpoint. (#53757) Thanks @mahopan.
- Models/google: normalize bare Google Generative AI API roots for custom provider names, and keep built-in Google model-id rewrites working when `api` is declared only on individual models, so custom Google lanes and older configs stop missing `/v1beta` or preview-id normalization. (#44969) Thanks @Kathie-yu.
- Feishu/startup: treat unresolved `SecretRef` app credentials as not configured during account resolution so CLI startup and read-only Feishu config surfaces stop crashing before runtime-backed secret resolution is available. (#53675) Thanks @hpt.
@@ -390,12 +666,9 @@ Docs: https://docs.openclaw.ai
- Security/session policy: require sender ownership for `/send` policy changes so command-authorized non-owners cannot rewrite owner-only session delivery policy.
- Security/bash stop: route `/bash stop` through the hardened process-tree killer so invalid or attacker-influenced SIGKILL targets cannot escape the intended bash-session scope.
- Security/installer: hide staged project `.npmrc` files during skill and package installs so npm registry and git settings inside the stage directory cannot hijack trusted installs.
- Agents/tool-call repair: recover malformed Kimi/OpenRouter tool-call argument streams when provider preambles appear before JSON payloads, and fail closed on non-tool leading text so fragment strings do not leak into filesystem path arguments during sub-agent runs. (#56560) Thanks @Originalwhite.
## 2026.3.23
### Breaking
### Changes
- ModelStudio/Qwen: add standard (pay-as-you-go) DashScope endpoints for China and global Qwen API keys alongside the existing Coding Plan endpoints, and relabel the provider group to `Qwen (Alibaba Cloud Model Studio)`. (#43878)
@@ -573,9 +846,7 @@ Docs: https://docs.openclaw.ai
- CLI/auth choice: lazy-load plugin/provider fallback resolution so mapped auth choices stay on the static path and only unknown choices pay the heavy provider load. (#47495) Thanks @vincentkoc.
- Gateway/Discord startup: load only configured channel plugins during gateway boot, and lazy-load Discord provider/session runtime setup so startup stops importing unrelated providers and trims cold-start delay. Thanks @vincentkoc.
- Agents/inbound: lazy-load media and link understanding for plain-text turns and cache synced auth stores by auth-file state so ordinary inbound replies avoid unnecessary startup churn. Thanks @vincentkoc.
- Agents/openai-compatible tool calls: deduplicate repeated tool call ids across live assistant messages and replayed history so OpenAI-compatible backends no longer reject duplicate `tool_call_id` values with HTTP 400. (#40996) Thanks @xaeon2026.
- Agents/openai-responses: strip `prompt_cache_key` and `prompt_cache_retention` for non-OpenAI-compatible Responses endpoints while keeping them on direct OpenAI and Azure OpenAI paths, so third-party OpenAI-compatible providers no longer reject those requests with HTTP 400. (#49877) Thanks @ShaunTsai.
- Models/openai-completions: default non-native OpenAI-compatible providers to omit tool-definition `strict` fields unless users explicitly opt back in, so tool calling keeps working on providers that reject that option. (#45497) Thanks @sahancava.
- Models/OpenRouter runtime capabilities: fetch uncatalogued OpenRouter model metadata on first use so newly added vision models keep image input instead of silently degrading to text-only, with top-level capability field fallbacks for `/api/v1/models`. (#45824) Thanks @DJjjjhao.
- Control UI/session routing: preserve established external delivery routes when webchat views or sends in externally originated sessions, so subagent completions still return to the original channel instead of the dashboard. (#47797) Thanks @brokemac79.
- Telegram/replies: set `allow_sending_without_reply` on reply-targeted sends and media-error notices so deleted parent messages no longer drop otherwise valid replies. (#52524) Thanks @moltbot886.
@@ -627,10 +898,6 @@ Docs: https://docs.openclaw.ai
- Google Chat/runtime API: thin the private runtime barrel onto the curated public SDK surface while keeping public Google Chat exports intact. (#49504) Thanks @scoootscooob.
- Onboarding/custom providers: keep Azure AI Foundry `*.services.ai.azure.com` custom endpoints on the selected compatibility path instead of forcing Responses, so chat-completions Foundry models still work after setup. Fixes #50528. (#50535) Thanks @obviyus.
- make `openclaw update status` explicitly say `up to date` when the local version already matches npm latest, while keeping the availability logic unchanged. (#51409) Thanks @dongzhenye.
- Agents/embedded transport errors: distinguish common network failures like connection refused, DNS lookup failure, and interrupted sockets from true timeouts in embedded-run user messaging and lifecycle diagnostics. (#51419) Thanks @scoootscooob.
- Security/pairing: bind iOS setup codes to the intended node profile and reject first-use bootstrap redemption that asks for broader roles or scopes. Thanks @tdjackey.
- Nostr/security: enforce inbound DM policy before decrypt, route Nostr DMs through the standard reply pipeline, and add pre-crypto rate and size guards so unknown senders cannot bypass pairing or force unbounded crypto work. Thanks @kuranikaran.
- Synology Chat/security: keep reply delivery bound to stable numeric `user_id` by default, and gate mutable username/nickname recipient lookup behind `dangerouslyAllowNameMatching` with new regression coverage. Thanks @nexrin.
- Browser/node proxy: enforce `nodeHost.browserProxy.allowProfiles` across `query.profile` and `body.profile`, block proxy-side profile create/delete when the allowlist is set, and keep the default full proxy surface when the allowlist is empty.
- Security/device pairing: harden `device.token.rotate` deny handling by keeping public failures generic while logging internal deny reasons and preserving approved-baseline enforcement. (`GHSA-7jrw-x62h-64p8`)
- Security/exec safe bins: remove `jq` from the default safe-bin allowlist and fail closed on the `jq` `env` builtin when operators explicitly opt `jq` back in, so `jq -n env` cannot dump host secrets without an explicit trust path. Thanks @gladiator9797 for reporting.
@@ -640,31 +907,15 @@ Docs: https://docs.openclaw.ai
- Security/network: harden explicit-proxy SSRF pinning by translating target-hop transport hints onto HTTPS proxy tunnels and failing closed for plain HTTP guarded fetches that cannot preserve pinned DNS.
- Security/Synology Chat: require explicit per-account webhook paths for multi-account setups by default, reject duplicate exact webhook paths fail-closed, and keep inherited-path behavior behind an explicit dangerous opt-in so shared routes can no longer collapse DM policy contexts across accounts. Thanks @tdjackey for reporting.
- Browser/remote CDP: honor strict browser SSRF policy during remote CDP reachability and `/json/version` discovery checks, redact sensitive `cdpUrl` tokens from status output, and warn when remote CDP targets private/internal hosts.
- Telegram/replies: set `allow_sending_without_reply` on reply-targeted sends and media-error notices so deleted parent messages no longer drop otherwise valid replies. (#52524) Thanks @moltbot886.
- Gateway/status: resolve env-backed `gateway.auth.*` SecretRefs before read-only probe auth checks so status no longer reports false probe failures when auth is configured through SecretRef. (#52513) Thanks @CodeForgeNet.
- Agents/exec: return plain-text failed tool output for timeouts and other non-success exec outcomes so models no longer parrot raw JSON error payloads back to users. (#52508) Thanks @martingarramon.
- CLI/startup: lazy-load channel add and root help startup paths to trim avoidable RSS and help latency on constrained hosts. (#46784) Thanks @vincentkoc.
- CLI/onboarding: import static provider definitions directly for onboarding model/config helpers so those paths no longer pull provider discovery just for built-in defaults. (#47467) Thanks @vincentkoc.
- CLI/configure: clarify fresh-setup memory-search warnings so they say semantic recall needs at least one embedding provider, and scope the initial model allowlist picker to the provider selected in configure. Thanks @vincentkoc.
- CLI/auth choice: lazy-load plugin/provider fallback resolution so mapped auth choices stay on the static path and only unknown choices pay the heavy provider load. (#47495) Thanks @vincentkoc.
- CLI: avoid loading provider discovery during startup model normalization. (#46522) Thanks @ItsAditya-xyz and @vincentkoc.
- CLI/status: keep `status --json` stdout clean by skipping plugin compatibility scans that were not rendered in the JSON payload. (#52449) Thanks @cgdusek.
- Agents/Telegram: avoid rebuilding the full model catalog on ordinary inbound replies so Telegram message handling no longer pays multi-second core startup latency before reply generation. Thanks @vincentkoc.
- Media/security: bound remote-media error-body snippets with the same streaming caps and idle timeouts as successful downloads, so malicious HTTP error responses cannot force unbounded buffering before OpenClaw throws.
- Gateway/auth: ignore spoofed loopback hops in trusted forwarding chains and block device approvals that request scopes above the caller session. (#46800) Thanks @vincentkoc.
- Gateway/auth: clear self-declared scopes for device-less trusted-proxy Control UI sessions so proxy-authenticated connects cannot claim admin or secrets scopes without a bound device identity.
- Hardening: refresh stale device pairing requests and pending metadata (#50695) Thanks @smaeljaish771 and @joshavant.
- Gateway/auth: add regression coverage that keeps device-less trusted-proxy Control UI sessions off privileged pairing approval RPCs. Thanks @vincentkoc.
- Media/Windows security: block remote-host `file://` media URLs and UNC/network paths before local filesystem resolution in core media loading and adjacent prompt/sandbox attachment seams, so the next release no longer allows structured local-media inputs to trigger outbound SMB credential handshakes on Windows. Thanks @RacerZ-fighting for reporting.
- Web tools/Exa: align the bundled Exa plugin with the current Exa API by supporting newer search types and richer `contents` options, while fixing the result-count cap to honor Exa's higher limit. Thanks @vincentkoc.
- Agents/default timeout: raise the shared default agent timeout from `600s` to `48h` so long-running ACP and agent sessions do not fail unless you configure a shorter limit.
- CLI: avoid loading provider discovery during startup model normalization. (#46522) Thanks @ItsAditya-xyz and @vincentkoc.
- Agents/Telegram: avoid rebuilding the full model catalog on ordinary inbound replies so Telegram message handling no longer pays multi-second core startup latency before reply generation. Thanks @vincentkoc.
- Agents/models: cache `models.json` readiness by config and auth-file state so embedded runner turns stop paying repeated model-catalog startup work before replies. Thanks @vincentkoc.
- Gateway/status: tolerate network interface discovery failures in status, onboarding control-UI links, and self-presence display paths so those surfaces fall back cleanly instead of crashing. (#52195) Thanks @meng-clb.
- Gateway/Linux: auto-detect nvm-managed Node TLS CA bundle needs before CLI startup and refresh installed services that are missing `NODE_EXTRA_CA_CERTS`. (#51146) Thanks @GodsBoy.
- Google auth/Node 25: patch `gaxios` to use native fetch without injecting `globalThis.window`, while translating proxy and mTLS transport settings so Google Vertex and Google Chat auth keep working on Node 25. (#47914) Thanks @pdd-cli.
- Gateway/status: resolve env-backed `gateway.auth.*` SecretRefs before read-only probe auth checks so status no longer reports false probe failures when auth is configured through SecretRef. (#52513) Thanks @CodeForgeNet.
- Gateway/plugins: pin runtime webhook routes to the gateway startup registry so channel webhooks keep working across plugin-registry churn, and make plugin auth + dispatch resolve routes from the same live HTTP-route registry. (#47902) Fixes #46924 and #47041. Thanks @steipete.
- Gateway/restart: defer externally signaled unmanaged restarts through the in-process idle drain, and preserve the restored subagent run as remap fallback during orphan recovery so resumed sessions do not duplicate work. (#47719) Thanks @joeykrug.
- Telegram/setup: seed fresh setups with `channels.telegram.groups["*"].requireMention=true` so new bots stay mention-gated in groups unless you explicitly open them up. Thanks @vincentkoc.
@@ -691,6 +942,9 @@ Docs: https://docs.openclaw.ai
- Feishu/topic threads: fetch full thread context, including prior bot replies, when starting a topic-thread session so follow-up turns in Feishu topics keep the right conversation state. (#45254) Thanks @Coobiw.
- Feishu/media: keep native image, file, audio, and video/media handling aligned across outbound sends, inbound downloads, thread replies, directory/action aliases, and capability docs so unsupported areas are explicit instead of implied. (#47968) Thanks @Takhoffman.
- Feishu/webhooks: harden signed webhook verification to use constant-time signature comparison and keep malformed short signatures fail-closed in webhook E2E coverage.
- Ollama/tool replay: deserialize stringified tool-call arguments on native and OpenAI-compatible Ollama paths while preserving unsafe integer ids across round-trips. (#52253) Thanks @Adam-Researchh.
- WhatsApp/reconnect: restore the append recency filter in the extension inbox monitor and handle protobuf `Long` timestamps correctly, so fresh post-reconnect append messages are processed while stale history sync stays suppressed. (#42588) Thanks @MonkeyLeeT.
- WhatsApp/login: wait for pending creds writes before reopening after Baileys `515` pairing restarts in both QR login and `channels login` flows, and keep the restart coverage pinned to the real wrapped error shape plus per-account creds queues. (#27910) Thanks @asyncjason.
- Telegram/message send: forward `--force-document` through the `sendPayload` path as well as `sendMedia`, so Telegram payload sends with `channelData` keep uploading images as documents instead of silently falling back to compressed photo sends. (#47119) Thanks @thepagent.
- Telegram/message chunking: preserve spaces, paragraph separators, and word boundaries when HTML overflow rechunking splits formatted replies. (#47274) Thanks @obviyus.
- Z.AI/onboarding: detect a working default model even for explicit `zai-coding-*` endpoint choices, so Coding Plan setup can keep the selected endpoint while defaulting to `glm-5` when available or `glm-4.7` as fallback. (#45969) Thanks @obviyus.
@@ -733,6 +987,7 @@ Docs: https://docs.openclaw.ai
- Exec: harden host env override handling across gateway and node (#51207) Thanks @gladiator9797 and @joshavant.
- Voice Call: enforce spoken-output contract and fix stream TTS silence regression (#51500) Thanks @joshavant.
- xAI/models: rename the bundled Grok 4.20 catalog entries to the GA IDs and normalize saved deprecated beta IDs at runtime so existing configs and sessions keep resolving. (#50772) thanks @Jaaneek
- WhatsApp/outbound media: fix HTML, XML, and CSS files being silently dropped on outbound send by adding missing MIME entries and falling back to `application/octet-stream` for unknown media types. (#51562) Thanks @bobbyt74
- Agents/bootstrap warnings: move bootstrap truncation warnings out of the system prompt and into the per-turn prompt body so prompt-cache reuse stays stable when truncation warnings appear or disappear. (#48753) Thanks @scoootscooob and @obviyus.
- Telegram/DM topic session keys: route named-account DM topics through the same per-account base session key across inbound messages, native commands, and session-state lookups so `/status` and thread recovery stop creating phantom `agent:main:main:thread:...` sessions. (#48204) Thanks @vincentkoc.
- ACP/configured bindings: reinitialize configured ACP sessions that are stuck in `error` state instead of reusing the failed runtime.
@@ -758,7 +1013,6 @@ Docs: https://docs.openclaw.ai
- Discord/ACP: forward worker abort signals into ACP turns so timed-out Discord jobs cancel the running turn instead of silently leaving the bound ACP session working in the background.
- ACP/Codex session replay: preserve hidden assistant thinking when loading or rebinding existing ACP sessions so stored thought chunks do not replay into visible assistant text. Thanks @vincentkoc.
- Gateway/commands: keep internal `chat.send` slash-command UX while requiring `operator.admin` before internal callers can persist `/exec` defaults or mutate `phone-control` node policy through `/phone arm|disarm`.
- Plugins/Matrix: move bundled plugin `KeyedAsyncQueue` imports onto the stable `plugin-sdk/core` surface so Matrix Docker/runtime builds do not depend on the brittle keyed-async-queue subpath. Thanks @ecohash-co and @vincentkoc.
- Plugins/context engines: enforce owner-aware context-engine registration on both loader and public SDK paths so plugins cannot spoof privileged ownership, claim the core `legacy` engine id, or overwrite an existing engine id through direct SDK imports. (#47595) Thanks @vincentkoc.
- Plugins/bundler TDZ: fix `RESERVED_COMMANDS` temporal dead zone error that prevented device-pair, phone-control, and talk-voice plugins from registering when the bundler placed the commands module after call sites in the same output chunk. Thanks @BunsDev.
- Plugins/imports: fix stale googlechat runtime-api import paths and signal SDK circular re-exports broken by recent plugin-sdk refactors. Thanks @BunsDev.
@@ -770,7 +1024,6 @@ Docs: https://docs.openclaw.ai
- Plugins/subagents: preserve gateway-owned plugin subagent access across runtime, tool, and embedded-runner load paths so gateway plugin tools and context engines can still spawn and manage subagents after the loader cache split. (#46648) Thanks @jalehman.
- Plugins/subagents: forward per-run provider and model overrides through gateway plugin subagent dispatch so plugin-launched agent delegations honor explicit model selection again. (#48277) Thanks @jalehman.
- Tests/OpenAI Codex auth: align login expectations with the default `gpt-5.4` model so CI coverage stays consistent with the current OpenAI Codex default. (#44367) Thanks @jrrcdev.
- Plugins/Matrix TTS: send auto-TTS replies as native Matrix voice bubbles instead of generic audio attachments. (#37080) thanks @Matthew19990919.
- Plugins/discovery: distinguish missing package entry files from package-path escape violations so startup skips absent plugin entry paths without raising false security diagnostics. (#52491) Thanks @hclsys.
- Plugins/Matrix: accept shared send-tool media aliases (`mediaUrl`, `filePath`, `path`) and preserve `asVoice` / `audioAsVoice` through Matrix action dispatch so media-only sends and voice-message intents reach the plugin send layer correctly. Thanks @psacc and @vincentkoc.
- Plugins/runtime-api: pin extension runtime-api export surfaces with explicit guardrail coverage so future surface creep becomes a deliberate diff. Thanks @vincentkoc.
@@ -778,14 +1031,11 @@ Docs: https://docs.openclaw.ai
- Plugins/update: let `openclaw plugins update <npm-spec>` target tracked npm installs by dist-tag or exact version, and preserve the recorded npm spec for later id-based updates. (#49998) Thanks @huntharo.
- Tests/CLI: reduce command-secret gateway test import pressure while keeping the real protocol payload validator in place, so the isolated lane no longer carries the heavier runtime-web and message-channel graphs. (#50663) Thanks @huntharo.
- Gateway/plugins: share plugin interactive callback routing and plugin bind approval state across duplicate module graphs so Telegram Codex picker buttons and plugin bind approvals no longer fall through to normal inbound message routing. (#50722) Thanks @huntharo.
- Plugins/context engines: retry strict legacy `assemble()` calls without the new `prompt` field when older engines reject it, preserving prompt-aware retrieval compatibility for pre-prompt plugins. (#50848) thanks @danhdoan.
- Plugins/runtime state: share plugin-facing infra singleton state across duplicate module graphs and keep session-binding adapter ownership stable until the active owner unregisters. (#50725) thanks @huntharo.
- Discord/pickers: keep `/codex_resume --browse-projects` picker callbacks alive in Discord by sharing component callback state across duplicate module graphs, preserving callback fallbacks, and acknowledging matched plugin interactions before dispatch. (#51260) Thanks @huntharo.
- Memory/core tools: register `memory_search` and `memory_get` independently so one unavailable memory tool no longer suppresses the other in new sessions. (#50198) Thanks @artwalker.
- Telegram/Mattermost message tool: keep plugin button schemas optional in isolated and cron sessions so plain sends do not fail validation when no current channel is active. (#52589) Thanks @tylerliu612.
- Release/npm publish: fail the npm release check when `dist/control-ui/index.html` is missing from the packed tarball, so broken Control UI asset releases are blocked before publish. Fixes #52808. (#52852) Thanks @kevinheinrichs.
- Slack/embedded delivery: suppress transcript-only `delivery-mirror` assistant messages before embedded re-delivery and raise the default Slack chunk fallback so messages just over 4000 characters stay in a single post. (#45489) Thanks @theo674.
- Slack/embedded delivery: suppress transcript-only `delivery-mirror` assistant messages before embedded re-delivery and raise the default Slack chunk fallback so messages just over 4000 characters stay in a single post. (#45489) Thanks @theo674.
### Fixes
@@ -847,7 +1097,6 @@ Docs: https://docs.openclaw.ai
- Security/exec approvals: unwrap `env` dispatch wrappers inside shell-segment allowlist resolution on macOS so `env FOO=bar /path/to/bin` resolves against the effective executable instead of the wrapper token.
- Security/exec approvals: treat backslash-newline as shell line continuation during macOS shell-chain parsing so line-continued `$(` substitutions fail closed instead of slipping past command-substitution checks.
- Security/exec approvals: bind macOS skill auto-allow trust to both executable name and resolved path so same-basename binaries no longer inherit trust from unrelated skill bins.
- Build/plugin-sdk bundling: bundle plugin-sdk subpath entries in one shared build pass so published packages stop duplicating shared chunks and avoid the recent plugin-sdk memory blow-up. (#45426) Thanks @TarasShyn.
- Cron/isolated sessions: route nested cron-triggered embedded runner work onto the nested lane so isolated cron jobs no longer deadlock when compaction or other queued inner work runs. Thanks @vincentkoc.
- Agents/OpenAI-compatible compat overrides: respect explicit user `models[].compat` opt-ins for non-native `openai-completions` endpoints so usage-in-streaming capability overrides no longer get forced off when the endpoint actually supports them. (#44432) Thanks @cheapestinference.
- Agents/Azure OpenAI startup prompts: rephrase the built-in `/new`, `/reset`, and post-compaction startup instruction so Azure OpenAI deployments no longer hit HTTP 400 false positives from the content filter. (#43403) Thanks @xingsy97.
@@ -943,13 +1192,8 @@ Docs: https://docs.openclaw.ai
- Agents/failover: classify z.ai `network_error` stop reasons as retryable timeouts so provider connectivity failures trigger fallback instead of surfacing raw unhandled-stop-reason errors. (#43884) Thanks @hougangdev.
- Config/Anthropic startup: inline Anthropic alias normalization during config load so gateway startup no longer crashes on dated Anthropic model refs like `anthropic/claude-sonnet-4-20250514`. (#45520) Thanks @BunsDev.
- Memory/session sync: add mode-aware post-compaction session reindexing with `agents.defaults.compaction.postIndexSync` plus `agents.defaults.memorySearch.sync.sessions.postCompactionForce`, so compacted session memory can refresh immediately without forcing every deployment into synchronous reindexing. (#25561) thanks @rodrigouroz.
- Telegram/model picker: make inline model button selections persist the chosen session model correctly, clear overrides when selecting the configured default, and include effective fallback models in `/models` button validation. (#40105) Thanks @avirweb.
- Telegram/native command sync: suppress expected `BOT_COMMANDS_TOO_MUCH` retry error noise, add a final fallback summary log, and document the difference between command-menu overflow and real Telegram network failures.
- Mattermost/reply media delivery: pass agent-scoped `mediaLocalRoots` through shared reply delivery so allowed local files upload correctly from button, slash-command, and model-picker replies. (#44021) Thanks @LyleLiu666.
- Plugins/env-scoped roots: fix plugin discovery/load caches and provenance tracking so same-process `HOME`/`OPENCLAW_HOME` changes no longer reuse stale plugin state or misreport `~/...` plugins as untracked. (#44046) thanks @gumadeiras.
- Gateway/session discovery: discover disk-only and retired ACP session stores under custom templated `session.store` roots so ACP reconciliation, session-id/session-label targeting, and run-id fallback keep working after restart. (#44176) thanks @gumadeiras.
- Browser/existing-session: stop reporting fake CDP ports/URLs for live attached Chrome sessions, render `transport: chrome-mcp` in CLI/status output instead of `port: 0`, and keep timeout diagnostics transport-aware when no direct CDP URL exists.
- Models/OpenRouter native ids: canonicalize native OpenRouter model keys across config writes, runtime lookups, fallback management, and `models list --plain`, and migrate legacy duplicated `openrouter/openrouter/...` config entries forward on write.
- Feishu/event dedupe: keep early duplicate suppression aligned with the shared Feishu message-id contract and release the pre-queue dedupe marker after failed dispatch so retried events can recover instead of being dropped until the short TTL expires. (#43762) Thanks @yunweibang.
- Gateway/hooks: bucket hook auth failures by forwarded client IP behind trusted proxies and warn when `hooks.allowedAgentIds` leaves hook routing unrestricted.
- Agents/compaction: skip the post-compaction `cache-ttl` marker write when a compaction completed in the same attempt, preventing the next turn from immediately triggering a second tiny compaction. (#28548) thanks @MoerAI.
@@ -964,8 +1208,6 @@ Docs: https://docs.openclaw.ai
- Docs/Brave pricing: escape literal dollar signs in Brave Search cost text so the docs render the free credit and per-request pricing correctly. (#44989) Thanks @keelanfh.
- Feishu/file uploads: preserve literal UTF-8 filenames in `im.file.create` so Chinese and other non-ASCII filenames no longer appear percent-encoded in chat. (#34262) Thanks @fabiaodemianyang and @KangShuaiFu.
- Agents/compaction safeguard: trim large kept `toolResult` payloads consistently for budgeting, pruning, and identifier seeding, then restore preserved payloads after prune so oversized safeguard summaries stay stable. (#44133) thanks @SayrWolfridge.
- Agents/compaction: compare post-compaction token sanity checks against full-session pre-compaction totals and skip the check when token estimation fails, so sessions with large bootstrap context keep real token counts instead of falling back to unknown. (#28347) thanks @efe-arv.
- Discord/gateway startup: treat plain-text and transient `/gateway/bot` metadata fetch failures as transient startup errors so Discord gateway boot no longer crashes on unhandled rejections. (#44397) Thanks @jalehman.
- Agents/Ollama overflow: rewrite Ollama `prompt too long` API payloads through the normal context-overflow sanitizer so embedded sessions keep the friendly overflow copy and auto-compaction trigger. (#34019) thanks @lishuaigit.
- Control UI/auth: restore one-time legacy `?token=` imports for shared Control UI links while keeping `#token=` preferred, and carry pending query tokens through gateway URL confirmation so compatibility links still authenticate after confirmation. (#43979) Thanks @stim64045-spec.
@@ -1009,7 +1251,6 @@ Docs: https://docs.openclaw.ai
- macOS/LaunchAgent install: tighten LaunchAgent directory and plist permissions during install so launchd bootstrap does not fail when the target home path or generated plist inherited group/world-writable modes.
- Discord/reply chunking: resolve the effective `maxLinesPerMessage` config across live reply paths and preserve `chunkMode` in the fast send path so long Discord replies no longer split unexpectedly at the default 17-line limit. (#40133) thanks @rbutera.
- Feishu/local image auto-convert: pass `mediaLocalRoots` through the `sendText` local-image shim so allowed local image paths upload as Feishu images again instead of falling back to raw path text. (#40623) Thanks @ayanesakura.
- Models/Kimi Coding: send `anthropic-messages` tools in native Anthropic format again so `kimi-coding` stops degrading tool calls into XML/plain-text pseudo invocations instead of real `tool_use` blocks. (#38669, #39907, #40552) Thanks @opriz.
- Telegram/outbound HTML sends: chunk long HTML-mode messages, preserve plain-text fallback and silent-delivery params across retries, and cut over to plain text when HTML chunk planning cannot safely preserve the full message. (#42240) thanks @obviyus.
- Telegram/final preview delivery: split active preview lifecycle from cleanup retention so missing archived preview edits avoid duplicate fallback sends without clearing the live preview or blocking later in-place finalization. (#41662) thanks @hougangdev.
- Telegram/final preview delivery followup: keep ambiguous missing-`message_id` finals only when a preview was already visible, while first-preview/no-id cases still fall back so Telegram users do not lose the final reply. (#41932) thanks @hougangdev.
@@ -1087,12 +1328,7 @@ Docs: https://docs.openclaw.ai
- Gateway/node pending drain followup: keep `hasMore` true when the deferred baseline status item still needs delivery, and avoid allocating empty pending-work state for drain-only nodes with no queued work. (#41429) Thanks @mbelinky.
- Protocol/Swift model sync: regenerate pending node work Swift bindings after the landed `node.pending.*` schema additions so generated protocol artifacts are consistent again. (#41477) Thanks @mbelinky.
- Cron/subagent followup: do not misclassify empty or `NO_REPLY` cron responses as interim acknowledgements that need a rerun, so deliberately silent cron jobs are no longer retried. (#41383) thanks @jackal092927.
- CLI/memory teardown: close cached memory search/index managers in the one-shot CLI shutdown path so watcher-backed memory caches no longer keep completed CLI runs alive after output finishes. (#40389) thanks @Julbarth.
- Tools/web search: treat Brave `llm-context` grounding snippets as plain strings so `web_search` no longer returns empty snippet arrays in LLM Context mode. (#41387) thanks @zheliu2.
- ACP/run-mode delivery: restore inline delivery for one-shot ACP run spawns from non-subagent (main) requester sessions so completions reach the originating Discord/Telegram/etc. channel again. Subagent orchestrators continue to use stream-to-parent when an active heartbeat relay route is available. (#52426) Thanks @distractedCoding.
- Telegram/exec approvals: reject `/approve` commands aimed at other bots, keep deterministic approval prompts visible when tool-result delivery fails, and stop resolved exact IDs from matching other pending approvals by prefix. (#37233) Thanks @huntharo.
- Control UI/Sessions: restore single-column session table collapse on narrow viewport or container widths by moving the responsive table override next to the base grid rule and enabling inline-size container queries. (#12175) Thanks @benjipeng.
- Telegram/final preview delivery: split active preview lifecycle from cleanup retention so missing archived preview edits avoid duplicate fallback sends without clearing the live preview or blocking later in-place finalization. (#41662) thanks @hougangdev.
- Cron/state errors: record `lastErrorReason` in cron job state and keep the gateway schema aligned with the full failover-reason set, including regression coverage for protocol conformance. (#14382) thanks @futuremind2026.
- Browser/Browserbase 429 handling: surface stable no-retry rate-limit guidance without buffering discarded HTTP 429 response bodies from remote browser services. (#40491) thanks @mvanhorn.
- CI/CodeQL Swift toolchain: select Xcode 26.1 before installing Swift build tools so the CodeQL Swift job uses Swift tools 6.2 on `macos-latest`. (#41787) thanks @BunsDev.
@@ -1101,10 +1337,7 @@ Docs: https://docs.openclaw.ai
- Telegram/direct delivery: bridge direct delivery sends to internal `message:sent` hooks so internal hook listeners observe successful Telegram deliveries. (#40185) Thanks @vincentkoc.
- Dependencies: refresh workspace dependencies except the pinned Carbon package, and harden ACP session-config writes against non-string SDK values so newer ACP clients fail fast instead of tripping type/runtime mismatches.
- Telegram/polling restarts: clear bounded cleanup timeout handles after `runner.stop()` and `bot.stop()` settle so stall recovery no longer leaves stray 15-second timers behind on clean shutdown. (#43188) thanks @kyohwang.
- Gateway/config errors: surface up to three validation issues in top-level `config.set`, `config.patch`, and `config.apply` error messages while preserving structured issue details. (#42664) Thanks @huntharo.
- Hooks/plugin context parity followup: pass `trigger` and `channelId` through embedded `llm_input`, `agent_end`, and `llm_output` hook contexts so plugins receive the same agent metadata across hook phases. (#42362) Thanks @zhoulf1006.
- Status/context windows: normalize provider-qualified override cache keys so `/status` resolves the active provider's configured context window even when `models.providers` keys use mixed case or surrounding whitespace. (#36389) Thanks @haoruilee.
- ACP/main session aliases: canonicalize `main` before ACP session lookup so restarted ACP main sessions rehydrate instead of failing closed with `Session is not ACP-enabled: main`. (#43285, fixes #25692)
- Agents/embedded runner: recover canonical allowlisted tool names from malformed `toolCallId` and malformed non-blank tool-name variants before dispatch, while failing closed on ambiguous matches. (#34485) thanks @yuweuii.
- Agents/failover: classify ZenMux quota-refresh `402` responses as `rate_limit` so model fallback retries continue instead of stopping on a temporary subscription window. (#43917) thanks @bwjoke.
- Agents/failover: classify HTTP 422 malformed-request responses as `format` and recognize OpenRouter "requires more credits" billing errors so provider fallback triggers instead of surfacing raw errors. (#43823) thanks @jnMetaCode.
@@ -1197,7 +1430,6 @@ Docs: https://docs.openclaw.ai
- CLI/skills tables: keep terminal table borders aligned for wide graphemes, use full reported terminal width, and switch a few ambiguous skill icons to Terminal-safe emoji so `openclaw skills` renders more consistently in Terminal.app and iTerm. Thanks @vincentkoc.
- Memory/Gemini: normalize returned Gemini embeddings across direct query, direct batch, and async batch paths so memory search uses consistent vector handling for Gemini too. (#43409) Thanks @gumadeiras.
- Agents/failover: recognize additional serialized network errno strings plus `EHOSTDOWN` and `EPIPE` structured codes so transient transport failures trigger timeout failover more reliably. (#42830) Thanks @jnMetaCode.
- Telegram/model picker: make inline model button selections persist the chosen session model correctly, clear overrides when selecting the configured default, and include effective fallback models in `/models` button validation. (#40105) Thanks @avirweb.
- Agents/embedded runner: carry provider-observed overflow token counts into compaction so overflow retries and diagnostics use the rejected live prompt size instead of only transcript estimates. (#40357) thanks @rabsef-bicrym.
- Agents/compaction transcript updates: emit a transcript-update event immediately after successful embedded compaction so downstream listeners observe the post-compact transcript without waiting for a later write. (#25558) thanks @rodrigouroz.
- Agents/sessions_spawn: use the target agent workspace for cross-agent spawned runs instead of inheriting the caller workspace, so child sessions load the correct workspace-scoped instructions and persona files. (#40176) Thanks @moshehbenavraham.

View File

@@ -159,7 +159,10 @@ We are currently prioritizing:
- **Skills**: For skill contributions, head to [ClawHub](https://clawhub.ai/) — the community hub for OpenClaw skills.
- **Performance**: Optimizing token usage and compaction logic.
Check the [GitHub Issues](https://github.com/openclaw/openclaw/issues) for "good first issue" labels!
Check the [GitHub Issues](https://github.com/openclaw/openclaw/issues) for
["good first issue"](https://github.com/openclaw/openclaw/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22)
labels. If none are open, pick a small docs or bug issue and leave a quick comment saying
you'd like to work on it.
## Maintainers

View File

@@ -64,7 +64,7 @@ WORKDIR /app
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml .npmrc ./
COPY ui/package.json ./ui/package.json
COPY patches ./patches
COPY scripts/postinstall-bundled-plugins.mjs ./scripts/postinstall-bundled-plugins.mjs
COPY scripts/postinstall-bundled-plugins.mjs scripts/npm-runner.mjs ./scripts/
COPY --from=ext-deps /out/ ./${OPENCLAW_BUNDLED_PLUGIN_DIR}/

4
Makefile Normal file
View File

@@ -0,0 +1,4 @@
.PHONY: build
build:
pnpm build

View File

@@ -32,9 +32,50 @@ New install? Start here: [Getting started](https://docs.openclaw.ai/start/gettin
## Sponsors
| OpenAI | Vercel | Blacksmith | Convex |
| ----------------------------------------------------------------- | ----------------------------------------------------------------- | ---------------------------------------------------------------------------- | --------------------------------------------------------------------- |
| [![OpenAI](docs/assets/sponsors/openai.svg)](https://openai.com/) | [![Vercel](docs/assets/sponsors/vercel.svg)](https://vercel.com/) | [![Blacksmith](docs/assets/sponsors/blacksmith.svg)](https://blacksmith.sh/) | [![Convex](docs/assets/sponsors/convex.svg)](https://www.convex.dev/) |
<table>
<tr>
<td align="center" width="20%">
<a href="https://openai.com/">
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/openclaw/openclaw/main/docs/assets/sponsors/openai-light.svg">
<img src="https://raw.githubusercontent.com/openclaw/openclaw/main/docs/assets/sponsors/openai.svg" alt="OpenAI" height="28">
</picture>
</a>
</td>
<td align="center" width="20%">
<a href="https://www.nvidia.com/">
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/openclaw/openclaw/main/docs/assets/sponsors/nvidia.svg">
<img src="https://raw.githubusercontent.com/openclaw/openclaw/main/docs/assets/sponsors/nvidia-dark.svg" alt="NVIDIA" height="28">
</picture>
</a>
</td>
<td align="center" width="20%">
<a href="https://vercel.com/">
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/openclaw/openclaw/main/docs/assets/sponsors/vercel-light.svg">
<img src="https://raw.githubusercontent.com/openclaw/openclaw/main/docs/assets/sponsors/vercel.svg" alt="Vercel" height="24">
</picture>
</a>
</td>
<td align="center" width="20%">
<a href="https://blacksmith.sh/">
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/openclaw/openclaw/main/docs/assets/sponsors/blacksmith-light.svg">
<img src="https://raw.githubusercontent.com/openclaw/openclaw/main/docs/assets/sponsors/blacksmith.svg" alt="Blacksmith" height="28">
</picture>
</a>
</td>
<td align="center" width="20%">
<a href="https://www.convex.dev/">
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/openclaw/openclaw/main/docs/assets/sponsors/convex-light.svg">
<img src="https://raw.githubusercontent.com/openclaw/openclaw/main/docs/assets/sponsors/convex.svg" alt="Convex" height="24">
</picture>
</a>
</td>
</tr>
</table>
**Subscriptions (OAuth):**

View File

@@ -56,8 +56,12 @@ These are frequently reported but are typically closed with no code change:
- Authorized user-triggered local actions presented as privilege escalation. Example: an allowlisted/owner sender running `/export-session /absolute/path.html` to write on the host. In this trust model, authorized user actions are trusted host actions unless you demonstrate an auth/sandbox/boundary bypass.
- Reports that only show a malicious plugin executing privileged actions after a trusted operator installs/enables it.
- Reports that assume per-user multi-tenant authorization on a shared gateway host/config.
- Reports that only show quoted/replied/thread/forwarded supplemental context from non-allowlisted senders being visible to the model, without demonstrating an auth, policy, approval, or sandbox boundary bypass.
- Reports that treat the Gateway HTTP compatibility endpoints (`POST /v1/chat/completions`, `POST /v1/responses`) as if they implemented scoped operator auth (`operator.write` vs `operator.admin`). These endpoints authenticate the shared Gateway bearer secret/password and are documented full operator-access surfaces, not per-user/per-scope boundaries.
- Reports that assume `x-openclaw-scopes` can reduce or redefine shared-secret bearer auth on the OpenAI-compatible HTTP endpoints. For shared-secret auth (`gateway.auth.mode="token"` or `"password"`), those endpoints ignore narrower bearer-declared scopes and restore the full default operator scope set plus owner semantics.
- Reports that treat `POST /tools/invoke` under shared-secret bearer auth (`gateway.auth.mode="token"` or `"password"`) as a narrower per-request/per-scope authorization surface. That endpoint is designed as the same trusted-operator HTTP boundary: shared-secret bearer auth is full operator access there, narrower `x-openclaw-scopes` values do not reduce that path, and owner-only tool policy follows the shared-secret operator contract.
- Reports that only show differences in heuristic detection/parity (for example obfuscation-pattern detection on one exec path but not another, such as `node.invoke -> system.run` parity gaps) without demonstrating bypass of auth, approvals, allowlist enforcement, sandboxing, or other documented trust boundaries.
- Reports that only show an ACP tool can indirectly execute, mutate, orchestrate sessions, or reach another tool/runtime without demonstrating bypass of ACP prompt/approval, allowlist enforcement, sandboxing, or another documented trust boundary. ACP silent approval is intentionally limited to narrow readonly classes; parity-only indirect-command findings are hardening, not vulnerabilities.
- ReDoS/DoS claims that require trusted operator configuration input (for example catastrophic regex in `sessionFilter` or `logging.redactPatterns`) without a trust-boundary bypass.
- Archive/install extraction claims that require pre-existing local filesystem priming in trusted state (for example planting symlink/hardlink aliases under destination directories such as skills/tools paths) without showing an untrusted path that can create/control that primitive.
- Reports that depend on replacing or rewriting an already-approved executable path on a trusted host (same-path inode/content swap) without showing an untrusted path to perform that write.
@@ -93,7 +97,14 @@ When patching a GHSA via `gh api`, include `X-GitHub-Api-Version: 2022-11-28` (o
OpenClaw does **not** model one gateway as a multi-tenant, adversarial user boundary.
- Authenticated Gateway callers are treated as trusted operators for that gateway instance.
- The HTTP compatibility endpoints (`POST /v1/chat/completions`, `POST /v1/responses`) are in that same trusted-operator bucket. Passing Gateway bearer auth there is equivalent to operator access for that gateway; they do not implement a narrower `operator.write` vs `operator.admin` trust split.
- The HTTP compatibility endpoints (`POST /v1/chat/completions`, `POST /v1/responses`) and direct tool endpoint (`POST /tools/invoke`) are in that same trusted-operator bucket. Passing Gateway bearer auth there is equivalent to operator access for that gateway; they do not implement a narrower `operator.write` vs `operator.admin` trust split.
- Concretely, on the OpenAI-compatible HTTP surface:
- shared-secret bearer auth (`token` / `password`) authenticates possession of the gateway operator secret
- those requests receive the full default operator scope set (`operator.admin`, `operator.read`, `operator.write`, `operator.approvals`, `operator.pairing`)
- chat-turn endpoints (`/v1/chat/completions`, `/v1/responses`) also treat those shared-secret callers as owner senders for owner-only tool policy
- `POST /tools/invoke` follows that same shared-secret rule and also treats those callers as owner senders for owner-only tool policy
- narrower `x-openclaw-scopes` headers are ignored for that shared-secret path
- only identity-bearing HTTP modes (for example trusted proxy auth or `gateway.auth.mode="none"` on private ingress) honor declared per-request operator scopes
- Session identifiers (`sessionKey`, session IDs, labels) are routing controls, not per-user authorization boundaries.
- If one operator can view data from another operator on the same gateway, that is expected in this trust model.
- OpenClaw can technically run multiple gateway instances on one machine, but recommended operations are clean separation by trust boundary.
@@ -129,6 +140,7 @@ Plugins/extensions are part of OpenClaw's trusted computing base for a gateway.
- Any report whose only claim is that an operator-enabled `dangerous*`/`dangerously*` config option weakens defaults (these are explicit break-glass tradeoffs by design)
- Reports that depend on trusted operator-supplied configuration values to trigger availability impact (for example custom regex patterns). These may still be fixed as defense-in-depth hardening, but are not security-boundary bypasses.
- Reports whose only claim is heuristic/parity drift in command-risk detection (for example obfuscation-pattern checks) across exec surfaces, without a demonstrated trust-boundary bypass. These are hardening-only findings and are not vulnerabilities; triage may close them as `invalid`/`no-action` or track them separately as low/informational hardening.
- Reports whose only claim is that an ACP-exposed tool can indirectly execute commands, mutate host state, or reach another privileged tool/runtime without demonstrating a bypass of ACP prompt/approval, allowlist enforcement, sandboxing, or another documented trust boundary. These are hardening-only findings, not vulnerabilities.
- Reports whose only claim is that exec approvals do not semantically model every interpreter/runtime loader form, subcommand, flag combination, package script, or transitive module/config import. Exec approvals bind exact request context and best-effort direct local file operands; they are not a complete semantic model of everything a runtime may load.
- Exposed secrets that are third-party/user-controlled credentials (not OpenClaw-owned and not granting access to OpenClaw-operated infrastructure/services) without demonstrated OpenClaw impact
- Reports whose only claim is host-side exec when sandbox runtime is disabled/unavailable (documented default behavior in the trusted-operator model), without a boundary bypass.
@@ -156,6 +168,24 @@ OpenClaw's security model is "personal assistant" (one trusted operator, potenti
- For company-shared setups, use a dedicated machine/VM/container and dedicated accounts; avoid mixing personal data on that runtime.
- If that host/browser profile is logged into personal accounts (for example Apple/Google/personal password manager), you have collapsed the boundary and increased personal-data exposure risk.
## Context Visibility and Allowlists
OpenClaw distinguishes:
- **Trigger authorization**: who can trigger the agent (`dmPolicy`, `groupPolicy`, allowlists, mention gates)
- **Context visibility**: what supplemental context is provided to the model (reply body, quoted text, thread history, forwarded metadata)
In current releases, allowlists primarily gate triggering and owner-style command access. They do not guarantee universal supplemental-context redaction across every channel/surface.
Current channel behavior is not fully uniform:
- some channels already filter parts of supplemental context by sender allowlist
- other channels still pass supplemental context as received
Reports that only show supplemental-context visibility differences are typically hardening/consistency findings unless they also demonstrate a documented boundary bypass (auth, policy, approvals, sandbox, or equivalent).
Hardening roadmap may add explicit visibility modes (for example `all`, `allowlist`, `allowlist_quote`) so operators can opt into stricter context filtering with predictable tradeoffs.
## Agent and Model Assumptions
- The model/agent is **not** a trusted principal. Assume prompt/content injection can manipulate behavior.

View File

@@ -3,238 +3,305 @@
<channel>
<title>OpenClaw</title>
<item>
<title>2026.3.28</title>
<pubDate>Sun, 29 Mar 2026 02:10:40 +0000</pubDate>
<title>2026.4.2</title>
<pubDate>Thu, 02 Apr 2026 18:57:54 +0000</pubDate>
<link>https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml</link>
<sparkle:version>2026032890</sparkle:version>
<sparkle:shortVersionString>2026.3.28</sparkle:shortVersionString>
<sparkle:version>2026040290</sparkle:version>
<sparkle:shortVersionString>2026.4.2</sparkle:shortVersionString>
<sparkle:minimumSystemVersion>15.0</sparkle:minimumSystemVersion>
<description><![CDATA[<h2>OpenClaw 2026.3.28</h2>
<description><![CDATA[<h2>OpenClaw 2026.4.2</h2>
<h3>Breaking</h3>
<ul>
<li>Providers/Qwen: remove the deprecated <code>qwen-portal-auth</code> OAuth integration for <code>portal.qwen.ai</code>; migrate to Model Studio with <code>openclaw onboard --auth-choice modelstudio-api-key</code>. (#52709) Thanks @pomelo-nwu.</li>
<li>Config/Doctor: drop automatic config migrations older than two months; very old legacy keys now fail validation instead of being rewritten on load or by <code>openclaw doctor</code>.</li>
<li>Plugins/xAI: move <code>x_search</code> settings from the legacy core <code>tools.web.x_search.*</code> path to the plugin-owned <code>plugins.entries.xai.config.xSearch.*</code> path, standardize <code>x_search</code> auth on <code>plugins.entries.xai.config.webSearch.apiKey</code> / <code>XAI_API_KEY</code>, and migrate legacy config with <code>openclaw doctor --fix</code>. (#59674) Thanks @vincentkoc.</li>
<li>Plugins/web fetch: move Firecrawl <code>web_fetch</code> config from the legacy core <code>tools.web.fetch.firecrawl.*</code> path to the plugin-owned <code>plugins.entries.firecrawl.config.webFetch.*</code> path, route <code>web_fetch</code> fallback through the new fetch-provider boundary instead of a Firecrawl-only core branch, and migrate legacy config with <code>openclaw doctor --fix</code>. (#59465) Thanks @vincentkoc.</li>
</ul>
<h3>Changes</h3>
<ul>
<li>xAI/tools: move the bundled xAI provider to the Responses API, add first-class <code>x_search</code>, and auto-enable the xAI plugin from owned web-search and tool config so bundled Grok auth/configured search flows work without manual plugin toggles. (#56048) Thanks @huntharo.</li>
<li>xAI/onboarding: let the bundled Grok web-search plugin offer optional <code>x_search</code> setup during <code>openclaw onboard</code> and <code>openclaw configure --section web</code>, including an x_search model picker with the shared xAI key.</li>
<li>MiniMax: add image generation provider for <code>image-01</code> model, supporting generate and image-to-image editing with aspect ratio control. (#54487) Thanks @liyuan97.</li>
<li>Plugins/hooks: add async <code>requireApproval</code> to <code>before_tool_call</code> hooks, letting plugins pause tool execution and prompt the user for approval via the exec approval overlay, Telegram buttons, Discord interactions, or the <code>/approve</code> command on any channel. The <code>/approve</code> command now handles both exec and plugin approvals with automatic fallback. (#55339) Thanks @vaclavbelak and @joshavant.</li>
<li>ACP/channels: add current-conversation ACP binds for Discord, BlueBubbles, and iMessage so <code>/acp spawn codex --bind here</code> can turn the current chat into a Codex-backed workspace without creating a child thread, and document the distinction between chat surface, ACP session, and runtime workspace.</li>
<li>OpenAI/apply_patch: enable <code>apply_patch</code> by default for OpenAI and OpenAI Codex models, and align its sandbox policy access with <code>write</code> permissions.</li>
<li>Plugins/CLI backends: move bundled Claude CLI, Codex CLI, and Gemini CLI inference defaults onto the plugin surface, add bundled Gemini CLI backend support, and replace <code>gateway run --claude-cli-logs</code> with generic <code>--cli-backend-logs</code> while keeping the old flag as a compatibility alias.</li>
<li>Plugins/startup: auto-load bundled provider and CLI-backend plugins from explicit config refs, so bundled Claude CLI, Codex CLI, and Gemini CLI message-provider setups no longer need manual <code>plugins.allow</code> entries.</li>
<li>Podman: simplify the container setup around the current rootless user, install the launch helper under <code>~/.local/bin</code>, and document the host-CLI <code>openclaw --container <name> ...</code> workflow instead of a dedicated <code>openclaw</code> service user.</li>
<li>Slack/tool actions: add an explicit <code>upload-file</code> Slack action that routes file uploads through the existing Slack upload transport, with optional filename/title/comment overrides for channels and DMs.</li>
<li>Message actions/files: start unifying file-first sends on the canonical <code>upload-file</code> action by adding explicit support for Microsoft Teams and Google Chat, and by exposing BlueBubbles file sends through <code>upload-file</code> while keeping the legacy <code>sendAttachment</code> alias.</li>
<li>Plugins/Matrix TTS: send auto-TTS replies as native Matrix voice bubbles instead of generic audio attachments. (#37080) thanks @Matthew19990919.</li>
<li>CLI: add <code>openclaw config schema</code> to print the generated JSON schema for <code>openclaw.json</code>. (#54523) Thanks @kvokka.</li>
<li>Config/TTS: auto-migrate legacy speech config on normal reads and secret resolution, keep legacy diagnostics for Doctor, and remove regular-mode runtime fallback for old bundled <code>tts.<provider></code> API-key shapes.</li>
<li>Memory/plugins: move the pre-compaction memory flush plan behind the active memory plugin contract so <code>memory-core</code> owns flush prompts and target-path policy instead of hardcoded core logic.</li>
<li>MiniMax: trim model catalog to M2.7 only, removing legacy M2, M2.1, M2.5, and VL-01 models. (#54487) Thanks @liyuan97.</li>
<li>Plugins/runtime: expose <code>runHeartbeatOnce</code> in the plugin runtime <code>system</code> namespace so plugins can trigger a single heartbeat cycle with an explicit delivery target override (e.g. <code>heartbeat: { target: "last" }</code>). (#40299) Thanks @loveyana.</li>
<li>Agents/compaction: preserve the post-compaction AGENTS refresh on stale-usage preflight compaction for both immediate replies and queued followups. (#49479) Thanks @jared596.</li>
<li>Agents/compaction: surface safeguard-specific cancel reasons and relabel benign manual <code>/compact</code> no-op cases as skipped instead of failed. (#51072) Thanks @afurm.</li>
<li>Docs: add <code>pnpm docs:check-links:anchors</code> for Mintlify anchor validation while keeping <code>scripts/docs-link-audit.mjs</code> as the stable link-audit entrypoint. (#55912) Thanks @velvet-shark.</li>
<li>Tavily: mark outbound API requests with <code>X-Client-Source: openclaw</code> so Tavily can attribute OpenClaw-originated traffic. (#55335) Thanks @lakshyaag-tavily.</li>
<li>Tasks/Task Flow: restore the core Task Flow substrate with managed-vs-mirrored sync modes, durable flow state/revision tracking, and <code>openclaw flows</code> inspection/recovery primitives so background orchestration can persist and be operated separately from plugin authoring layers. (#58930) Thanks @mbelinky.</li>
<li>Tasks/Task Flow: add managed child task spawning plus sticky cancel intent, so external orchestrators can stop scheduling immediately and let parent Task Flows settle to <code>cancelled</code> once active child tasks finish. (#59610) Thanks @mbelinky.</li>
<li>Plugins/Task Flow: add a bound <code>api.runtime.taskFlow</code> seam so plugins and trusted authoring layers can create and drive managed Task Flows from host-resolved OpenClaw context without passing owner identifiers on each call. (#59622) Thanks @mbelinky.</li>
<li>Android/assistant: add assistant-role entrypoints plus Google Assistant App Actions metadata so Android can launch OpenClaw from the assistant trigger and hand prompts into the chat composer. (#59596) Thanks @obviyus.</li>
<li>Exec defaults: make gateway/node host exec default to YOLO mode by requesting <code>security=full</code> with <code>ask=off</code>, and align host approval-file fallbacks plus docs/doctor reporting with that no-prompt default.</li>
<li>Providers/runtime: add provider-owned replay hook surfaces for transcript policy, replay cleanup, and reasoning-mode dispatch. (#59143) Thanks @jalehman.</li>
<li>Plugins/hooks: add <code>before_agent_reply</code> so plugins can short-circuit the LLM with synthetic replies after inline actions. (#20067) Thanks @JoshuaLelon.</li>
<li>Channels/session routing: move provider-specific session conversation grammar into plugin-owned session-key surfaces, preserving Telegram topic routing and Feishu scoped inheritance across bootstrap, model override, restart, and tool-policy paths.</li>
<li>Feishu/comments: add a dedicated Drive comment-event flow with comment-thread context resolution, in-thread replies, and <code>feishu_drive</code> comment actions for document collaboration workflows. (#58497) Thanks @wittam-01.</li>
<li>Matrix/plugin: emit spec-compliant <code>m.mentions</code> metadata across text sends, media captions, edits, poll fallback text, and action-driven edits so Matrix mentions notify reliably in clients like Element. (#59323) Thanks @gumadeiras.</li>
<li>Diffs: add plugin-owned <code>viewerBaseUrl</code> so viewer links can use a stable proxy/public origin without passing <code>baseUrl</code> on every tool call. (#59341) Related #59227. Thanks @gumadeiras.</li>
<li>Agents/compaction: resolve <code>agents.defaults.compaction.model</code> consistently for manual <code>/compact</code> and other context-engine compaction paths, so engine-owned compaction uses the configured override model across runtime entrypoints. (#56710) Thanks @oliviareid-svg.</li>
<li>Agents/compaction: add <code>agents.defaults.compaction.notifyUser</code> so the <code>🧹 Compacting context...</code> start notice is opt-in instead of always being shown. (#54251) Thanks @oguricap0327.</li>
<li>WhatsApp/reactions: add <code>reactionLevel</code> guidance for agent reactions. Thanks @mcaxtr.</li>
<li>Exec approvals/channels: auto-enable DM-first native chat approvals when supported channels can infer approvers from existing owner config, while keeping channel fanout explicit and clarifying forwarding versus native approval client config.</li>
</ul>
<h3>Fixes</h3>
<ul>
<li>Agents/Anthropic: recover unhandled provider stop reasons (e.g. <code>sensitive</code>) as structured assistant errors instead of crashing the agent run. (#56639)</li>
<li>Google/models: resolve Gemini 3.1 pro, flash, and flash-lite for all Google provider aliases by passing the actual runtime provider ID and adding a template-provider fallback; fix flash-lite prefix ordering. (#56567)</li>
<li>OpenAI Codex/image tools: register Codex for media understanding and route image prompts through Codex instructions so image analysis no longer fails on missing provider registration or missing <code>instructions</code>. (#54829) Thanks @neeravmakwana.</li>
<li>Agents/image tool: restore the generic image-runtime fallback when no provider-specific media-understanding provider is registered, so image analysis works again for providers like <code>openrouter</code> and <code>minimax-portal</code>. (#54858) Thanks @MonkeyLeeT.</li>
<li>WhatsApp: fix infinite echo loop in self-chat DM mode where the bot's own outbound replies were re-processed as new inbound user messages. (#54570) Thanks @joelnishanth</li>
<li>Telegram/splitting: replace proportional text estimate with verified HTML-length search so long messages split at word boundaries instead of mid-word; gracefully degrade when tag overhead exceeds the limit. (#56595)</li>
<li>Telegram/delivery: skip whitespace-only and hook-blanked text replies in bot delivery to prevent GrammyError 400 empty-text crashes. (#56620)</li>
<li>Telegram/send: validate <code>replyToMessageId</code> at all four API sinks with a shared normalizer that rejects non-numeric, NaN, and mixed-content strings. (#56587)</li>
<li>Mistral: normalize OpenAI-compatible request flags so official Mistral API runs no longer fail with remaining <code>422 status code (no body)</code> chat errors.</li>
<li>Control UI/config: keep sensitive raw config hidden by default, replace the blank blocked editor with an explicit reveal-to-edit state, and restore raw JSON editing without auto-exposing secrets. Fixes #55322.</li>
<li>CLI/zsh: defer <code>compdef</code> registration until <code>compinit</code> is available so zsh completion loads cleanly with plugin managers and manual setups. (#56555)</li>
<li>BlueBubbles/debounce: guard debounce flush against null message text by sanitizing at the enqueue boundary and adding an independent combiner guard. (#56573)</li>
<li>Auto-reply: suppress JSON-wrapped <code>{"action":"NO_REPLY"}</code> control envelopes before channel delivery with a strict single-key detector; preserves media when text is only a silent envelope. (#56612)</li>
<li>ACP/ACPX agent registry: align OpenClaw's ACPX built-in agent mirror with the latest <code>openclaw/acpx</code> command defaults and built-in aliases, pin versioned <code>npx</code> built-ins to exact versions, and stop unknown ACP agent ids from falling through to raw <code>--agent</code> command execution on the MCP-proxy path. (#28321) Thanks @m0nkmaster and @vincentkoc.</li>
<li>Security/audit: extend web search key audit to recognize Gemini, Grok/xAI, Kimi, Moonshot, and OpenRouter credentials via a boundary-safe bundled-web-search registry shim. (#56540)</li>
<li>Docs/FAQ: remove broken Xfinity SSL troubleshooting cross-links from English and zh-CN FAQ entries — both sections already contain the full workaround inline. (#56500)</li>
<li>Telegram: deliver verbose tool summaries inside forum topic sessions again, so threaded topic chats now match DM verbose behavior. (#43236) Thanks @frankbuild.</li>
<li>BlueBubbles/CLI agents: restore inbound prompt image refs for CLI routed turns, reapply embedded runner image size guardrails, and cover both CLI image transport paths with regression tests. (#51373)</li>
<li>BlueBubbles/groups: optionally enrich unnamed participant lists with local macOS Contacts names after group gating passes, so group member context can show names instead of only raw phone numbers.</li>
<li>Discord/reconnect: drain stale gateway sockets, clear cached resume state before forced fresh reconnects, and fail closed when old sockets refuse to die so Discord recovery stops looping on poisoned resume state. (#54697) Thanks @ngutman.</li>
<li>iMessage: stop leaking inline <code>[[reply_to:...]]</code> tags into delivered text by sending <code>reply_to</code> as RPC metadata and stripping stray directive tags from outbound messages. (#39512) Thanks @mvanhorn.</li>
<li>CLI/plugins: make routed commands use the same auto-enabled bundled-channel snapshot as gateway startup, so configured bundled channels like Slack load without requiring a prior config rewrite. (#54809) Thanks @neeravmakwana.</li>
<li>CLI/message send: write manual <code>openclaw message send</code> deliveries into the resolved agent session transcript again by always threading the default CLI agent through outbound mirroring. (#54187) Thanks @KevInTheCloud5617.</li>
<li>CLI/onboarding: show the Kimi Code API key option again in the Moonshot setup menu so the interactive picker includes all Kimi setup paths together. Fixes #54412 Thanks @sparkyrider</li>
<li>Agents/status: use provider-aware context window lookup for fresh Anthropic 4.6 model overrides so <code>/status</code> shows the correct 1.0m window instead of an underreported shared-cache minimum. (#54796) Thanks @neeravmakwana.</li>
<li>OpenAI/WebSocket: preserve reasoning replay metadata and tool-call item ids on WebSocket tool turns, and start a fresh response chain when full-context resend is required. (#53856) Thanks @xujingchen1996.</li>
<li>OpenAI/WS: restore reasoning blocks for Responses WebSocket runs and keep reasoning/tool-call replay metadata intact so resumed sessions do not lose or break follow-up reasoning-capable turns. (#53856) Thanks @xujingchen1996.</li>
<li>Agents/errors: surface provider quota/reset details when available, but keep HTML/Cloudflare rate-limit pages on the generic fallback so raw error pages are not shown to users. (#54512) Thanks @bugkill3r.</li>
<li>Claude CLI: switch the bundled Claude CLI backend to <code>stream-json</code> output so watchdogs see progress on long runs, and keep session/usage metadata even when Claude finishes with an empty result line. (#49698) Thanks @felear2022.</li>
<li>Claude CLI/MCP: always pass a strict generated <code>--mcp-config</code> overlay for background Claude CLI runs, including the empty-server case, so Claude does not inherit ambient user/global MCP servers. (#54961) Thanks @markojak.</li>
<li>Agents/embedded replies: surface mid-turn 429 and overload failures when embedded runs end without a user-visible reply, while preserving successful media-only replies that still use legacy <code>mediaUrl</code>. (#50930) Thanks @infichen.</li>
<li>Chat/UI: move the chat send button onto the shared ghost-button theme styling, while keeping the stop button icon readable on the danger state. (#55075) Thanks @bottenbenny.</li>
<li>WhatsApp/allowFrom: show a specific allowFrom policy error for valid blocked targets instead of the misleading <code><E.164|group JID></code> format hint. Thanks @mcaxtr.</li>
<li>Agents/cooldowns: scope rate-limit cooldowns per model so one 429 no longer blocks every model on the same auth profile, replace the exponential 1 min -> 1 h escalation with a stepped 30 s / 1 min / 5 min ladder, and surface a user-facing countdown message when all models are rate-limited. (#49834) Thanks @kiranvk-2011.</li>
<li>Agents/embedded transport errors: distinguish common network failures like connection refused, DNS lookup failure, and interrupted sockets from true timeouts in embedded-run user messaging and lifecycle diagnostics. (#51419) Thanks @scoootscooob.</li>
<li>Telegram/pairing: ignore self-authored DM <code>message</code> updates so bot-pinned status cards and similar service updates do not trigger bogus pairing requests or re-enter inbound dispatch. (#54530) thanks @huntharo</li>
<li>Mattermost/replies: keep pairing replies, slash-command fallback replies, and model-picker messages on the resolved config path so <code>exec:</code> SecretRef bot tokens work across all outbound reply branches. (#48347) thanks @mathiasnagler.</li>
<li>Microsoft Teams/config: accept the existing <code>welcomeCard</code>, <code>groupWelcomeCard</code>, <code>promptStarters</code>, and feedback/reflection keys in strict config validation so already-supported Teams runtime settings stop failing schema checks. (#54679) Thanks @gumclaw.</li>
<li>MCP/channels: add a Gateway-backed channel MCP bridge with Codex/Claude-facing conversation tools, Claude channel notifications, and safer stdio bridge lifecycle handling for reconnects and routed session discovery.</li>
<li>Plugins/SDK: thread <code>moduleUrl</code> through plugin-sdk alias resolution so user-installed plugins outside the openclaw directory correctly resolve <code>openclaw/plugin-sdk/*</code> subpath imports, and gate <code>plugin-sdk:check-exports</code> in <code>release:check</code>. (#54283) Thanks @xieyongliang.</li>
<li>Config/web fetch: allow the documented <code>tools.web.fetch.maxResponseBytes</code> setting in runtime schema validation so valid configs no longer fail with unrecognized-key errors. (#53401) Thanks @erhhung.</li>
<li>Message tool/buttons: keep the shared <code>buttons</code> schema optional in merged tool definitions so plain <code>action=send</code> calls stop failing validation when no buttons are provided. (#54418) Thanks @adzendo.</li>
<li>Agents/openai-compatible tool calls: deduplicate repeated tool call ids across live assistant messages and replayed history so OpenAI-compatible backends no longer reject duplicate <code>tool_call_id</code> values with HTTP 400. (#40996) Thanks @xaeon2026.</li>
<li>Models/openai-completions: default non-native OpenAI-compatible providers to omit tool-definition <code>strict</code> fields unless users explicitly opt back in, so tool calling keeps working on providers that reject that option. (#45497) Thanks @sahancava.</li>
<li>Plugins/context engines: retry strict legacy <code>assemble()</code> calls without the new <code>prompt</code> field when older engines reject it, preserving prompt-aware retrieval compatibility for pre-prompt plugins. (#50848) thanks @danhdoan.</li>
<li>CLI/update status: explicitly say <code>up to date</code> when the local version already matches npm latest, while keeping the availability logic unchanged. (#51409) Thanks @dongzhenye.</li>
<li>Daemon/Linux: stop flagging non-gateway systemd services as duplicate gateways just because their unit files mention OpenClaw, reducing false-positive doctor/log noise. (#45328) Thanks @gregretkowski.</li>
<li>Feishu: close WebSocket connections on monitor stop/abort so ghost connections no longer persist, preventing duplicate event processing and resource leaks across restart cycles. (#52844) Thanks @schumilin.</li>
<li>Feishu: use the original message <code>create_time</code> instead of <code>Date.now()</code> for inbound timestamps so offline-retried messages carry the correct authoring time, preventing mis-targeted agent actions on stale instructions. (#52809) Thanks @schumilin.</li>
<li>Control UI/Skills: open skill detail dialogs with the browser modal lifecycle so clicking a skill row keeps the panel centered instead of rendering it off-screen at the bottom of the page.</li>
<li>Matrix/replies: include quoted poll question/options in inbound reply context so the agent sees the original poll content when users reply to Matrix poll messages. (#55056) Thanks @alberthild.</li>
<li>Matrix/plugins: keep plugin bootstrap from crashing when built runtime mixes bare and deep <code>matrix-js-sdk</code> entrypoints, so unrelated channels do not get taken down during plugin load. (#56273) Thanks @aquaright1.</li>
<li>Agents/sandbox: honor <code>tools.sandbox.tools.alsoAllow</code>, let explicit sandbox re-allows remove matching built-in default-deny tools, and keep sandbox explain/error guidance aligned with the effective sandbox tool policy. (#54492) Thanks @ngutman.</li>
<li>Agents/sandbox: make blocked-tool guidance glob-aware again, redact/sanitize session-specific explain hints for safer copy-paste, and avoid leaking control-character session keys in those hints. (#54684) Thanks @ngutman.</li>
<li>Agents/compaction: trigger timeout recovery compaction before retrying high-context LLM timeouts so embedded runs stop repeating oversized requests. (#46417) thanks @joeykrug.</li>
<li>Agents/compaction: reconcile <code>sessions.json.compactionCount</code> after a late embedded auto-compaction success so persisted session counts catch up once the handler reports completion. (#45493) Thanks @jackal092927.</li>
<li>Agents/failover: classify Codex accountId token extraction failures as auth errors so model fallback continues to the next configured candidate. (#55206) Thanks @cosmicnet.</li>
<li>Plugins/runtime: reuse only compatible active plugin registries across tools, providers, web search, and channel bootstrap, align <code>/tools/invoke</code> plugin loading with the session workspace, and retry outbound channel recovery when the pinned channel surface changes so plugin tools and channels stop disappearing or re-registering from mismatched runtime loads. Thanks @gumadeiras.</li>
<li>Talk/macOS: stop direct system-voice failures from replaying system speech, use app-locale fallback for shared watchdog timing, and add regression coverage for the macOS fallback route and language-aware timeout policy. (#53511) thanks @hongsw.</li>
<li>Discord/gateway cleanup: keep late Carbon reconnect-exhausted errors suppressed through startup/dispose cleanup so Discord monitor shutdown no longer crashes on late gateway close events. (#55373) Thanks @Takhoffman.</li>
<li>Discord/gateway shutdown: treat expected reconnect-exhausted events during intentional lifecycle stop as clean shutdowns so startup-abort cleanup no longer surfaces false gateway failures. (#55324) Thanks @joelnishanth.</li>
<li>Discord/gateway shutdown: suppress reconnect-exhausted events that were already buffered before teardown flips <code>lifecycleStopping</code>, so stale-socket Discord restarts no longer crash the whole gateway. Fixes #55403 and #55421. Thanks @lml2468 and @vincentkoc.</li>
<li>GitHub Copilot/auth refresh: treat large <code>expires_at</code> values as seconds epochs and clamp far-future runtime auth refresh timers so Copilot token refresh cannot fall into a <code>setTimeout</code> overflow hot loop. (#55360) Thanks @michael-abdo.</li>
<li>Agents/status: use the persisted runtime session model in <code>session_status</code> when no explicit override exists, and honor per-agent <code>thinkingDefault</code> in both <code>session_status</code> and <code>/status</code>. (#55425) Thanks @scoootscooob, @xaeon2026, and @ysfbsf.</li>
<li>Heartbeat/runner: guarantee the interval timer is re-armed after heartbeat runs and unexpected runner errors so scheduled heartbeats do not silently stop after an interrupted cycle. (#52270) Thanks @MiloStack.</li>
<li>Config/Doctor: rewrite stale bundled plugin load paths from legacy bundled-plugin locations to the packaged bundled path, including directory-name mismatches and slash-suffixed config entries. (#55054) Thanks @SnowSky1.</li>
<li>WhatsApp/mentions: stop treating mentions embedded in quoted messages as direct mentions so replying to a message that @mentioned the bot no longer falsely triggers mention gating. (#52711) Thanks @lurebat.</li>
<li>Matrix: keep separate 2-person rooms out of DM routing after <code>m.direct</code> seeds successfully, while still honoring explicit <code>is_direct</code> state and startup fallback recovery. (#54890) thanks @private-peter</li>
<li>Agents/ollama fallback: surface non-2xx Ollama HTTP errors with a leading status code so HTTP 503 responses trigger model fallback again. (#55214) Thanks @bugkill3r.</li>
<li>Feishu/tools: stop synthetic agent ids like <code>agent-spawner</code> from being treated as Feishu account ids during tool execution, so tools fall back to the configured/default Feishu account unless the contextual id is a real enabled Feishu account. (#55627) Thanks @MonkeyLeeT.</li>
<li>Google/tools: strip empty <code>required: []</code> arrays from Gemini tool schemas so optional-only tool parameters no longer trigger Google validator 400s. (#52106) Thanks @oliviareid-svg.</li>
<li>Onboarding/TUI/local gateways: show the resolved gateway port in setup output, clarify no-daemon local health/dashboard messaging, and preserve loopback Control UI auth on reruns and explicit local gateway URLs so local quickstart flows recover cleanly. (#55730) Thanks @shakkernerd.</li>
<li>TUI/chat log: keep system messages as single logical entries and prune overflow at whole-message boundaries so wrapped system spacing stays intact. (#55732) Thanks @shakkernerd.</li>
<li>TUI/activation: validate <code>/activation</code> arguments in the TUI and reject invalid values instead of silently coercing them to <code>mention</code>. (#55733) Thanks @shakkernerd.</li>
<li>Agents/model switching: apply <code>/model</code> changes to active embedded runs at the next safe retry boundary, so overloaded or retrying turns switch to the newly selected model instead of staying pinned to the old provider.</li>
<li>Agents/Codex fallback: classify Codex <code>server_error</code> payloads as failoverable, sanitize <code>Codex error:</code> payloads before they reach chat, preserve context-overflow guidance for prefixed <code>invalid_request_error</code> payloads, and omit provider <code>request_id</code> values from user-facing UI copy. (#42892) Thanks @xaeon2026.</li>
<li>Memory/search: share memory embedding provider registrations across split plugin runtimes so memory search no longer fails with unknown provider errors after memory-core registers built-in adapters. (#55945) Thanks @glitch418x.</li>
<li>Discord/Carbon beta: update <code>@buape/carbon</code> to the latest beta and pass the new <code>RateLimitError</code> request argument so Discord stays compatible with the upstream beta constructor change. (#55980) Thanks @ngutman.</li>
<li>Plugins/inbound claims: pass full inbound attachment arrays through <code>inbound_claim</code> hook metadata while keeping the legacy singular media attachment fields for compatibility. (#55452) Thanks @huntharo.</li>
<li>Plugins/Matrix: preserve sender filenames for inbound media by forwarding <code>originalFilename</code> to <code>saveMediaBuffer</code>. (#55692) thanks @esrehmki.</li>
<li>Matrix/mentions: recognize <code>matrix.to</code> mentions whose visible label uses the bot's room display name, so <code>requireMention: true</code> rooms respond correctly in modern Matrix clients. (#55393) thanks @nickludlam.</li>
<li>Ollama/thinking off: route <code>thinkingLevel=off</code> through the live Ollama extension request path so thinking-capable Ollama models now receive top-level <code>think: false</code> instead of silently generating hidden reasoning tokens. (#53200) Thanks @BruceMacD.</li>
<li>Plugins/diffs: stage bundled <code>@pierre/diffs</code> runtime dependencies during packaged updates so the bundled diff viewer keeps loading after global installs and updates. (#56077) Thanks @gumadeiras.</li>
<li>Plugins/diffs: load bundled Pierre themes without JSON module imports so diff rendering keeps working on newer Node builds. (#45869) thanks @NickHood1984.</li>
<li>Plugins/uninstall: remove owned <code>channels.<id></code> config when uninstalling channel plugins, and keep the uninstall preview aligned with explicit channel ownership so built-in channels and shared keys stay intact. (#35915) Thanks @wbxl2000.</li>
<li>Plugins/Matrix: prefer explicit DM signals when choosing outbound direct rooms and routing unmapped verification summaries, so strict 2-person fallback rooms do not outrank the real DM. (#56076) thanks @gumadeiras</li>
<li>Plugins/Matrix: resolve env-backed <code>accessToken</code> and <code>password</code> SecretRefs against the active Matrix config env path during startup, and officially accept SecretRef <code>accessToken</code> config values. (#54980) thanks @kakahu2015.</li>
<li>Microsoft Teams/proactive DMs: prefer the freshest personal conversation reference for <code>user:<aadObjectId></code> sends when multiple stored references exist, so replies stop targeting stale DM threads. (#54702) Thanks @gumclaw.</li>
<li>Gateway/plugins: reuse the session workspace when building HTTP <code>/tools/invoke</code> tool lists and harden tool construction to infer the session agent workspace by default, so workspace plugins do not re-register on repeated HTTP tool calls. (#56101) thanks @neeravmakwana</li>
<li>Brave/web search: normalize unsupported Brave <code>country</code> filters to <code>ALL</code> before request and cache-key generation so locale-derived values like <code>VN</code> stop failing with upstream 422 validation errors. (#55695) Thanks @chen-zhang-cs-code.</li>
<li>Discord/replies: preserve leading indentation when stripping inline reply tags so reply-tagged plain text and fenced code blocks keep their formatting. (#55960) Thanks @Nanako0129.</li>
<li>Daemon/status: surface immediate gateway close reasons from lightweight probes and prefer those concrete auth or pairing failures over generic timeouts in <code>openclaw daemon status</code>. (#56282) Thanks @mbelinky.</li>
<li>Agents/failover: classify HTTP 410 errors as retryable timeouts by default while still preserving explicit session-expired, billing, and auth signals from the payload. (#55201) thanks @nikus-pan.</li>
<li>Agents/subagents: restore completion announce delivery for extension channels like BlueBubbles. (#56348)</li>
<li>Plugins/Matrix: load bundled <code>@matrix-org/matrix-sdk-crypto-nodejs</code> through <code>createRequire(...)</code> so E2EE media send and receive keep the package-local native binding lookup working in packaged ESM builds. (#54566) thanks @joelnishanth.</li>
<li>Plugins/Matrix: encrypt E2EE image thumbnails with <code>thumbnail_file</code> while keeping unencrypted-room previews on <code>thumbnail_url</code>, so encrypted Matrix image events keep thumbnail metadata without leaking plaintext previews. (#54711) thanks @frischeDaten.</li>
<li>Telegram/forum topics: keep native <code>/new</code> and <code>/reset</code> routed to the active topic by preserving the topic target on forum-thread command context. (#35963)</li>
<li>Providers/transport policy: centralize request auth, proxy, TLS, and header shaping across shared HTTP, stream, and websocket paths, block insecure TLS/runtime transport overrides, and keep proxy-hop TLS separate from target mTLS settings. (#59682) Thanks @vincentkoc.</li>
<li>Providers/Copilot: classify native GitHub Copilot API hosts in the shared provider endpoint resolver and harden token-derived proxy endpoint parsing so Copilot base URL routing stays centralized and fails closed on malformed hints. (#59644) Thanks @vincentkoc.</li>
<li>Providers/streaming headers: centralize default and attribution header merging across OpenAI websocket, embedded-runner, and proxy stream paths so provider-specific headers stay consistent and caller overrides only win where intended. (#59542) Thanks @vincentkoc.</li>
<li>Providers/media HTTP: centralize base URL normalization, default auth/header injection, and explicit header override handling across shared OpenAI-compatible audio, Deepgram audio, Gemini media/image, and Moonshot video request paths. (#59469) Thanks @vincentkoc.</li>
<li>Providers/OpenAI-compatible routing: centralize native-vs-proxy request policy so hidden attribution and related OpenAI-family defaults only apply on verified native endpoints across stream, websocket, and shared audio HTTP paths. (#59433) Thanks @vincentkoc.</li>
<li>Providers/Anthropic routing: centralize native-vs-proxy endpoint classification for direct Anthropic <code>service_tier</code> handling so spoofed or proxied hosts do not inherit native Anthropic defaults. (#59608) Thanks @vincentkoc.</li>
<li>Gateway/exec loopback: restore legacy-role fallback for empty paired-device token maps and allow silent local role upgrades so local exec and node clients stop failing with pairing-required errors after <code>2026.3.31</code>. (#59092) Thanks @openperf.</li>
<li>Agents/subagents: pin admin-only subagent gateway calls to <code>operator.admin</code> while keeping <code>agent</code> at least privilege, so <code>sessions_spawn</code> no longer dies on loopback scope-upgrade pairing with <code>close(1008) "pairing required"</code>. (#59555) Thanks @openperf.</li>
<li>Exec approvals/config: strip invalid <code>security</code>, <code>ask</code>, and <code>askFallback</code> values from <code>~/.openclaw/exec-approvals.json</code> during normalization so malformed policy enums fall back cleanly to the documented defaults instead of corrupting runtime policy resolution. (#59112) Thanks @openperf.</li>
<li>Exec approvals/doctor: report host policy sources from the real approvals file path and ignore malformed host override values when attributing effective policy conflicts. (#59367) Thanks @gumadeiras.</li>
<li>Exec/runtime: treat <code>tools.exec.host=auto</code> as routing-only, keep implicit no-config exec on sandbox when available or gateway otherwise, and reject per-call host overrides that would bypass the configured sandbox or host target. (#58897) Thanks @vincentkoc.</li>
<li>Slack/mrkdwn formatting: add built-in Slack mrkdwn guidance in inbound context so Slack replies stop falling back to generic Markdown patterns that render poorly in Slack. (#59100) Thanks @jadewon.</li>
<li>WhatsApp/presence: send <code>unavailable</code> presence on connect in self-chat mode so personal-phone users stop losing all push notifications while the gateway is running. (#59410) Thanks @mcaxtr.</li>
<li>WhatsApp/media: add HTML, XML, and CSS to the MIME map and fall back gracefully for unknown media types instead of dropping the attachment. (#51562) Thanks @bobbyt74.</li>
<li>Matrix/onboarding: restore guided setup in <code>openclaw channels add</code> and <code>openclaw configure --section channels</code>, while keeping custom plugin wizards on the shared <code>setupWizard</code> seam. (#59462) Thanks @gumadeiras.</li>
<li>Matrix/streaming: keep live partial previews for the current assistant block while preserving completed block updates as separate messages when <code>channels.matrix.blockStreaming</code> is enabled. (#59384) Thanks @gumadeiras.</li>
<li>Feishu/comment threads: harden document comment-thread delivery so whole-document comments fall back to <code>add_comment</code>, delayed reply lookups retry more reliably, and user-visible replies avoid reasoning/planning spillover. (#59129) Thanks @wittam-01.</li>
<li>MS Teams/streaming: strip already-streamed text from fallback block delivery when replies exceed the 4000-character streaming limit so long responses stop duplicating content. (#59297) Thanks @bradgroux.</li>
<li>Slack/thread context: filter thread starter and history by the effective conversation allowlist without dropping valid open-room, DM, or group DM context. (#58380) Thanks @jacobtomlinson.</li>
<li>Mattermost/probes: route status probes through the SSRF guard and honor <code>allowPrivateNetwork</code> so connectivity checks stay safe for self-hosted Mattermost deployments. (#58529) Thanks @mappel-nv.</li>
<li>Zalo/webhook replay: scope replay dedupe key by chat and sender so reused message IDs across different chats or senders no longer collide, and harden metadata reads for partially missing payloads. (#58444)</li>
<li>QQBot/structured payloads: restrict local file paths to QQ Bot-owned media storage, block traversal outside that root, reduce path leakage in logs, and keep inline image data URLs working. (#58453) Thanks @jacobtomlinson.</li>
<li>Image generation/providers: route OpenAI, MiniMax, and fal image requests through the shared provider HTTP transport path so custom base URLs, guarded private-network routing, and provider request defaults stay aligned with the rest of provider HTTP. Thanks @vincentkoc.</li>
<li>Image generation/providers: stop inferring private-network access from configured OpenAI, MiniMax, and fal image base URLs, and cap shared HTTP error-body reads so hostile or misconfigured endpoints fail closed without relaxing SSRF policy or buffering unbounded error payloads. Thanks @vincentkoc.</li>
<li>Browser/host inspection: keep static Chrome inspection helpers out of the activated browser runtime so <code>openclaw doctor browser</code> and related checks do not eagerly load the bundled browser plugin. (#59471) Thanks @vincentkoc.</li>
<li>Browser/CDP: normalize trailing-dot localhost absolute-form hosts before loopback checks so remote CDP websocket URLs like <code>ws://localhost.:...</code> rewrite back to the configured remote host. (#59236) Thanks @mappel-nv.</li>
<li>Agents/output sanitization: strip namespaced <code>antml:thinking</code> blocks from user-visible text so Anthropic-style internal monologue tags do not leak into replies. (#59550) Thanks @obviyus.</li>
<li>Kimi Coding/tools: normalize Anthropic tool payloads into the OpenAI-compatible function shape Kimi Coding expects so tool calls stop losing required arguments. (#59440) Thanks @obviyus.</li>
<li>Image tool/paths: resolve relative local media paths against the agent <code>workspaceDir</code> instead of <code>process.cwd()</code> so inputs like <code>inbox/receipt.png</code> pass the local-path allowlist reliably. (#57222) Thanks Priyansh Gupta.</li>
<li>Podman/launch: remove noisy container output from <code>scripts/run-openclaw-podman.sh</code> and align the Podman install guidance with the quieter startup flow. (#59368) Thanks @sallyom.</li>
<li>Plugins/runtime: keep LINE reply directives and browser-backed cleanup/reset flows working even when those plugins are disabled while tightening bundled plugin activation guards. (#59412) Thanks @vincentkoc.</li>
<li>ACP/gateway reconnects: keep ACP prompts alive across transient websocket drops while still failing boundedly when reconnect recovery does not complete. (#59473) Thanks @obviyus.</li>
<li>ACP/gateway reconnects: reject stale pre-ack ACP prompts after reconnect grace expiry so callers fail cleanly instead of hanging indefinitely when the gateway never confirms the run.</li>
<li>Gateway/session kill: enforce HTTP operator scopes on session kill requests and gate authorization before session lookup so unauthenticated callers cannot probe session existence. (#59128) Thanks @jacobtomlinson.</li>
<li>MS Teams/logging: format non-<code>Error</code> failures with the shared unknown-error helper so logs stop collapsing caught SDK or Axios objects into <code>[object Object]</code>. (#59321) Thanks @bradgroux.</li>
<li>Channels/setup: ignore untrusted workspace channel plugins during setup resolution so a shadowing workspace plugin cannot override built-in channel setup/login flows unless explicitly trusted in config. (#59158) Thanks @mappel-nv.</li>
<li>Exec/Windows: restore allowlist enforcement with quote-aware <code>argPattern</code> matching across gateway and node exec, and surface accurate dynamic pre-approved executable hints in the exec tool description. (#56285) Thanks @kpngr.</li>
<li>Gateway: prune empty <code>node-pending-work</code> state entries after explicit acknowledgments and natural expiry so the per-node state map no longer grows indefinitely. (#58179) Thanks @gavyngong.</li>
<li>Webhooks/secret comparison: replace ad-hoc timing-safe secret comparisons across BlueBubbles, Feishu, Mattermost, Telegram, Twilio, and Zalo webhook handlers with the shared <code>safeEqualSecret</code> helper and reject empty auth tokens in BlueBubbles. (#58432) Thanks @eleqtrizit.</li>
<li>OpenShell/mirror: constrain <code>remoteWorkspaceDir</code> and <code>remoteAgentWorkspaceDir</code> to the managed <code>/sandbox</code> and <code>/agent</code> roots, and keep mirror sync from overwriting or removing user-added shell roots during config synchronization. (#58515) Thanks @eleqtrizit.</li>
<li>Plugins/activation: preserve explicit, auto-enabled, and default activation provenance plus reason metadata across CLI, gateway bootstrap, and status surfaces so plugin enablement state stays accurate after auto-enable resolution. (#59641) Thanks @vincentkoc.</li>
<li>Exec/env: block additional host environment override pivots for package roots, language runtimes, compiler include paths, and credential/config locations so request-scoped exec cannot redirect trusted toolchains or config lookups. (#59233) Thanks @drobison00.</li>
<li>Dotenv/workspace overrides: block workspace <code>.env</code> files from overriding <code>OPENCLAW_PINNED_PYTHON</code> and <code>OPENCLAW_PINNED_WRITE_PYTHON</code> so trusted helper interpreters cannot be redirected by repo-local env injection. (#58473) Thanks @eleqtrizit.</li>
<li>Plugins/install: accept JSON5 syntax in <code>openclaw.plugin.json</code> and bundle <code>plugin.json</code> manifests during install/validation, so third-party plugins with trailing commas, comments, or unquoted keys no longer fail to install. (#59084) Thanks @singleGanghood.</li>
<li>Telegram/exec approvals: rewrite shared <code>/approve … allow-always</code> callback payloads to <code>/approve … always</code> before Telegram button rendering so plugin approval IDs still fit Telegram's <code>callback_data</code> limit and keep the Allow Always action visible. (#59217) Thanks @jameslcowan.</li>
<li>Cron/exec timeouts: surface timed-out <code>exec</code> and <code>bash</code> failures in isolated cron runs even when <code>verbose: off</code>, including custom session-target cron jobs, so scheduled runs stop failing silently. (#58247) Thanks @skainguyen1412.</li>
<li>Telegram/exec approvals: fall back to the origin session key for async approval followups and keep resume-failure status delivery sanitized so Telegram followups still land without leaking raw exec metadata. (#59351) Thanks @seonang.</li>
<li>Node-host/exec approvals: bind <code>pnpm dlx</code> invocations through the approval planner's mutable-script path so the effective runtime command is resolved for approval instead of being left unbound. (#58374)</li>
<li>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 <code>SYSTEM_RUN_DENIED</code>. (#58977) Thanks @Starhappysh.</li>
<li>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.</li>
</ul>
<h3>Changes</h3>
<ul>
<li>macOS/Voice Wake: add the Voice Wake option to trigger Talk Mode. (#58490) Thanks @SmoothExec.</li>
<li>Tasks/chat: add <code>/tasks</code> as a chat-native background task board for the current session, with recent task details and agent-local fallback counts when no linked tasks are visible. Related #54226. Thanks @vincentkoc.</li>
<li>Web search/SearXNG: add the bundled SearXNG provider plugin for <code>web_search</code> with configurable host support. (#57317) Thanks @cgdusek.</li>
<li>Telegram/errors: add configurable <code>errorPolicy</code> and <code>errorCooldownMs</code> controls so Telegram can suppress repeated delivery errors per account, chat, and topic without muting distinct failures. (#51914) Thanks @chinar-amrutkar</li>
<li>Gateway/webchat: make <code>chat.history</code> text truncation configurable with <code>gateway.webchat.chatHistoryMaxChars</code> and per-request <code>maxChars</code>, while preserving silent-reply filtering and existing default payload limits. (#58900)</li>
<li>Amazon Bedrock/Guardrails: add Bedrock Guardrails support to the bundled provider. (#58588) Thanks @MikeORed.</li>
<li>ZAI/models: add <code>glm-5.1</code> and <code>glm-5v-turbo</code> to the bundled Z.AI provider catalog. (#58793) Thanks @tomsun28</li>
<li>Agents/default params: add <code>agents.defaults.params</code> for global default provider parameters. (#58548) Thanks @lpender.</li>
<li>Agents/failover: cap prompt-side and assistant-side same-provider auth-profile retries for rate-limit failures before cross-provider model fallback, add the <code>auth.cooldowns.rateLimitedProfileRotations</code> knob, and document the new fallback behavior. (#58707) Thanks @Forgely3D</li>
<li>Agents/compaction: resolve <code>agents.defaults.compaction.model</code> consistently for manual <code>/compact</code> and other context-engine compaction paths, so engine-owned compaction uses the configured override model across runtime entrypoints. (#56710) Thanks @oliviareid-svg</li>
<li>Cron/tools allowlist: add <code>openclaw cron --tools</code> for per-job tool allowlists. (#58504) Thanks @andyk-ms.</li>
</ul>
<h3>Fixes</h3>
<ul>
<li>Chat/error replies: stop leaking raw provider/runtime failures into external chat channels, return a friendly retry message instead, and add a specific <code>/new</code> hint for Bedrock toolResult/toolUse session mismatches. (#58831) Thanks @ImLukeF.</li>
<li>Sessions/model switching: keep <code>/model</code> changes queued behind busy runs instead of interrupting the active turn, and retarget queued followups so later work picks up the new model as soon as the current turn finishes.</li>
<li>Web UI/OpenResponses: preserve rewritten stream snapshots in webchat and keep OpenResponses final streamed text aligned when models rewind earlier output. (#58641) Thanks @neeravmakwana</li>
<li>Discord/inbound media: pass Discord attachment and sticker downloads through the shared idle-timeout and worker-abort path so slow or stuck inbound media fetches stop hanging message processing. (#58593) Thanks @aquaright1</li>
<li>Telegram/retries: keep non-idempotent sends on the strict safe-send path, retry wrapped pre-connect failures, and preserve <code>429</code> / <code>retry_after</code> backoff for safe delivery retries. (#51895) Thanks @chinar-amrutkar</li>
<li>Telegram/exec approvals: route topic-aware exec approval followups through Telegram-owned threading and approval-target parsing, so forum-topic approvals stay in the originating topic instead of falling back to the root chat. (#58783)</li>
<li>Telegram/local Bot API: preserve media MIME types for absolute-path downloads so local audio files still trigger transcription and other MIME-based handling. (#54603) Thanks @jzakirov</li>
<li>Channels/WhatsApp: pass inbound message timestamp to model context so the AI can see when WhatsApp messages were sent. (#58590) Thanks @Maninae</li>
<li>QQBot/voice: lazy-load <code>silk-wasm</code> in <code>audio-convert.ts</code> 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.</li>
</ul>
<p><a href="https://github.com/openclaw/openclaw/blob/main/CHANGELOG.md">View full changelog</a></p>
]]></description>
<enclosure url="https://github.com/openclaw/openclaw/releases/download/v2026.3.28/OpenClaw-2026.3.28.zip" length="25811288" type="application/octet-stream" sparkle:edSignature="SJp4ptVaGlOIXRPevS89DbfN2WKP0bKMXQoaT0fmLhy7pataDfHN0kxC3zu6P0Q/HtsxaESEhJUw48SCUNNKDA=="/>
<enclosure url="https://github.com/openclaw/openclaw/releases/download/v2026.4.2/OpenClaw-2026.4.2.zip" length="25843797" type="application/octet-stream" sparkle:edSignature="bNNXr4BJEU8W7ghXOujLJTYHZL2PL/r/p4llGBw0BFL+46mJ2Bir+IK8XQaCj5zp+O5JSuh5mY+Y/Nrq6TR7Cg=="/>
</item>
<item>
<title>2026.3.24</title>
<pubDate>Wed, 25 Mar 2026 17:06:31 +0000</pubDate>
<title>2026.4.1</title>
<pubDate>Wed, 01 Apr 2026 17:14:12 +0000</pubDate>
<link>https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml</link>
<sparkle:version>2026032490</sparkle:version>
<sparkle:shortVersionString>2026.3.24</sparkle:shortVersionString>
<sparkle:version>2026040190</sparkle:version>
<sparkle:shortVersionString>2026.4.1</sparkle:shortVersionString>
<sparkle:minimumSystemVersion>15.0</sparkle:minimumSystemVersion>
<description><![CDATA[<h2>OpenClaw 2026.3.24</h2>
<h3>Breaking</h3>
<description><![CDATA[<h2>OpenClaw 2026.4.1</h2>
<h3>Changes</h3>
<ul>
<li>Gateway/OpenAI compatibility: add <code>/v1/models</code> and <code>/v1/embeddings</code>, and forward explicit model overrides through <code>/v1/chat/completions</code> and <code>/v1/responses</code> for broader client and RAG compatibility. Thanks @vincentkoc.</li>
<li>Agents/tools: make <code>/tools</code> show the tools the current agent can actually use right now, add a compact default view with an optional detailed mode, and add a live "Available Right Now" section in the Control UI so it is easier to see what will work before you ask.</li>
<li>Microsoft Teams: migrate to the official Teams SDK and add AI-agent UX best practices including streaming 1:1 replies, welcome cards with prompt starters, feedback/reflection, informative status updates, typing indicators, and native AI labeling. (#51808)</li>
<li>Microsoft Teams: add message edit and delete support for sent messages, including in-thread fallbacks when no explicit target is provided. (#49925)</li>
<li>Skills/install metadata: add one-click install recipes to bundled skills (coding-agent, gh-issues, openai-whisper-api, session-logs, tmux, trello, weather) so the CLI and Control UI can offer dependency installation when requirements are missing. (#53411) Thanks @BunsDev.</li>
<li>Control UI/skills: add status-filter tabs (All / Ready / Needs Setup / Disabled) with counts, replace inline skill cards with a click-to-detail dialog showing requirements, toggle switch, install action, API key entry, source metadata, and homepage link. (#53411) Thanks @BunsDev.</li>
<li>Slack/interactive replies: restore rich reply parity for direct deliveries, auto-render simple trailing <code>Options:</code> lines as buttons/selects, improve Slack interactive setup defaults, and isolate reply controls from plugin interactive handlers. (#53389) Thanks @vincentkoc.</li>
<li>CLI/containers: add <code>--container</code> and <code>OPENCLAW_CONTAINER</code> to run <code>openclaw</code> commands inside a running Docker or Podman OpenClaw container. (#52651) Thanks @sallyom.</li>
<li>Discord/auto threads: add optional <code>autoThreadName: "generated"</code> naming so new auto-created threads can be renamed asynchronously with concise LLM-generated titles while keeping the existing message-based naming as the default. (#43366) Thanks @davidguttman.</li>
<li>Plugins/hooks: add <code>before_dispatch</code> with canonical inbound metadata and route handled replies through the normal final-delivery path, preserving TTS and routed delivery semantics. (#50444) Thanks @gfzhx.</li>
<li>Control UI/agents: convert agent workspace file rows to expandable <code><details></code> with lazy-loaded inline markdown preview, and add comprehensive <code>.sidebar-markdown</code> styles for headings, lists, code blocks, tables, blockquotes, and details/summary elements. (#53411) Thanks @BunsDev.</li>
<li>Control UI/markdown preview: restyle the agent workspace file preview dialog with a frosted backdrop, sized panel, and styled header, and integrate <code>@create-markdown/preview</code> v2 system theme for rich markdown rendering (headings, tables, code blocks, callouts, blockquotes) that auto-adapts to the app's light/dark design tokens. (#53411) Thanks @BunsDev.</li>
<li>macOS app/config: replace horizontal pill-based subsection navigation with a collapsible tree sidebar using disclosure chevrons and indented subsection rows. (#53411) Thanks @BunsDev.</li>
<li>CLI/skills: soften missing-requirements label from "missing" to "needs setup" and surface API key setup guidance (where to get a key, CLI save command, storage path) in <code>openclaw skills info</code> output. (#53411) Thanks @BunsDev.</li>
<li>macOS app/skills: add "Get your key" homepage link and storage-path hint to the API key editor dialog, and show the config path in save confirmation messages. (#53411) Thanks @BunsDev.</li>
<li>Control UI/agents: add a "Not set" placeholder to the default agent model selector dropdown. (#53411) Thanks @BunsDev.</li>
<li>Runtime/install: lower the supported Node 22 floor to <code>22.14+</code> while continuing to recommend Node 24, so npm installs and self-updates do not strand Node 22.14 users on older releases.</li>
<li>CLI/update: preflight the target npm package <code>engines.node</code> before <code>openclaw update</code> runs a global package install, so outdated Node runtimes fail with a clear upgrade message instead of attempting an unsupported latest release.</li>
<li>Tasks/chat: add <code>/tasks</code> as a chat-native background task board for the current session, with recent task details and agent-local fallback counts when no linked tasks are visible. Related #54226. Thanks @vincentkoc.</li>
<li>Web search/SearXNG: add the bundled SearXNG provider plugin for <code>web_search</code> with configurable host support. (#57317) Thanks @cgdusek.</li>
<li>Amazon Bedrock/Guardrails: add Bedrock Guardrails support to the bundled provider. (#58588) Thanks @MikeORed.</li>
<li>macOS/Voice Wake: add the Voice Wake option to trigger Talk Mode. (#58490) Thanks @SmoothExec.</li>
<li>Feishu/comments: add a dedicated Drive comment-event flow with comment-thread context resolution, in-thread replies, and <code>feishu_drive</code> comment actions for document collaboration workflows. (#58497) Thanks @wittam-01.</li>
<li>Gateway/webchat: make <code>chat.history</code> text truncation configurable with <code>gateway.webchat.chatHistoryMaxChars</code> and per-request <code>maxChars</code>, while preserving silent-reply filtering and existing default payload limits. (#58900)</li>
<li>Agents/default params: add <code>agents.defaults.params</code> for global default provider parameters. (#58548) Thanks @lpender.</li>
<li>Agents/failover: cap prompt-side and assistant-side same-provider auth-profile retries for rate-limit failures before cross-provider model fallback, add the <code>auth.cooldowns.rateLimitedProfileRotations</code> knob, and document the new fallback behavior. (#58707) Thanks @Forgely3D</li>
<li>Cron/tools allowlist: add <code>openclaw cron --tools</code> for per-job tool allowlists. (#58504) Thanks @andyk-ms.</li>
<li>Channels/session routing: move provider-specific session conversation grammar into plugin-owned session-key surfaces, preserving Telegram topic routing and Feishu scoped inheritance across bootstrap, model override, restart, and tool-policy paths.</li>
<li>WhatsApp/reactions: add <code>reactionLevel</code> guidance for agent reactions. Thanks @mcaxtr.</li>
<li>Telegram/errors: add configurable <code>errorPolicy</code> and <code>errorCooldownMs</code> controls so Telegram can suppress repeated delivery errors per account, chat, and topic without muting distinct failures. (#51914) Thanks @chinar-amrutkar</li>
<li>ZAI/models: add <code>glm-5.1</code> and <code>glm-5v-turbo</code> to the bundled Z.AI provider catalog. (#58793) Thanks @tomsun28</li>
<li>Agents/compaction: resolve <code>agents.defaults.compaction.model</code> consistently for manual <code>/compact</code> and other context-engine compaction paths, so engine-owned compaction uses the configured override model across runtime entrypoints. (#56710) Thanks @oliviareid-svg</li>
</ul>
<h3>Fixes</h3>
<ul>
<li>Outbound media/local files: align outbound media access with the configured fs policy so host-local files and inbound-media paths keep sending when <code>workspaceOnly</code> is off, while strict workspace-only agents remain sandboxed.</li>
<li>Security/sandbox media dispatch: close the <code>mediaUrl</code>/<code>fileUrl</code> alias bypass so outbound tool and message actions cannot escape media-root restrictions. (#54034)</li>
<li>Gateway/restart sentinel: wake the interrupted agent session via heartbeat after restart instead of only sending a best-effort restart note, retry outbound delivery once on transient failure, and preserve explicit thread/topic routing through the wake path so replies land in the correct Telegram topic or Slack thread. (#53940) Thanks @VACInc.</li>
<li>Docker/setup: avoid the pre-start <code>openclaw-cli</code> shared-network namespace loop by routing setup-time onboard/config writes through <code>openclaw-gateway</code>, so fresh Docker installs stop failing before the gateway comes up. (#53385) Thanks @amsminn.</li>
<li>Gateway/channels: keep channel startup sequential while isolating per-channel boot failures, so one broken channel no longer blocks later channels from starting. (#54215) Thanks @JonathanJing.</li>
<li>Embedded runs/secrets: stop unresolved <code>SecretRef</code> config from crashing embedded agent runs by falling back to the resolved runtime snapshot when needed. Fixes #45838.</li>
<li>WhatsApp/groups: track recent gateway-sent message IDs and suppress only matching group echoes, preserving owner <code>/status</code>, <code>/new</code>, and <code>/activation</code> commands from linked-account <code>fromMe</code> traffic. (#53624) Thanks @w-sss.</li>
<li>WhatsApp/reply-to-bot detection: restore implicit group reply detection by unwrapping <code>botInvokeMessage</code> payloads and reading <code>selfLid</code> from <code>creds.json</code>, so reply-based mentions reach the bot again in linked-account group chats.</li>
<li>Telegram/forum topics: recover <code>#General</code> topic <code>1</code> routing when Telegram omits forum metadata, including native commands, interactive callbacks, inbound message context, and fallback error replies. (#53699) thanks @huntharo</li>
<li>Discord/gateway supervision: centralize gateway error handling behind a lifetime-owned supervisor so early, active, and late-teardown Carbon gateway errors stay classified consistently and stop surfacing as process-killing teardown crashes.</li>
<li>Discord/timeouts: send a visible timeout reply when the inbound Discord worker times out before a final reply starts, including created auto-thread targets and queued-run ordering. (#53823) Thanks @Kimbo7870.</li>
<li>ACP/direct chats: always deliver a terminal ACP result when final TTS does not yield audio, even if block text already streamed earlier, and skip redundant empty-text final synthesis. (#53692) Thanks @w-sss.</li>
<li>Telegram/outbound errors: preserve actionable 403 membership/block/kick details and treat <code>bot not a member</code> as a permanent delivery failure so Telegram sends stop retrying doomed chats. (#53635) Thanks @w-sss.</li>
<li>Telegram/photos: preflight Telegram photo dimension and aspect-ratio rules, and fall back to document sends when image metadata is invalid or unavailable so photo uploads stop failing with <code>PHOTO_INVALID_DIMENSIONS</code>. (#52545) Thanks @hnshah.</li>
<li>Slack/runtime defaults: trim Slack DM reply overhead, restore Codex auto transport, and tighten Slack/web-search runtime defaults around DM preview threading, cache scoping, warning dedupe, and explicit web-search opt-in. (#53957) Thanks @vincentkoc.</li>
<li>Chat/error replies: stop leaking raw provider/runtime failures into external chat channels, return a friendly retry message instead, and add a specific <code>/new</code> hint for Bedrock toolResult/toolUse session mismatches. (#58831) Thanks @ImLukeF.</li>
<li>Gateway/reload: ignore startup config writes by persisted hash in the config reloader so generated auth tokens and seeded Control UI origins do not trigger a restart loop, while real <code>gateway.auth.*</code> edits still require restart. (#58678) Thanks @yelog</li>
<li>Tasks/gateway: keep the task registry maintenance sweep from stalling the gateway event loop under synchronous SQLite pressure, so upgraded gateways stop hanging about a minute after startup. (#58670) Thanks @openperf</li>
<li>Tasks/status: hide stale completed background tasks from <code>/status</code> and <code>session_status</code>, prefer live task context, and show recent failures only when no active work remains. (#58661) Thanks @vincentkoc</li>
<li>Tasks/gateway: re-check the current task record before maintenance marks runs lost or prunes them, so a task heartbeat or cleanup update that lands during a sweep no longer gets overwritten by stale snapshot state.</li>
<li>Exec/approvals: honor <code>exec-approvals.json</code> security defaults when inline or configured tool policy is unset, and keep Slack and Discord native approval handling aligned with inferred approvers and real channel enablement so remote exec stops falling into false approval timeouts and disabled states. Thanks @scoootscooob and @vincentkoc.</li>
<li>Exec/approvals: make <code>allow-always</code> persist as durable user-approved trust instead of behaving like <code>allow-once</code>, reuse exact-command trust on shell-wrapper paths that cannot safely persist an executable allowlist entry, keep static allowlist entries from silently bypassing <code>ask:"always"</code>, and require explicit approval when Windows cannot build an allowlist execution plan instead of hard-dead-ending remote exec. Thanks @scoootscooob and @vincentkoc.</li>
<li>Exec/cron: resolve isolated cron no-route approval dead-ends from the effective host fallback policy when trusted automation is allowed, and make <code>openclaw doctor</code> warn when <code>tools.exec</code> is broader than <code>~/.openclaw/exec-approvals.json</code> so stricter host-policy conflicts are explicit. Thanks @scoootscooob and @vincentkoc.</li>
<li>Sessions/model switching: keep <code>/model</code> changes queued behind busy runs instead of interrupting the active turn, and retarget queued followups so later work picks up the new model as soon as the current turn finishes.</li>
<li>Gateway/HTTP: skip failing HTTP request stages so one broken facade no longer forces every HTTP endpoint to return 500. (#58746) Thanks @yelog</li>
<li>Gateway/nodes: stop pinning live node commands to the approved node-pair record. Node pairing remains a trust/token flow, while per-node <code>system.run</code> policy stays in that node's exec approvals config. Fixes #58824.</li>
<li>WebChat/exec approvals: use native approval UI guidance in agent system prompts instead of telling agents to paste manual <code>/approve</code> commands in webchat sessions. Thanks @vincentkoc.</li>
<li>Web UI/OpenResponses: preserve rewritten stream snapshots in webchat and keep OpenResponses final streamed text aligned when models rewind earlier output. (#58641) Thanks @neeravmakwana</li>
<li>Discord/inbound media: pass Discord attachment and sticker downloads through the shared idle-timeout and worker-abort path so slow or stuck inbound media fetches stop hanging message processing. (#58593) Thanks @aquaright1</li>
<li>Telegram/retries: keep non-idempotent sends on the strict safe-send path, retry wrapped pre-connect failures, and preserve <code>429</code> / <code>retry_after</code> backoff for safe delivery retries. (#51895) Thanks @chinar-amrutkar</li>
<li>Telegram/exec approvals: route topic-aware exec approval followups through Telegram-owned threading and approval-target parsing, so forum-topic approvals stay in the originating topic instead of falling back to the root chat. (#58783)</li>
<li>Telegram/local Bot API: preserve media MIME types for absolute-path downloads so local audio files still trigger transcription and other MIME-based handling. (#54603) Thanks @jzakirov</li>
<li>Channels/WhatsApp: pass inbound message timestamp to model context so the AI can see when WhatsApp messages were sent. (#58590) Thanks @Maninae</li>
<li>Channels/QQ Bot: keep <code>/bot-logs</code> export gated behind a truly explicit QQBot allowlist, rejecting wildcard and mixed wildcard entries while preserving the real framework command path. Thanks @vincentkoc.</li>
<li>Channels/plugins: keep bundled channel plugins loadable from legacy <code>channels.<id></code> config even under restrictive plugin allowlists, and make <code>openclaw doctor</code> warn only on real plugin blockers instead of misleading setup guidance. (#58873) Thanks @obviyus</li>
<li>Plugins/bundled runtimes: restore externalized bundled plugin runtime dependency staging across packed installs, Docker builds, and local runtime staging so bundled plugins keep their declared runtime deps after the 2026.3.31 externalization change. (#58782)</li>
<li>LINE/runtime: resolve the packaged runtime contract from the built <code>dist/plugins/runtime</code> layout so LINE channels start correctly again after global npm installs on <code>2026.3.31</code>. (#58799) Thanks @vincentkoc.</li>
<li>MiniMax/plugins: auto-enable the bundled MiniMax plugin for API-key auth/config so MiniMax image generation and other plugin-owned capabilities load without manual plugin allowlisting. (#57127) Thanks @tars90percent.</li>
<li>Ollama/model picker: show only Ollama models after provider selection in the CLI picker. (#55290) Thanks @Luckymingxuan.</li>
<li>CDP/profiles: prefer <code>cdpPort</code> over stale WebSocket URLs so browser automation reconnects cleanly. (#58499) Thanks @Mlightsnow.</li>
<li>Media/paths: resolve relative <code>MEDIA</code> paths against the agent workspace so local attachment references keep working. (#58624) Thanks @aquaright1.</li>
<li>Memory/session indexing: keep full reindexes from skipping session transcripts when sync is triggered by <code>session-start</code> or <code>watch</code>, so restart-driven reindexes preserve session memory. (#39732) Thanks @upupc</li>
<li>Memory/QMD: prefer <code>--mask</code> over <code>--glob</code> when creating QMD collections so default memory collections keep their intended patterns and stop colliding on restart. (#58643) Thanks @GitZhangChi.</li>
<li>Subagents/tasks: keep subagent completion and cleanup from crashing when task-registry writes fail, so a corrupt or missing task row no longer takes down the gateway during lifecycle finalization. Thanks @vincentkoc.</li>
<li>Sandbox/browser: compare browser runtime inspection against <code>agents.defaults.sandbox.browser.image</code> so <code>openclaw sandbox list --browser</code> stops reporting healthy browser containers as image mismatches. (#58759) Thanks @sandpile.</li>
<li>Plugins/install: forward <code>--dangerously-force-unsafe-install</code> through archive and npm-spec plugin installs so the documented override reaches the security scanner on those install paths. (#58879) Thanks @ryanlee-gemini.</li>
<li>Auto-reply/commands: strip inbound metadata before slash command detection so wrapped <code>/model</code>, <code>/new</code>, and <code>/status</code> commands are recognized. (#58725) Thanks @Mlightsnow.</li>
<li>Agents/Anthropic: preserve thinking blocks and signatures across replay, cache-control patching, and context pruning so compacted Anthropic sessions continue working instead of failing on later turns. (#58916) Thanks @obviyus</li>
<li>Agents/failover: unify structured and raw provider error classification so provider-specific <code>400</code>/<code>422</code> payloads no longer get forced into generic format failures before retry, billing, or compaction logic can inspect them. (#58856) Thanks @aaron-he-zhu.</li>
<li>Auth profiles/store: coerce misplaced SecretRef objects out of plaintext <code>key</code> and <code>token</code> fields during store load so agents without ACP runtime stop crashing on <code>.trim()</code> after upgrade. (#58923) Thanks @openperf.</li>
<li>ACPX/runtime: repair <code>queue owner unavailable</code> 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</li>
<li>ACPX/runtime: retry dead-session queue-owner repair without <code>--resume-session</code> when the reported ACPX session id is stale, so recovery still creates a fresh named session instead of failing session init. Thanks @obviyus.</li>
<li>Auth/OpenAI Codex: persist plugin-refreshed OAuth credentials to <code>auth-profiles.json</code> before returning them, so rotated Codex refresh tokens survive restart and stop falling into <code>refresh_token_reused</code> loops. (#53082)</li>
<li>Discord/gateway: hand reconnect ownership back to Carbon, keep runtime status aligned with close/reconnect state, and force-stop sockets that open without reaching READY so Discord monitors recover promptly instead of waiting on stale health timeouts. (#59019) Thanks @obviyus</li>
</ul>
<p><a href="https://github.com/openclaw/openclaw/blob/main/CHANGELOG.md">View full changelog</a></p>
]]></description>
<enclosure url="https://github.com/openclaw/openclaw/releases/download/v2026.3.24/OpenClaw-2026.3.24.zip" length="24749233" type="application/octet-stream" sparkle:edSignature="gLm2VvI+PPEnNy4klYSs9WmZLkJTF5BcfFparrtPdnmeE4xgc8kFfICg445I039ev9/A6xGav7pm08reUHDcAg=="/>
<enclosure url="https://github.com/openclaw/openclaw/releases/download/v2026.4.1/OpenClaw-2026.4.1.zip" length="25841903" type="application/octet-stream" sparkle:edSignature="0TPiyshScmwDbgs626JU08NOUUFJmIsVFa5g0xmizfl64Fr+IoT4l/dkXarFqbZAJidtj5WN7Bff7fG8ye/7AA=="/>
</item>
<item>
<title>2026.3.23</title>
<pubDate>Mon, 23 Mar 2026 16:59:51 -0700</pubDate>
<title>2026.3.31</title>
<pubDate>Tue, 31 Mar 2026 21:47:15 +0000</pubDate>
<link>https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml</link>
<sparkle:version>2026032390</sparkle:version>
<sparkle:shortVersionString>2026.3.23</sparkle:shortVersionString>
<sparkle:version>2026033190</sparkle:version>
<sparkle:shortVersionString>2026.3.31</sparkle:shortVersionString>
<sparkle:minimumSystemVersion>15.0</sparkle:minimumSystemVersion>
<description><![CDATA[<h2>OpenClaw 2026.3.23</h2>
<description><![CDATA[<h2>OpenClaw 2026.3.31</h2>
<h3>Breaking</h3>
<ul>
<li>Nodes/exec: remove the duplicated <code>nodes.run</code> shell wrapper from the CLI and agent <code>nodes</code> tool so node shell execution always goes through <code>exec host=node</code>, keeping node-specific capabilities on <code>nodes invoke</code> and the dedicated media/location/notify actions.</li>
<li>Plugin SDK: deprecate the legacy provider compat subpaths plus the older bundled provider setup and channel-runtime compatibility shims, emit migration warnings, and keep the current documented <code>openclaw/plugin-sdk/*</code> entrypoints plus local <code>api.ts</code> / <code>runtime-api.ts</code> barrels as the forward path ahead of a future major-release removal.</li>
<li>Skills/install and Plugins/install: built-in dangerous-code <code>critical</code> findings and install-time scan failures now fail closed by default, so plugin installs and gateway-backed skill dependency installs that previously succeeded may now require an explicit dangerous override such as <code>--dangerously-force-unsafe-install</code> to proceed.</li>
<li>Gateway/auth: <code>trusted-proxy</code> now rejects mixed shared-token configs, and local-direct fallback requires the configured token instead of implicitly authenticating same-host callers. Thanks @zhangning-agent, @jacobtomlinson, and @vincentkoc.</li>
<li>Gateway/node commands: node commands now stay disabled until node pairing is approved, so device pairing alone is no longer enough to expose declared node commands. (#57777) Thanks @jacobtomlinson.</li>
<li>Gateway/node events: node-originated runs now stay on a reduced trusted surface, so notification-driven or node-triggered flows that previously relied on broader host/session tool access may need adjustment. (#57691) Thanks @jacobtomlinson.</li>
</ul>
<h3>Changes</h3>
<ul>
<li>ACP/plugins: add an explicit default-off ACPX plugin-tools MCP bridge config, document the trust boundary, and harden the built-in bridge packaging/logging path so global installs and stdio MCP sessions work reliably. (#56867) Thanks @joe2643.</li>
<li>Agents/LLM: add a configurable idle-stream timeout for embedded runner requests so stalled model streams abort cleanly instead of hanging until the broader run timeout fires. (#55072) Thanks @liuy.</li>
<li>Agents/MCP: materialize bundle MCP tools with provider-safe names (<code>serverName__toolName</code>), support optional <code>streamable-http</code> transport selection plus per-server connection timeouts, and preserve real tool results from aborted/error turns unless truncation explicitly drops them. (#49505) Thanks @ziomancer.</li>
<li>Android/notifications: add notification-forwarding controls with package filtering, quiet hours, rate limiting, and safer picker behavior for forwarded notification events. (#40175) Thanks @nimbleenigma.</li>
<li>Background tasks: turn tasks into a real shared background-run control plane instead of ACP-only bookkeeping by unifying ACP, subagent, cron, and background CLI execution under one SQLite-backed ledger, routing detached lifecycle updates through the executor seam, adding audit/maintenance/status visibility, tightening auto-cleanup and lost-run recovery, improving task awareness in internal status/tool surfaces, and clarifying the split between heartbeat/main-session automation and detached scheduled runs. Thanks @mbelinky and @vincentkoc.</li>
<li>Background tasks: add the first linear task flow control surface with <code>openclaw flows list|show|cancel</code>, keep manual multi-task flows separate from one-task auto-sync flows, and surface doctor recovery hints for obviously orphaned or broken flow/task linkage. Thanks @mbelinky and @vincentkoc.</li>
<li>Channels/QQ Bot: add QQ Bot as a bundled channel plugin with multi-account setup, SecretRef-aware credentials, slash commands, reminders, and media send/receive support. (#52986) Thanks @sliverp.</li>
<li>Diffs: skip unused viewer-versus-file SSR preload work so <code>diffs</code> view-only and file-only runs do less render work while keeping mode outputs aligned. (#57909) thanks @gumadeiras.</li>
<li>Tasks: add a minimal SQLite-backed task flow registry plus task-to-flow linkage scaffolding, so orchestrated work can start gaining a first-class parent record without changing current task delivery behavior. Thanks @mbelinky and @vincentkoc.</li>
<li>Tasks: persist blocked state on one-task task flows and let the same flow reopen cleanly on retry, so blocked detached work can carry a parent-level reason and continue without fragmenting into a new job. Thanks @mbelinky and @vincentkoc.</li>
<li>Tasks: route one-task ACP and subagent updates through a parent task-flow owner context, so detached work can emerge back through the intended parent thread/session instead of speaking only as a raw child task. Thanks @mbelinky and @vincentkoc.</li>
<li>LINE/outbound media: add LINE image, video, and audio outbound sends on the LINE-specific delivery path, including explicit preview/tracking handling for videos while keeping generic media sends on the existing image-only route. (#45826) Thanks @masatohoshino.</li>
<li>Matrix/history: add optional room history context for Matrix group triggers via <code>channels.matrix.historyLimit</code>, with per-agent watermarks and retry-safe snapshots so failed trigger retries do not drift into newer room messages. (#57022) thanks @chain710.</li>
<li>Matrix/network: add explicit <code>channels.matrix.proxy</code> config for routing Matrix traffic through an HTTP(S) proxy, including account-level overrides and matching probe/runtime behavior. (#56931) thanks @patrick-yingxi-pan.</li>
<li>Matrix/streaming: add draft streaming so partial Matrix replies update the same message in place instead of sending a new message for each chunk. (#56387) Thanks @jrusz.</li>
<li>Matrix/threads: add per-DM <code>threadReplies</code> overrides and keep thread session isolation aligned with the effective room or DM thread policy from the triggering message onward. (#57995) thanks @teconomix.</li>
<li>MCP: add remote HTTP/SSE server support for <code>mcp.servers</code> URL configs, including auth headers and safer config redaction for MCP credentials. (#50396) Thanks @dhananjai1729.</li>
<li>Memory/QMD: add per-agent <code>memorySearch.qmd.extraCollections</code> so agents can opt into cross-agent session search without flattening every transcript collection into one shared QMD namespace. Thanks @vincentkoc.</li>
<li>Microsoft Teams/member info: add a Graph-backed member info action so Teams automations and tools can resolve channel member details directly from Microsoft Graph. (#57528) Thanks @sudie-codes.</li>
<li>Nostr/inbound DMs: verify inbound event signatures before pairing or sender-authorization side effects, so forged DM events no longer create pairing requests or trigger reply attempts. Thanks @smaeljaish771 and @vincentkoc.</li>
<li>OpenAI/Responses: forward configured <code>text.verbosity</code> across Responses HTTP and WebSocket transports, surface it in <code>/status</code>, and keep per-agent verbosity precedence aligned with runtime behavior. (#47106) Thanks @merc1305 and @vincentkoc.</li>
<li>Pi/Codex: add native Codex web search support for embedded Pi runs, including config/docs/wizard coverage and managed-tool suppression when native Codex search is active. (#46579) Thanks @Evizero.</li>
<li>Slack/exec approvals: add native Slack approval routing and approver authorization so exec approval prompts can stay in Slack instead of falling back to the Web UI or terminal. Thanks @vincentkoc.</li>
<li>TTS: Add structured provider diagnostics and fallback attempt analytics. (#57954) Thanks @joshavant.</li>
<li>WhatsApp/reactions: agents can now react with emoji on incoming WhatsApp messages, enabling more natural conversational interactions like acknowledging a photo with ❤️ instead of typing a reply. Thanks @mcaxtr.</li>
<li>Agents/BTW: force <code>/btw</code> side questions to disable provider reasoning so Anthropic adaptive-thinking sessions stop failing with <code>No BTW response generated</code>. Fixes #55376. Thanks @Catteres and @vincentkoc.</li>
<li>CLI/onboarding: reset the remote gateway URL prompt to the safe loopback default after declining a discovered endpoint, so onboarding does not keep a previously rejected remote URL. (#57828)</li>
<li>Agents/exec defaults: honor per-agent <code>tools.exec</code> defaults when no inline directive or session override is present, so configured exec host, security, ask, and node settings actually apply. (#57689)</li>
<li>Sandbox/networking: sanitize SSH subprocess env vars through the shared sandbox policy and route marketplace archive downloads plus Ollama discovery, auth, and pull requests through the guarded fetch path so sandboxed execution and remote fetches follow the repo's trust boundaries. (#57848, #57850)</li>
</ul>
<h3>Fixes</h3>
<ul>
<li>Browser/Chrome MCP: wait for existing-session browser tabs to become usable after attach instead of treating the initial Chrome MCP handshake as ready, which reduces user-profile timeouts and repeated consent churn on macOS Chrome attach flows. Fixes #52930. Thanks @vincentkoc.</li>
<li>Browser/CDP: reuse an already-running loopback browser after a short initial reachability miss instead of immediately falling back to relaunch detection, which fixes second-run browser start/open regressions on slower headless Linux setups. Fixes #53004. Thanks @vincentkoc.</li>
<li>ClawHub/macOS auth: honor macOS auth config and XDG auth paths for saved ClawHub credentials, so <code>openclaw skills ...</code> and gateway skill browsing keep using the signed-in auth state instead of silently falling back to unauthenticated mode. Fixes #53034.</li>
<li>ClawHub/macOS: read the local ClawHub login from the macOS Application Support path and still honor XDG config on macOS, so skill browsing uses the logged-in token on both default and XDG-style setups. Fixes #52949. Thanks @scoootscooob.</li>
<li>ClawHub/skills: resolve the local ClawHub auth token for gateway skill browsing and switch browse-all requests to search so ClawControl stops falling into unauthenticated 429s and empty authenticated skill lists. Fixes #52949. Thanks @vincentkoc.</li>
<li>Plugins/message tool: make Discord <code>components</code> and Slack <code>blocks</code> optional again, and route Feishu <code>message(..., media=...)</code> sends through the outbound media path, so pin/unpin/react flows stop failing schema validation and Feishu file/image attachments actually send. Fixes #52970 and #52962. Thanks @vincentkoc.</li>
<li>Gateway/model pricing: stop <code>openrouter/auto</code> pricing refresh from recursing indefinitely during bootstrap, so OpenRouter auto routes can populate cached pricing and <code>usage.cost</code> again. Fixes #53035. Thanks @vincentkoc.</li>
<li>Mistral/models: lower bundled Mistral max-token defaults to safe output budgets and teach <code>openclaw doctor --fix</code> to repair old persisted Mistral provider configs that still carry context-sized output limits, avoiding deterministic Mistral 422 rejects on fresh and existing setups. Fixes #52599. Thanks @vincentkoc.</li>
<li>Agents/web_search: use the active runtime <code>web_search</code> provider instead of stale/default selection, so agent turns keep hitting the provider you actually configured. Fixes #53020. Thanks @jzakirov.</li>
<li>Models/OpenAI Codex OAuth: bootstrap the env-configured HTTP/HTTPS proxy dispatcher on the stored-credential refresh path before token renewal runs, so expired Codex OAuth profiles can refresh successfully in proxy-required environments instead of locking users out after the first token expiry.</li>
<li>Plugins/memory-lancedb: bootstrap LanceDB into plugin runtime state on first use when the bundled npm install does not already have it, so <code>plugins.slots.memory="memory-lancedb"</code> works again after global npm installs without moving LanceDB into OpenClaw core dependencies. Fixes #26100.</li>
<li>Config/plugins: treat stale unknown <code>plugins.allow</code> ids as warnings instead of fatal config errors, so recovery commands like <code>plugins install</code>, <code>doctor --fix</code>, and <code>status</code> still run when a plugin is missing locally. Fixes #52992. Thanks @vincentkoc.</li>
<li>Doctor/WhatsApp: stop auto-enable from appending built-in channel ids like <code>whatsapp</code> to <code>plugins.allow</code>, so <code>openclaw doctor --fix</code> no longer writes schema-invalid plugin allowlist entries when repairing built-in channels. Fixes #52931. Thanks @vincentkoc.</li>
<li>Telegram/auto-reply: preserve same-chat inbound debounce order without stranding stale busy-session followups, and keep same-key overflow turns ordered when tracked debounce keys are saturated. (#52998) Thanks @osolmaz.</li>
<li>Discord/commands: return an explicit unauthorized reply for privileged native slash commands instead of falling through to Discord's misleading generic completion when auth gates reject the sender. Fixes #53041. Thanks @scoootscooob.</li>
<li>Channels/catalog: let external channel catalogs override shipped fallback metadata and honor overridden npm specs during channel setup, so custom channel catalogs no longer fall back to bundled packages when a channel id matches. (#52988)</li>
<li>Voice-call/Plivo: stabilize Plivo v2 replay keys so webhook retries and replay protection stop colliding on valid follow-up deliveries.</li>
<li>Agents/skills: prefer the active resolved runtime snapshot for embedded skill config and env injection, so <code>skills.entries.<skill>.apiKey</code> SecretRefs resolve correctly during embedded startup instead of failing on raw source config. Fixes #53098. Thanks @vincentkoc.</li>
<li>Agents/subagents: recheck timed-out worker waits against the latest runtime snapshot before sending completion events, so fast-finishing workers stop being reported as timed out when they actually succeeded. Fixes #53106. Thanks @vincentkoc.</li>
<li>Agents/Anthropic: preserve latest assistant thinking and redacted-thinking block ordering during transcript image sanitization so follow-up turns do not trip Anthropic's unmodified-thinking validation. (#52961) Thanks @vincentkoc.</li>
<li>Gateway/probe: stop successful gateway handshakes from timing out as unreachable while post-connect detail RPCs are still loading, so slow devices report a reachable RPC failure instead of a false negative dead gateway. Fixes #52927. Thanks @vincentkoc.</li>
<li>Gateway/supervision: stop lock conflicts from crash-looping under launchd and systemd by keeping the duplicate process in a retry wait instead of exiting as a failure while another healthy gateway still owns the lock. Fixes #52922. Thanks @vincentkoc.</li>
<li>Gateway/auth: require auth for canvas routes and admin scope for agent session reset, so anonymous canvas access and non-admin reset requests fail closed.</li>
<li>Release/install: keep previously released bundled plugins and Control UI assets in published openclaw npm installs, and fail release checks when those shipped artifacts are missing. Thanks @vincentkoc.</li>
<li>Slack: stop retry-driven duplicate replies when draft-finalization edits fail ambiguously, and log configured allowlisted users/channels by readable name instead of raw IDs.</li>
<li>Agents/OpenAI Responses: normalize raw bundled MCP tool schemas on the WebSocket/Responses path so bare-object, object-ish, and top-level union MCP tools no longer get rejected by OpenAI during tool registration. (#58299) Thanks @yelog.</li>
<li>ACP/security: replace ACP's dangerous-tool name override with semantic approval classes, so only narrow readonly reads/searches can auto-approve while indirect exec-capable and control-plane tools always require explicit prompt approval. Thanks @vincentkoc.</li>
<li>ACP/sessions_spawn: register ACP child runs for completion tracking and lifecycle cleanup, and make registration-failure cleanup explicitly best-effort so callers do not assume an already-started ACP turn was fully aborted. (#40885) Thanks @xaeon2026 and @vincentkoc.</li>
<li>ACP/tasks: mark cleanly exited ACP runs as blocked when they end on deterministic write or authorization blockers, and wake the parent session with a follow-up instead of falsely reporting success.</li>
<li>ACPX/runtime: derive the bundled ACPX expected version from the extension package metadata instead of hardcoding a separate literal, so plugin-local ACPX installs stop drifting out of health-check parity after version bumps. (#49089) Thanks @jiejiesks and @vincentkoc.</li>
<li>Agents/Anthropic failover: treat Anthropic <code>api_error</code> payloads with <code>An unexpected error occurred while processing the response</code> as transient so retry/fallback can engage instead of surfacing a terminal failure. (#57441) Thanks @zijiess and @vincentkoc.</li>
<li>Agents/compaction: keep late compaction-retry completions from double-resolving finished compaction futures, so interrupted or timed-out compactions stop surfacing spurious second-completion races. (#57796) Thanks @joshavant.</li>
<li>Agents/disabled providers: make disabled providers disappear from default model selection and embedded provider fallback, while letting explicitly pinned disabled providers fail with a clear config error instead of silently taking traffic. (#57735) Thanks @rileybrown-dev and @vincentkoc.</li>
<li>Agents/OAuth output: force exec-host OAuth output readers through the gateway fs policy so embedded gateway runs stop crashing when provider auth writes land outside the current sandbox workspace. (#58249) Thanks @joshavant.</li>
<li>Agents/system prompt: fix <code>agent.name</code> interpolation in the embedded runtime system prompt and make provider/model fallback text reflect the effective runtime selection after start. (#57625) Thanks @StllrSvr and @vincentkoc.</li>
<li>Android/device info: read the app's version metadata from the package manager instead of hidden APIs so Android 15+ onboarding and device info no longer fail to compile or report placeholder values. (#58126) Thanks @L3ER0Y.</li>
<li>Android/pairing: stop appending duplicate push receiver entries to <code>gateway-service.conf</code> on repeated QR pairing and keep push registration bounded to the current successful pairing, so Android push delivery stays healthy across re-pair and token rotation. (#58256) Thanks @surrealroad.</li>
<li>App install smoke: pin the latest-release lookup to <code>latest</code>, cache the first stable install version across the rerun, and relax prerelease package assertions so the Parallels smoke lane can validate stable-to-main upgrades even when <code>beta</code> moves ahead or the guest starts from an older stable. (#58177) Thanks @vincentkoc.</li>
<li>Auth/profiles: keep the last successful config load in memory for the running process and refresh that snapshot on successful writes/reloads, so hot paths stop reparsing <code>openclaw.json</code> between watcher-driven swaps.</li>
<li>Config/SecretRef + Control UI: harden SecretRef redaction round-trip restore, block unsafe raw fallback (force Form mode when raw is unavailable), and preflight submitted-config SecretRefs before config write RPC persistence. (#58044) Thanks @joshavant.</li>
<li>Config/Telegram: migrate removed <code>channels.telegram.groupMentionsOnly</code> into <code>channels.telegram.groups[\"*\"].requireMention</code> on load so legacy configs no longer crash at startup. (#55336) thanks @jameslcowan.</li>
<li>Config/update: stop <code>openclaw doctor</code> write-backs from persisting plugin-injected channel defaults, so <code>openclaw update</code> no longer seeds config keys that later break service refresh validation. (#56834) Thanks @openperf.</li>
<li>Control UI/agents: auto-load agent workspace files on initial Files panel open, and populate overview model/workspace/fallbacks from effective runtime agent metadata so defaulted models no longer show as <code>Not set</code>. (#56637) Thanks @dxsx84.</li>
<li>Control UI/slash commands: make <code>/steer</code> and <code>/redirect</code> work from the chat command palette with visible pending state for active-run <code>/steer</code>, correct redirected-run tracking, and a single canonical <code>/steer</code> entry in the command menu. (#54625) Thanks @fuller-stack-dev.</li>
<li>Cron/announce: preserve all deliverable text payloads for announce mode instead of collapsing to the last chunk, so multi-line cron reports deliver in full to Telegram forum topics.</li>
<li>Cron/isolated sessions: carry the full live-session provider, model, and auth-profile selection across retry restarts so cron jobs with model overrides no longer fail or loop on mid-run model-switch requests. (#57972) Thanks @issaba1.</li>
<li>Diffs/config: preserve schema-shaped plugin config parsing from <code>diffsPluginConfigSchema.safeParse()</code>, so direct callers keep <code>defaults</code> and <code>security</code> sections instead of receiving flattened tool defaults. (#57904) Thanks @gumadeiras.</li>
<li>Diffs: fall back to plain text when <code>lang</code> hints are invalid during diff render and viewer hydration, so bad or stale language values no longer break the diff viewer. (#57902) Thanks @gumadeiras.</li>
<li>Discord/voice: enforce the same guild channel and member allowlist checks on spoken voice ingress before transcription, so joined voice channels no longer accept speech from users outside the configured Discord access policy. Thanks @cyjhhh and @vincentkoc.</li>
<li>Docker/setup: force BuildKit for local image builds (including sandbox image builds) so <code>./docker-setup.sh</code> no longer fails on <code>RUN --mount=...</code> when hosts default to Docker's legacy builder. (#56681) Thanks @zhanghui-china.</li>
<li>Docs/anchors: fix broken English docs links and make Mint anchor audits run against the English-source docs tree. (#57039) thanks @velvet-shark.</li>
<li>Doctor/plugins: skip false Matrix legacy-helper warnings when no migration plans exist, and keep bundled <code>enabledByDefault</code> plugins in the gateway startup set. (#57931) Thanks @dinakars777.</li>
<li>Exec approvals/macOS: unwrap <code>arch</code> and <code>xcrun</code> before deriving shell payloads and allow-always patterns, so wrapper approvals stay bound to the carried command instead of the outer carrier. Thanks @tdjackey and @vincentkoc.</li>
<li>Exec approvals: unwrap <code>caffeinate</code> and <code>sandbox-exec</code> before persisting allow-always trust so later shell payload changes still require a fresh approval. Thanks @tdjackey and @vincentkoc.</li>
<li>Exec/approvals: infer Discord and Telegram exec approvers from existing owner config when <code>execApprovals.approvers</code> is unset, extend the default approval window to 30 minutes, and clarify approval-unavailable guidance so approvals do not appear to silently disappear.</li>
<li>Pi/TUI: flush message-boundary replies at <code>message_end</code> so turns stop looking stuck until the next nudge when the final reply was already ready. Thanks @vincentkoc.</li>
<li>Exec/approvals: keep <code>awk</code> and <code>sed</code> family binaries out of the low-risk <code>safeBins</code> fast path, and stop doctor profile scaffolding from treating them like ordinary custom filters. Thanks @vincentkoc.</li>
<li>Exec/env: block proxy, TLS, and Docker endpoint env overrides in host execution so request-scoped commands cannot silently reroute outbound traffic or trust attacker-supplied certificate settings. Thanks @AntAISecurityLab.</li>
<li>Exec/env: block Python package index override variables from request-scoped host exec environment sanitization so package fetches cannot be redirected through a caller-supplied index. Thanks @nexrin and @vincentkoc.</li>
<li>Exec/node: stop gateway-side workdir fallback from rewriting explicit <code>host=node</code> cwd values to the gateway filesystem, so remote node exec approval and runs keep using the intended node-local directory. (#50961) Thanks @openperf.</li>
<li>Exec/runtime: default implicit exec to <code>host=auto</code>, resolve that target to sandbox only when a sandbox runtime exists, keep explicit <code>host=sandbox</code> fail-closed without sandbox, and show <code>/exec</code> effective host state in runtime status/docs.</li>
<li>Exec: fail closed when the implicit sandbox host has no sandbox runtime, and stop denied async approval followups from reusing prior command output from the same session. (#56800) Thanks @scoootscooob.</li>
<li>Feishu/groups: keep quoted replies and topic bootstrap context aligned with group sender allowlists so only allowlisted thread messages seed agent context. Thanks @AntAISecurityLab and @vincentkoc.</li>
<li>Gateway/attachments: offload large inbound images without leaking <code>media://</code> markers into text-only runs, preserve mixed attachment order for model input/transcripts, and fail closed when model image capability cannot be resolved. (#55513) Thanks @Syysean.</li>
<li>Gateway/auth: keep shared-auth rate limiting active during WebSocket handshake attempts even when callers also send device-token candidates, so bogus device-token fields no longer suppress shared-secret brute-force tracking. Thanks @kexinoh and @vincentkoc.</li>
<li>Gateway/auth: reject mismatched browser <code>Origin</code> headers on trusted-proxy HTTP operator requests while keeping origin-less headless proxy clients working. Thanks @AntAISecurityLab and @vincentkoc.</li>
<li>Gateway/device tokens: disconnect active device sessions after token rotation so newly rotated credentials revoke existing live connections immediately instead of waiting for those sockets to close naturally. Thanks @zsxsoft and @vincentkoc.</li>
<li>Gateway/health: carry webhook-vs-polling account mode from channel descriptors into runtime snapshots so passive channels like LINE and BlueBubbles skip false stale-socket health failures. (#47488) Thanks @karesansui-u.</li>
<li>Gateway/pairing: restore QR bootstrap onboarding handoff so fresh <code>/pair qr</code> iPhone setup can auto-approve the initial node pairing, receive a reusable node device token, and stop retrying with spent bootstrap auth. (#58382) Thanks @ngutman.</li>
<li>Gateway/OpenAI compatibility: accept flat Responses API function tool definitions on <code>/v1/responses</code> and preserve <code>strict</code> when normalizing hosted tools into the embedded runner, so spec-compliant clients like Codex no longer fail validation or silently lose strict tool enforcement. Thanks @malaiwah and @vincentkoc.</li>
<li>Gateway/OpenAI HTTP: restore default operator scopes for bearer-authenticated requests that omit <code>x-openclaw-scopes</code>, so headless <code>/v1/chat/completions</code> and session-history callers work again after the recent method-scope hardening. (#57596) Thanks @openperf.</li>
<li>Gateway/plugins: scope plugin-auth HTTP route runtime clients to read-only access and keep gateway-authenticated plugin routes on write scope, so plugin-owned webhook handlers do not inherit write-capable runtime access by default. Thanks @davidluzsilva and @vincentkoc.</li>
<li>Gateway/SecretRef: resolve restart token drift checks with merged service/runtime env sources and hard-fail unsupported mutable SecretRef plus OAuth-profile combinations so restart warnings and policy enforcement match runtime behavior. (#58141) Thanks @joshavant.</li>
<li>Gateway/tools HTTP: tighten HTTP tool-invoke authorization so owner-only tools stay off HTTP invoke paths. (#57773) Thanks @jacobtomlinson.</li>
<li>Harden async approval followup delivery in webchat-only sessions (#57359) Thanks @joshavant.</li>
<li>Heartbeat/auth: prevent exec-event heartbeat runs from inheriting owner-only tool access from the session delivery target, so node exec output stays on the non-owner tool surface even when the target session belongs to the owner. Thanks @AntAISecurityLab and @vincentkoc.</li>
<li>Hooks/config: accept runtime channel plugin ids in <code>hooks.mappings[].channel</code> (for example <code>feishu</code>) instead of rejecting non-core channels during config validation. (#56226) Thanks @AiKrai001.</li>
<li>Hooks/session routing: rebind hook-triggered <code>agent:</code> session keys to the actual target agent before isolated dispatch so dedicated hook agents keep their own session-scoped tool and plugin identity. Thanks @kexinoh and @vincentkoc.</li>
<li>Host exec/env: block additional request-scoped env overrides that can redirect Docker endpoints, trust roots, compiler include paths, package resolution, or Python environment roots during approved host runs. Thanks @tdjackey and @vincentkoc.</li>
<li>Image generation/build: write stable runtime alias files into <code>dist/</code> and route provider-auth runtime lookups through those aliases so image-generation providers keep resolving auth/runtime modules after rebuilds instead of crashing on missing hashed chunk files.</li>
<li>iOS/Live Activities: mark the <code>ActivityKit</code> import in <code>LiveActivityManager.swift</code> as <code>@preconcurrency</code> so Xcode 26.4 / Swift 6 builds stop failing on strict concurrency checks. (#57180) Thanks @ngutman.</li>
<li>LINE/ACP: add current-conversation binding and inbound binding-routing parity so <code>/acp spawn ... --thread here</code>, configured ACP bindings, and active conversation-bound ACP sessions work on LINE like the other conversation channels.</li>
<li>LINE/markdown: preserve underscores inside Latin, Cyrillic, and CJK words when stripping markdown, while still removing standalone <code>_italic_</code> markers on the shared text-runtime path used by LINE and TTS. (#47465) Thanks @jackjin1997.</li>
<li>Agents/failover: make overloaded same-provider retry count and retry delay configurable via <code>auth.cooldowns</code>, default to one retry with no delay, and document the model-fallback behavior.</li>
</ul>
<p><a href="https://github.com/openclaw/openclaw/blob/main/CHANGELOG.md">View full changelog</a></p>
]]></description>
<enclosure url="https://github.com/openclaw/openclaw/releases/download/v2026.3.23/OpenClaw-2026.3.23.zip" length="24522883" type="application/octet-stream" sparkle:edSignature="ptBgHYLBqq/TSdONYCfIB5d6aP/ij/9G0gYQ5mJI9jf8Y31sbQIh5CqpJVxEEWLTMIGQKsHQir/kXZjtRvvZAg=="/>
<enclosure url="https://github.com/openclaw/openclaw/releases/download/v2026.3.31/OpenClaw-2026.3.31.zip" length="25820093" type="application/octet-stream" sparkle:edSignature="NjpuH/j7OaNASEatBTpQ4uQy6+oUNq/lIwjrY69rJfkgGSk3/kU8vgxo9osjSgx034m7TpuZvWyulu57OBsQCg=="/>
</item>
</channel>
</rss>
</rss>

View File

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

View File

@@ -76,10 +76,17 @@
android:exported="true"
android:windowSoftInputMode="adjustResize"
android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize|uiMode|density|keyboard|keyboardHidden|navigation">
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/shortcuts" />
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.ASSIST" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@@ -0,0 +1,43 @@
package ai.openclaw.app
import android.content.Intent
const val actionAskOpenClaw = "ai.openclaw.app.action.ASK_OPENCLAW"
const val extraAssistantPrompt = "prompt"
enum class HomeDestination {
Connect,
Chat,
Voice,
Screen,
Settings,
}
data class AssistantLaunchRequest(
val source: String,
val prompt: String?,
val autoSend: Boolean,
)
fun parseAssistantLaunchIntent(intent: Intent?): AssistantLaunchRequest? {
val action = intent?.action ?: return null
return when (action) {
Intent.ACTION_ASSIST ->
AssistantLaunchRequest(
source = "assist",
prompt = null,
autoSend = false,
)
actionAskOpenClaw -> {
val prompt = intent.getStringExtra(extraAssistantPrompt)?.trim()?.ifEmpty { null }
AssistantLaunchRequest(
source = "app_action",
prompt = prompt,
autoSend = prompt != null,
)
}
else -> null
}
}

View File

@@ -23,6 +23,7 @@ class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
handleAssistantIntent(intent)
WindowCompat.setDecorFitsSystemWindows(window, false)
permissionRequester = PermissionRequester(this)
@@ -70,4 +71,15 @@ class MainActivity : ComponentActivity() {
viewModel.setForeground(false)
super.onStop()
}
override fun onNewIntent(intent: android.content.Intent) {
super.onNewIntent(intent)
setIntent(intent)
handleAssistantIntent(intent)
}
private fun handleAssistantIntent(intent: android.content.Intent?) {
val request = parseAssistantLaunchIntent(intent) ?: return
viewModel.handleAssistantLaunch(request)
}
}

View File

@@ -27,6 +27,12 @@ class MainViewModel(app: Application) : AndroidViewModel(app) {
private val prefs = nodeApp.prefs
private val runtimeRef = MutableStateFlow<NodeRuntime?>(null)
private var foreground = true
private val _requestedHomeDestination = MutableStateFlow<HomeDestination?>(null)
val requestedHomeDestination: StateFlow<HomeDestination?> = _requestedHomeDestination
private val _chatDraft = MutableStateFlow<String?>(null)
val chatDraft: StateFlow<String?> = _chatDraft
private val _pendingAssistantAutoSend = MutableStateFlow<String?>(null)
val pendingAssistantAutoSend: StateFlow<String?> = _pendingAssistantAutoSend
private fun ensureRuntime(): NodeRuntime {
runtimeRef.value?.let { return it }
@@ -91,6 +97,7 @@ class MainViewModel(app: Application) : AndroidViewModel(app) {
val manualPort: StateFlow<Int> = prefs.manualPort
val manualTls: StateFlow<Boolean> = prefs.manualTls
val gatewayToken: StateFlow<String> = prefs.gatewayToken
val gatewayBootstrapToken: StateFlow<String> = prefs.gatewayBootstrapToken
val onboardingCompleted: StateFlow<Boolean> = prefs.onboardingCompleted
val canvasDebugStatusEnabled: StateFlow<Boolean> = prefs.canvasDebugStatusEnabled
val speakerEnabled: StateFlow<Boolean> = prefs.speakerEnabled
@@ -245,6 +252,29 @@ class MainViewModel(app: Application) : AndroidViewModel(app) {
ensureRuntime().setVoiceScreenActive(active)
}
fun handleAssistantLaunch(request: AssistantLaunchRequest) {
_requestedHomeDestination.value = HomeDestination.Chat
if (request.autoSend) {
_pendingAssistantAutoSend.value = request.prompt
_chatDraft.value = null
return
}
_pendingAssistantAutoSend.value = null
_chatDraft.value = request.prompt
}
fun clearRequestedHomeDestination() {
_requestedHomeDestination.value = null
}
fun clearChatDraft() {
_chatDraft.value = null
}
fun clearPendingAssistantAutoSend() {
_pendingAssistantAutoSend.value = null
}
fun setMicEnabled(enabled: Boolean) {
ensureRuntime().setMicEnabled(enabled)
}
@@ -261,6 +291,22 @@ class MainViewModel(app: Application) : AndroidViewModel(app) {
ensureRuntime().connect(endpoint)
}
fun connect(
endpoint: GatewayEndpoint,
token: String?,
bootstrapToken: String?,
password: String?,
) {
ensureRuntime().connect(
endpoint,
NodeRuntime.GatewayConnectAuth(
token = token,
bootstrapToken = bootstrapToken,
password = password,
),
)
}
fun connectManual() {
ensureRuntime().connectManual()
}
@@ -320,4 +366,16 @@ class MainViewModel(app: Application) : AndroidViewModel(app) {
fun sendChat(message: String, thinking: String, attachments: List<OutgoingAttachment>) {
ensureRuntime().sendChat(message = message, thinking = thinking, attachments = attachments)
}
suspend fun sendChatAwaitAcceptance(
message: String,
thinking: String,
attachments: List<OutgoingAttachment>,
): Boolean {
return ensureRuntime().sendChatAwaitAcceptance(
message = message,
thinking = thinking,
attachments = attachments,
)
}
}

View File

@@ -16,6 +16,8 @@ import ai.openclaw.app.gateway.DeviceIdentityStore
import ai.openclaw.app.gateway.GatewayDiscovery
import ai.openclaw.app.gateway.GatewayEndpoint
import ai.openclaw.app.gateway.GatewaySession
import ai.openclaw.app.gateway.GatewayTlsProbeFailure
import ai.openclaw.app.gateway.GatewayTlsProbeResult
import ai.openclaw.app.gateway.probeGatewayTlsFingerprint
import ai.openclaw.app.node.*
import ai.openclaw.app.protocol.OpenClawCanvasA2UIAction
@@ -44,7 +46,14 @@ import java.util.concurrent.atomic.AtomicLong
class NodeRuntime(
context: Context,
val prefs: SecurePrefs = SecurePrefs(context.applicationContext),
private val tlsFingerprintProbe: suspend (String, Int) -> GatewayTlsProbeResult = ::probeGatewayTlsFingerprint,
) {
data class GatewayConnectAuth(
val token: String?,
val bootstrapToken: String?,
val password: String?,
)
private val appContext = context.applicationContext
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
private val deviceAuthStore = DeviceAuthStore(prefs)
@@ -183,6 +192,7 @@ class NodeRuntime(
data class GatewayTrustPrompt(
val endpoint: GatewayEndpoint,
val fingerprintSha256: String,
val auth: GatewayConnectAuth,
)
private val _isConnected = MutableStateFlow(false)
@@ -775,41 +785,69 @@ class NodeRuntime(
}
operatorStatusText = "Connecting…"
updateStatus()
val token = prefs.loadGatewayToken()
val bootstrapToken = prefs.loadGatewayBootstrapToken()
val password = prefs.loadGatewayPassword()
connectWithAuth(endpoint = endpoint, auth = resolveGatewayConnectAuth(), reconnect = true)
}
private fun connectWithAuth(
endpoint: GatewayEndpoint,
auth: GatewayConnectAuth,
reconnect: Boolean = false,
) {
val tls = connectionManager.resolveTlsParams(endpoint)
operatorSession.connect(
endpoint,
token,
bootstrapToken,
password,
connectionManager.buildOperatorConnectOptions(),
tls,
)
val connectOperator =
shouldConnectOperatorSession(
auth.token,
auth.bootstrapToken,
auth.password,
loadStoredRoleDeviceToken("operator"),
)
if (!connectOperator) {
operatorConnected = false
operatorStatusText = "Offline"
operatorSession.disconnect()
updateStatus()
} else {
operatorSession.connect(
endpoint,
auth.token,
auth.bootstrapToken,
auth.password,
connectionManager.buildOperatorConnectOptions(),
tls,
)
}
nodeSession.connect(
endpoint,
token,
bootstrapToken,
password,
auth.token,
auth.bootstrapToken,
auth.password,
connectionManager.buildNodeConnectOptions(),
tls,
)
operatorSession.reconnect()
nodeSession.reconnect()
if (reconnect && connectOperator) {
operatorSession.reconnect()
}
if (reconnect) {
nodeSession.reconnect()
}
}
fun connect(endpoint: GatewayEndpoint) {
private fun beginConnect(
endpoint: GatewayEndpoint,
auth: GatewayConnectAuth,
) {
val tls = connectionManager.resolveTlsParams(endpoint)
if (tls?.required == true && tls.expectedFingerprint.isNullOrBlank()) {
// First-time TLS: capture fingerprint, ask user to verify out-of-band, then store and connect.
_statusText.value = "Verify gateway TLS fingerprint…"
scope.launch {
val fp = probeGatewayTlsFingerprint(endpoint.host, endpoint.port) ?: run {
_statusText.value = "Failed: can't read TLS fingerprint"
val tlsProbe = tlsFingerprintProbe(endpoint.host, endpoint.port)
val fp = tlsProbe.fingerprintSha256 ?: run {
_statusText.value = gatewayTlsProbeFailureMessage(tlsProbe.failure)
return@launch
}
_pendingGatewayTrust.value = GatewayTrustPrompt(endpoint = endpoint, fingerprintSha256 = fp)
_pendingGatewayTrust.value =
GatewayTrustPrompt(endpoint = endpoint, fingerprintSha256 = fp, auth = auth)
}
return
}
@@ -818,32 +856,34 @@ class NodeRuntime(
operatorStatusText = "Connecting…"
nodeStatusText = "Connecting…"
updateStatus()
val token = prefs.loadGatewayToken()
val bootstrapToken = prefs.loadGatewayBootstrapToken()
val password = prefs.loadGatewayPassword()
operatorSession.connect(
endpoint,
token,
bootstrapToken,
password,
connectionManager.buildOperatorConnectOptions(),
tls,
)
nodeSession.connect(
endpoint,
token,
bootstrapToken,
password,
connectionManager.buildNodeConnectOptions(),
tls,
)
connectWithAuth(endpoint = endpoint, auth = auth)
}
fun connect(endpoint: GatewayEndpoint) {
beginConnect(endpoint = endpoint, auth = resolveGatewayConnectAuth())
}
fun connect(
endpoint: GatewayEndpoint,
auth: GatewayConnectAuth,
) {
beginConnect(endpoint = endpoint, auth = resolveGatewayConnectAuth(auth))
}
internal fun resolveGatewayConnectAuth(explicitAuth: GatewayConnectAuth? = null): GatewayConnectAuth {
return explicitAuth
?: GatewayConnectAuth(
token = prefs.loadGatewayToken(),
bootstrapToken = prefs.loadGatewayBootstrapToken(),
password = prefs.loadGatewayPassword(),
)
}
fun acceptGatewayTrustPrompt() {
val prompt = _pendingGatewayTrust.value ?: return
_pendingGatewayTrust.value = null
prefs.saveGatewayTlsFingerprint(prompt.endpoint.stableId, prompt.fingerprintSha256)
connect(prompt.endpoint)
beginConnect(endpoint = prompt.endpoint, auth = prompt.auth)
}
fun declineGatewayTrustPrompt() {
@@ -851,6 +891,15 @@ class NodeRuntime(
_statusText.value = "Offline"
}
private fun gatewayTlsProbeFailureMessage(failure: GatewayTlsProbeFailure?): String {
return when (failure) {
GatewayTlsProbeFailure.TLS_UNAVAILABLE ->
"Failed: this host requires wss:// or Tailscale Serve. No TLS endpoint detected."
GatewayTlsProbeFailure.ENDPOINT_UNREACHABLE, null ->
"Failed: couldn't reach the secure gateway endpoint for this host."
}
}
private fun hasRecordAudioPermission(): Boolean {
return (
ContextCompat.checkSelfPermission(appContext, Manifest.permission.RECORD_AUDIO) ==
@@ -868,6 +917,11 @@ class NodeRuntime(
connect(GatewayEndpoint.manual(host = host, port = port))
}
private fun loadStoredRoleDeviceToken(role: String): String? {
val deviceId = identityStore.loadOrCreate().deviceId
return deviceAuthStore.loadToken(deviceId, role)
}
fun disconnect() {
connectedEndpoint = null
_pendingGatewayTrust.value = null
@@ -980,6 +1034,14 @@ class NodeRuntime(
chat.sendMessage(message = message, thinkingLevel = thinking, attachments = attachments)
}
suspend fun sendChatAwaitAcceptance(
message: String,
thinking: String,
attachments: List<OutgoingAttachment>,
): Boolean {
return chat.sendMessageAwaitAcceptance(message = message, thinkingLevel = thinking, attachments = attachments)
}
private fun handleGatewayEvent(event: String, payloadJson: String?) {
micCapture.handleGatewayEvent(event, payloadJson)
talkMode.handleGatewayEvent(event, payloadJson)
@@ -1197,6 +1259,20 @@ class NodeRuntime(
}
internal fun shouldConnectOperatorSession(
token: String?,
bootstrapToken: String?,
password: String?,
storedOperatorToken: String?,
): Boolean {
return (
!token.isNullOrBlank() ||
!bootstrapToken.isNullOrBlank() ||
!password.isNullOrBlank() ||
!storedOperatorToken.isNullOrBlank()
)
}
private enum class HomeCanvasGatewayState {
Connected,
Connecting,

View File

@@ -130,11 +130,25 @@ class ChatController(
thinkingLevel: String,
attachments: List<OutgoingAttachment>,
) {
scope.launch {
sendMessageAwaitAcceptance(
message = message,
thinkingLevel = thinkingLevel,
attachments = attachments,
)
}
}
suspend fun sendMessageAwaitAcceptance(
message: String,
thinkingLevel: String,
attachments: List<OutgoingAttachment>,
): Boolean {
val trimmed = message.trim()
if (trimmed.isEmpty() && attachments.isEmpty()) return
if (trimmed.isEmpty() && attachments.isEmpty()) return false
if (!_healthOk.value) {
_errorText.value = "Gateway health not OK; cannot send"
return
return false
}
val runId = UUID.randomUUID().toString()
@@ -177,45 +191,45 @@ class ChatController(
pendingToolCallsById.clear()
publishPendingToolCalls()
scope.launch {
try {
val params =
buildJsonObject {
put("sessionKey", JsonPrimitive(sessionKey))
put("message", JsonPrimitive(text))
put("thinking", JsonPrimitive(thinking))
put("timeoutMs", JsonPrimitive(30_000))
put("idempotencyKey", JsonPrimitive(runId))
if (attachments.isNotEmpty()) {
put(
"attachments",
JsonArray(
attachments.map { att ->
buildJsonObject {
put("type", JsonPrimitive(att.type))
put("mimeType", JsonPrimitive(att.mimeType))
put("fileName", JsonPrimitive(att.fileName))
put("content", JsonPrimitive(att.base64))
}
},
),
)
}
}
val res = session.request("chat.send", params.toString())
val actualRunId = parseRunId(res) ?: runId
if (actualRunId != runId) {
clearPendingRun(runId)
armPendingRunTimeout(actualRunId)
synchronized(pendingRuns) {
pendingRuns.add(actualRunId)
_pendingRunCount.value = pendingRuns.size
return try {
val params =
buildJsonObject {
put("sessionKey", JsonPrimitive(sessionKey))
put("message", JsonPrimitive(text))
put("thinking", JsonPrimitive(thinking))
put("timeoutMs", JsonPrimitive(30_000))
put("idempotencyKey", JsonPrimitive(runId))
if (attachments.isNotEmpty()) {
put(
"attachments",
JsonArray(
attachments.map { att ->
buildJsonObject {
put("type", JsonPrimitive(att.type))
put("mimeType", JsonPrimitive(att.mimeType))
put("fileName", JsonPrimitive(att.fileName))
put("content", JsonPrimitive(att.base64))
}
},
),
)
}
}
} catch (err: Throwable) {
val res = session.request("chat.send", params.toString())
val actualRunId = parseRunId(res) ?: runId
if (actualRunId != runId) {
clearPendingRun(runId)
_errorText.value = err.message
armPendingRunTimeout(actualRunId)
synchronized(pendingRuns) {
pendingRuns.add(actualRunId)
_pendingRunCount.value = pendingRuns.size
}
}
true
} catch (err: Throwable) {
clearPendingRun(runId)
_errorText.value = err.message
false
}
}

View File

@@ -0,0 +1,124 @@
package ai.openclaw.app.gateway
import android.os.Build
import java.net.InetAddress
import java.util.Locale
internal fun isLoopbackGatewayHost(
rawHost: String?,
allowEmulatorBridgeAlias: Boolean = isAndroidEmulatorRuntime(),
): Boolean {
var host =
rawHost
?.trim()
?.lowercase(Locale.US)
?.trim('[', ']')
.orEmpty()
if (host.endsWith(".")) {
host = host.dropLast(1)
}
val zoneIndex = host.indexOf('%')
if (zoneIndex >= 0) return false
if (host.isEmpty()) return false
if (host == "localhost") return true
if (allowEmulatorBridgeAlias && host == "10.0.2.2") return true
parseIpv4Address(host)?.let { ipv4 ->
return ipv4.first() == 127.toByte()
}
if (!host.contains(':') || !host.all(::isIpv6LiteralChar)) return false
val address = runCatching { InetAddress.getByName(host) }.getOrNull()?.address ?: return false
if (address.size == 4) {
return address[0] == 127.toByte()
}
if (address.size != 16) return false
// `::1` is 15 zero bytes followed by `0x01`.
val isIpv6Loopback = address.copyOfRange(0, 15).all { it == 0.toByte() } && address[15] == 1.toByte()
if (isIpv6Loopback) return true
val isMappedIpv4 =
address.copyOfRange(0, 10).all { it == 0.toByte() } &&
address[10] == 0xFF.toByte() &&
address[11] == 0xFF.toByte()
return isMappedIpv4 && address[12] == 127.toByte()
}
internal fun isPrivateLanGatewayHost(
rawHost: String?,
allowEmulatorBridgeAlias: Boolean = isAndroidEmulatorRuntime(),
): Boolean {
var host =
rawHost
?.trim()
?.lowercase(Locale.US)
?.trim('[', ']')
.orEmpty()
if (host.endsWith(".")) {
host = host.dropLast(1)
}
val zoneIndex = host.indexOf('%')
if (zoneIndex >= 0) {
host = host.substring(0, zoneIndex)
}
if (host.isEmpty()) return false
if (isLoopbackGatewayHost(host, allowEmulatorBridgeAlias = allowEmulatorBridgeAlias)) return true
if (host.endsWith(".local")) return true
if (!host.contains('.') && !host.contains(':')) return true
parseIpv4Address(host)?.let { ipv4 ->
val first = ipv4[0].toInt() and 0xff
val second = ipv4[1].toInt() and 0xff
return when {
first == 10 -> true
first == 172 && second in 16..31 -> true
first == 192 && second == 168 -> true
first == 169 && second == 254 -> true
else -> false
}
}
if (!host.contains(':') || !host.all(::isIpv6LiteralChar)) return false
val address = runCatching { InetAddress.getByName(host) }.getOrNull() ?: return false
return when {
address.isLinkLocalAddress -> true
address.isSiteLocalAddress -> true
else -> {
val bytes = address.address
bytes.size == 16 && (bytes[0].toInt() and 0xfe) == 0xfc
}
}
}
private fun isAndroidEmulatorRuntime(): Boolean {
val fingerprint = Build.FINGERPRINT?.lowercase(Locale.US).orEmpty()
val model = Build.MODEL?.lowercase(Locale.US).orEmpty()
val manufacturer = Build.MANUFACTURER?.lowercase(Locale.US).orEmpty()
val brand = Build.BRAND?.lowercase(Locale.US).orEmpty()
val device = Build.DEVICE?.lowercase(Locale.US).orEmpty()
val product = Build.PRODUCT?.lowercase(Locale.US).orEmpty()
return fingerprint.contains("generic") ||
fingerprint.contains("robolectric") ||
model.contains("emulator") ||
model.contains("sdk_gphone") ||
manufacturer.contains("genymotion") ||
(brand.contains("generic") && device.contains("generic")) ||
product.contains("sdk_gphone") ||
product.contains("emulator") ||
product.contains("simulator")
}
private fun parseIpv4Address(host: String): ByteArray? {
val parts = host.split('.')
if (parts.size != 4) return null
val bytes = ByteArray(4)
for ((index, part) in parts.withIndex()) {
val value = part.toIntOrNull() ?: return null
if (value !in 0..255) return null
bytes[index] = value.toByte()
}
return bytes
}
private fun isIpv6LiteralChar(char: Char): Boolean = char in '0'..'9' || char in 'a'..'f' || char == ':' || char == '.'

View File

@@ -268,16 +268,10 @@ class GatewaySession(
private var socket: WebSocket? = null
private val loggerTag = "OpenClawGateway"
val remoteAddress: String =
if (endpoint.host.contains(":")) {
"[${endpoint.host}]:${endpoint.port}"
} else {
"${endpoint.host}:${endpoint.port}"
}
val remoteAddress: String = formatGatewayAuthority(endpoint.host, endpoint.port)
suspend fun connect() {
val scheme = if (tls != null) "wss" else "ws"
val url = "$scheme://${endpoint.host}:${endpoint.port}"
val url = buildGatewayWebSocketUrl(endpoint.host, endpoint.port, tls != null)
val request = Request.Builder().url(url).build()
socket = client.newWebSocket(request, Listener())
try {
@@ -752,7 +746,7 @@ class GatewaySession(
// If raw URL is a non-loopback address and this connection uses TLS,
// normalize scheme/port to the endpoint we actually connected to.
if (trimmed.isNotBlank() && host.isNotBlank() && !isLoopbackHost(host)) {
if (trimmed.isNotBlank() && host.isNotBlank() && !isLoopbackGatewayHost(host)) {
val needsTlsRewrite =
isTlsConnection &&
(
@@ -781,7 +775,7 @@ class GatewaySession(
private fun buildCanvasUrl(host: String, scheme: String, port: Int, suffix: String): String {
val loweredScheme = scheme.lowercase()
val formattedHost = if (host.contains(":")) "[${host}]" else host
val formattedHost = formatGatewayAuthorityHost(host)
val portSuffix = if ((loweredScheme == "https" && port == 443) || (loweredScheme == "http" && port == 80)) "" else ":$port"
return "$loweredScheme://$formattedHost$portSuffix$suffix"
}
@@ -794,15 +788,6 @@ class GatewaySession(
return "$path$query$fragment"
}
private fun isLoopbackHost(raw: String?): Boolean {
val host = raw?.trim()?.lowercase().orEmpty()
if (host.isEmpty()) return false
if (host == "localhost") return true
if (host == "::1") return true
if (host == "0.0.0.0" || host == "::") return true
return host.startsWith("127.")
}
private fun selectConnectAuth(
endpoint: GatewayEndpoint,
tls: GatewayTlsParams?,
@@ -891,13 +876,27 @@ class GatewaySession(
endpoint: GatewayEndpoint,
tls: GatewayTlsParams?,
): Boolean {
if (isLoopbackHost(endpoint.host)) {
if (isLoopbackGatewayHost(endpoint.host)) {
return true
}
return tls?.expectedFingerprint?.trim()?.isNotEmpty() == true
}
}
internal fun buildGatewayWebSocketUrl(host: String, port: Int, useTls: Boolean): String {
val scheme = if (useTls) "wss" else "ws"
return "$scheme://${formatGatewayAuthority(host, port)}"
}
internal fun formatGatewayAuthority(host: String, port: Int): String {
return "${formatGatewayAuthorityHost(host)}:$port"
}
private fun formatGatewayAuthorityHost(host: String): String {
val normalizedHost = host.trim().trim('[', ']')
return if (normalizedHost.contains(":")) "[${normalizedHost}]" else normalizedHost
}
private fun JsonElement?.asObjectOrNull(): JsonObject? = this as? JsonObject
private fun JsonElement?.asStringOrNull(): String? =

View File

@@ -3,7 +3,11 @@ package ai.openclaw.app.gateway
import android.annotation.SuppressLint
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.EOFException
import java.net.ConnectException
import java.net.InetSocketAddress
import java.net.SocketTimeoutException
import java.net.UnknownHostException
import java.security.MessageDigest
import java.security.SecureRandom
import java.security.cert.CertificateException
@@ -12,6 +16,7 @@ import java.util.Locale
import javax.net.ssl.HttpsURLConnection
import javax.net.ssl.HostnameVerifier
import javax.net.ssl.SSLContext
import javax.net.ssl.SSLException
import javax.net.ssl.SSLParameters
import javax.net.ssl.SSLSocketFactory
import javax.net.ssl.SNIHostName
@@ -32,6 +37,16 @@ data class GatewayTlsConfig(
val hostnameVerifier: HostnameVerifier,
)
enum class GatewayTlsProbeFailure {
TLS_UNAVAILABLE,
ENDPOINT_UNREACHABLE,
}
data class GatewayTlsProbeResult(
val fingerprintSha256: String? = null,
val failure: GatewayTlsProbeFailure? = null,
)
fun buildGatewayTlsConfig(
params: GatewayTlsParams?,
onStore: ((String) -> Unit)? = null,
@@ -85,10 +100,10 @@ suspend fun probeGatewayTlsFingerprint(
host: String,
port: Int,
timeoutMs: Int = 3_000,
): String? {
): GatewayTlsProbeResult {
val trimmedHost = host.trim()
if (trimmedHost.isEmpty()) return null
if (port !in 1..65535) return null
if (trimmedHost.isEmpty()) return GatewayTlsProbeResult(failure = GatewayTlsProbeFailure.ENDPOINT_UNREACHABLE)
if (port !in 1..65535) return GatewayTlsProbeResult(failure = GatewayTlsProbeFailure.ENDPOINT_UNREACHABLE)
return withContext(Dispatchers.IO) {
val trustAll =
@@ -121,10 +136,21 @@ suspend fun probeGatewayTlsFingerprint(
}
socket.startHandshake()
val cert = socket.session.peerCertificates.firstOrNull() as? X509Certificate ?: return@withContext null
sha256Hex(cert.encoded)
} catch (_: Throwable) {
null
val cert =
socket.session.peerCertificates.firstOrNull() as? X509Certificate
?: return@withContext GatewayTlsProbeResult(failure = GatewayTlsProbeFailure.TLS_UNAVAILABLE)
GatewayTlsProbeResult(fingerprintSha256 = sha256Hex(cert.encoded))
} catch (err: Throwable) {
val failure =
when (err) {
is SSLException,
is EOFException -> GatewayTlsProbeFailure.TLS_UNAVAILABLE
is ConnectException,
is SocketTimeoutException,
is UnknownHostException -> GatewayTlsProbeFailure.ENDPOINT_UNREACHABLE
else -> GatewayTlsProbeFailure.ENDPOINT_UNREACHABLE
}
GatewayTlsProbeResult(failure = failure)
} finally {
try {
socket.close()

View File

@@ -7,6 +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.LocationMode
import ai.openclaw.app.VoiceWakeMode
@@ -33,9 +34,10 @@ class ConnectionManager(
val stableId = endpoint.stableId
val stored = storedFingerprint?.trim().takeIf { !it.isNullOrEmpty() }
val isManual = stableId.startsWith("manual|")
val cleartextAllowedHost = isLoopbackGatewayHost(endpoint.host)
if (isManual) {
if (!manualTlsEnabled) return null
if (!manualTlsEnabled && cleartextAllowedHost) return null
if (!stored.isNullOrBlank()) {
return GatewayTlsParams(
required = true,
@@ -73,6 +75,15 @@ class ConnectionManager(
)
}
if (!cleartextAllowedHost) {
return GatewayTlsParams(
required = true,
expectedFingerprint = null,
allowTOFU = false,
stableId = stableId,
)
}
return null
}
}

View File

@@ -53,6 +53,7 @@ import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import ai.openclaw.app.MainViewModel
import ai.openclaw.app.gateway.GatewayEndpoint
import ai.openclaw.app.ui.mobileCardSurface
private enum class ConnectInputMode {
@@ -71,6 +72,7 @@ fun ConnectTabScreen(viewModel: MainViewModel) {
val manualTls by viewModel.manualTls.collectAsState()
val manualEnabled by viewModel.manualEnabled.collectAsState()
val gatewayToken by viewModel.gatewayToken.collectAsState()
val gatewayBootstrapToken by viewModel.gatewayBootstrapToken.collectAsState()
val pendingTrust by viewModel.pendingGatewayTrust.collectAsState()
var advancedOpen by rememberSaveable { mutableStateOf(false) }
@@ -240,9 +242,13 @@ fun ConnectTabScreen(viewModel: MainViewModel) {
resolveGatewayConnectConfig(
useSetupCode = inputMode == ConnectInputMode.SetupCode,
setupCode = setupCode,
manualHost = manualHostInput,
manualPort = manualPortInput,
manualTls = manualTlsInput,
savedManualHost = manualHost,
savedManualPort = manualPort.toString(),
savedManualTls = manualTls,
manualHostInput = manualHostInput,
manualPortInput = manualPortInput,
manualTlsInput = manualTlsInput,
fallbackBootstrapToken = gatewayBootstrapToken,
fallbackToken = gatewayToken,
fallbackPassword = passwordInput,
)
@@ -250,9 +256,23 @@ fun ConnectTabScreen(viewModel: MainViewModel) {
if (config == null) {
validationText =
if (inputMode == ConnectInputMode.SetupCode) {
"Paste a valid setup code to connect."
val parsedSetup = decodeGatewaySetupCode(setupCode)
if (parsedSetup == null) {
"Paste a valid setup code to connect."
} else {
val parsedGateway = parseGatewayEndpointResult(parsedSetup.url)
gatewayEndpointValidationMessage(
parsedGateway.error ?: GatewayEndpointValidationError.INVALID_URL,
GatewayEndpointInputSource.SETUP_CODE,
)
}
} else {
"Enter a valid manual host and port to connect."
val manualUrl = composeGatewayManualUrl(manualHostInput, manualPortInput, manualTlsInput)
val parsedGateway = manualUrl?.let(::parseGatewayEndpointResult)
gatewayEndpointValidationMessage(
parsedGateway?.error ?: GatewayEndpointValidationError.INVALID_URL,
GatewayEndpointInputSource.MANUAL,
)
}
return@Button
}
@@ -269,7 +289,12 @@ fun ConnectTabScreen(viewModel: MainViewModel) {
viewModel.setGatewayToken("")
}
viewModel.setGatewayPassword(config.password)
viewModel.connectManual()
viewModel.connect(
GatewayEndpoint.manual(host = config.host, port = config.port),
token = config.token.ifEmpty { null },
bootstrapToken = config.bootstrapToken.ifEmpty { null },
password = config.password.ifEmpty { null },
)
},
modifier = Modifier.fillMaxWidth().height(52.dp),
shape = RoundedCornerShape(14.dp),
@@ -375,6 +400,11 @@ fun ConnectTabScreen(viewModel: MainViewModel) {
Text("Run these on the gateway host:", style = mobileCallout, color = mobileTextSecondary)
CommandBlock("openclaw qr --setup-code-only")
CommandBlock("openclaw qr --json")
Text(
"For Tailscale or public hosts, use wss:// or Tailscale Serve. Private LAN ws:// remains supported.",
style = mobileCaption1,
color = mobileTextSecondary,
)
if (inputMode == ConnectInputMode.SetupCode) {
Text("Setup Code", style = mobileCaption1.copy(fontWeight = FontWeight.SemiBold), color = mobileTextSecondary)
@@ -457,7 +487,11 @@ fun ConnectTabScreen(viewModel: MainViewModel) {
) {
Column(verticalArrangement = Arrangement.spacedBy(2.dp)) {
Text("Use TLS", style = mobileHeadline, color = mobileText)
Text("Switch to secure websocket (`wss`).", style = mobileCallout, color = mobileTextSecondary)
Text(
"Turn this on for Tailscale or public hosts. Private LAN ws:// remains supported.",
style = mobileCallout,
color = mobileTextSecondary,
)
}
Switch(
checked = manualTlsInput,

View File

@@ -1,5 +1,6 @@
package ai.openclaw.app.ui
import ai.openclaw.app.gateway.isLoopbackGatewayHost
import java.util.Base64
import java.util.Locale
import java.net.URI
@@ -32,20 +33,49 @@ internal data class GatewayConnectConfig(
val password: String,
)
internal enum class GatewayEndpointValidationError {
INVALID_URL,
INSECURE_REMOTE_URL,
}
internal enum class GatewayEndpointInputSource {
SETUP_CODE,
MANUAL,
QR_SCAN,
}
internal data class GatewayEndpointParseResult(
val config: GatewayEndpointConfig? = null,
val error: GatewayEndpointValidationError? = null,
)
internal data class GatewayScannedSetupCodeResult(
val setupCode: String? = null,
val error: GatewayEndpointValidationError? = null,
)
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."
private const val remoteGatewaySecurityFix =
"Use a private LAN host/address, or enable Tailscale Serve / expose a wss:// gateway URL."
internal fun resolveGatewayConnectConfig(
useSetupCode: Boolean,
setupCode: String,
manualHost: String,
manualPort: String,
manualTls: Boolean,
savedManualHost: String,
savedManualPort: String,
savedManualTls: Boolean,
manualHostInput: String,
manualPortInput: String,
manualTlsInput: Boolean,
fallbackBootstrapToken: String,
fallbackToken: String,
fallbackPassword: String,
): GatewayConnectConfig? {
if (useSetupCode) {
val setup = decodeGatewaySetupCode(setupCode) ?: return null
val parsed = parseGatewayEndpoint(setup.url) ?: return null
val parsed = parseGatewayEndpointResult(setup.url).config ?: return null
val setupBootstrapToken = setup.bootstrapToken?.trim().orEmpty()
val sharedToken =
when {
@@ -69,26 +99,42 @@ internal fun resolveGatewayConnectConfig(
)
}
val manualUrl = composeGatewayManualUrl(manualHost, manualPort, manualTls) ?: return null
val parsed = parseGatewayEndpoint(manualUrl) ?: return null
val manualUrl = composeGatewayManualUrl(manualHostInput, manualPortInput, manualTlsInput) ?: return null
val parsed = parseGatewayEndpointResult(manualUrl).config ?: return null
val savedManualEndpoint =
composeGatewayManualUrl(savedManualHost, savedManualPort, savedManualTls)
?.let { parseGatewayEndpointResult(it).config }
val preserveBootstrapToken =
savedManualEndpoint != null &&
savedManualEndpoint.host == parsed.host &&
savedManualEndpoint.port == parsed.port &&
savedManualEndpoint.tls == parsed.tls &&
fallbackToken.isBlank() &&
fallbackPassword.isBlank()
return GatewayConnectConfig(
host = parsed.host,
port = parsed.port,
tls = parsed.tls,
bootstrapToken = "",
bootstrapToken = if (preserveBootstrapToken) fallbackBootstrapToken.trim() else "",
token = fallbackToken.trim(),
password = fallbackPassword.trim(),
)
}
internal fun parseGatewayEndpoint(rawInput: String): GatewayEndpointConfig? {
return parseGatewayEndpointResult(rawInput).config
}
internal fun parseGatewayEndpointResult(rawInput: String): GatewayEndpointParseResult {
val raw = rawInput.trim()
if (raw.isEmpty()) return null
if (raw.isEmpty()) return GatewayEndpointParseResult(error = GatewayEndpointValidationError.INVALID_URL)
val normalized = if (raw.contains("://")) raw else "https://$raw"
val uri = runCatching { URI(normalized) }.getOrNull() ?: return null
val host = uri.host?.trim().orEmpty()
if (host.isEmpty()) return null
val uri =
runCatching { URI(normalized) }.getOrNull()
?: return GatewayEndpointParseResult(error = GatewayEndpointValidationError.INVALID_URL)
val host = uri.host?.trim()?.trim('[', ']').orEmpty()
if (host.isEmpty()) return GatewayEndpointParseResult(error = GatewayEndpointValidationError.INVALID_URL)
val scheme = uri.scheme?.trim()?.lowercase(Locale.US).orEmpty()
val tls =
@@ -97,6 +143,9 @@ internal fun parseGatewayEndpoint(rawInput: String): GatewayEndpointConfig? {
"wss", "https" -> true
else -> true
}
if (!tls && !isLoopbackGatewayHost(host)) {
return GatewayEndpointParseResult(error = GatewayEndpointValidationError.INSECURE_REMOTE_URL)
}
val defaultPort =
when (scheme) {
"wss", "https" -> 443
@@ -110,14 +159,17 @@ internal fun parseGatewayEndpoint(rawInput: String): GatewayEndpointConfig? {
else -> 443
}
val port = uri.port.takeIf { it in 1..65535 } ?: defaultPort
val displayHost = if (host.contains(":")) "[$host]" else host
val displayUrl =
if (port == displayPort && defaultPort == displayPort) {
"${if (tls) "https" else "http"}://$host"
"${if (tls) "https" else "http"}://$displayHost"
} else {
"${if (tls) "https" else "http"}://$host:$port"
"${if (tls) "https" else "http"}://$displayHost:$port"
}
return GatewayEndpointConfig(host = host, port = port, tls = tls, displayUrl = displayUrl)
return GatewayEndpointParseResult(
config = GatewayEndpointConfig(host = host, port = port, tls = tls, displayUrl = displayUrl),
)
}
internal fun decodeGatewaySetupCode(rawInput: String): GatewaySetupCode? {
@@ -148,8 +200,44 @@ internal fun decodeGatewaySetupCode(rawInput: String): GatewaySetupCode? {
}
internal fun resolveScannedSetupCode(rawInput: String): String? {
val setupCode = resolveSetupCodeCandidate(rawInput) ?: return null
return setupCode.takeIf { decodeGatewaySetupCode(it) != null }
return resolveScannedSetupCodeResult(rawInput).setupCode
}
internal fun resolveScannedSetupCodeResult(rawInput: String): GatewayScannedSetupCodeResult {
val setupCode =
resolveSetupCodeCandidate(rawInput)
?: return GatewayScannedSetupCodeResult(error = GatewayEndpointValidationError.INVALID_URL)
val decoded =
decodeGatewaySetupCode(setupCode)
?: return GatewayScannedSetupCodeResult(error = GatewayEndpointValidationError.INVALID_URL)
val parsed = parseGatewayEndpointResult(decoded.url)
if (parsed.config == null) {
return GatewayScannedSetupCodeResult(error = parsed.error)
}
return GatewayScannedSetupCodeResult(setupCode = setupCode)
}
internal fun gatewayEndpointValidationMessage(
error: GatewayEndpointValidationError,
source: GatewayEndpointInputSource,
): String {
return when (error) {
GatewayEndpointValidationError.INSECURE_REMOTE_URL ->
when (source) {
GatewayEndpointInputSource.SETUP_CODE ->
"Setup code points to an insecure remote gateway. $remoteGatewaySecurityRule $remoteGatewaySecurityFix"
GatewayEndpointInputSource.QR_SCAN ->
"QR code points to an insecure remote gateway. $remoteGatewaySecurityRule $remoteGatewaySecurityFix"
GatewayEndpointInputSource.MANUAL ->
"$remoteGatewaySecurityRule $remoteGatewaySecurityFix"
}
GatewayEndpointValidationError.INVALID_URL ->
when (source) {
GatewayEndpointInputSource.SETUP_CODE -> "Setup code has invalid gateway URL."
GatewayEndpointInputSource.QR_SCAN -> "QR code did not contain a valid setup code."
GatewayEndpointInputSource.MANUAL -> "Enter a valid manual host and port to connect."
}
}
}
internal fun composeGatewayManualUrl(hostInput: String, portInput: String, tls: Boolean): String? {

View File

@@ -49,6 +49,7 @@ internal fun buildGatewayDiagnosticsReport(
Please:
- pick one route only: same machine, same LAN, Tailscale, or public URL
- classify this as pairing/auth, TLS trust, wrong advertised route, wrong address/port, or gateway down
- remember: Tailscale/public mobile routes require wss:// or Tailscale Serve; private LAN ws:// is still allowed
- quote the exact app status/error below
- tell me whether `openclaw devices list` should show a pending pairing request
- if more signal is needed, ask for `openclaw qr --json`, `openclaw devices list`, and `openclaw nodes status`

View File

@@ -96,6 +96,7 @@ import androidx.lifecycle.compose.LocalLifecycleOwner
import ai.openclaw.app.BuildConfig
import ai.openclaw.app.LocationMode
import ai.openclaw.app.MainViewModel
import ai.openclaw.app.gateway.GatewayEndpoint
import ai.openclaw.app.node.DeviceNotificationListenerService
import com.google.mlkit.vision.barcode.common.Barcode
import com.google.mlkit.vision.codescanner.GmsBarcodeScannerOptions
@@ -211,6 +212,7 @@ fun OnboardingFlow(viewModel: MainViewModel, modifier: Modifier = Modifier) {
val context = androidx.compose.ui.platform.LocalContext.current
val statusText by viewModel.statusText.collectAsState()
val isConnected by viewModel.isConnected.collectAsState()
val isNodeConnected by viewModel.isNodeConnected.collectAsState()
val serverName by viewModel.serverName.collectAsState()
val remoteAddress by viewModel.remoteAddress.collectAsState()
val persistedGatewayToken by viewModel.gatewayToken.collectAsState()
@@ -227,6 +229,7 @@ fun OnboardingFlow(viewModel: MainViewModel, modifier: Modifier = Modifier) {
var manualTls by rememberSaveable { mutableStateOf(false) }
var gatewayError by rememberSaveable { mutableStateOf<String?>(null) }
var attemptedConnect by rememberSaveable { mutableStateOf(false) }
val canFinishOnboarding = canFinishOnboarding(isConnected = isConnected, isNodeConnected = isNodeConnected)
val lifecycleOwner = LocalLifecycleOwner.current
val qrScannerOptions =
@@ -563,12 +566,16 @@ fun OnboardingFlow(viewModel: MainViewModel, modifier: Modifier = Modifier) {
if (contents.isEmpty()) {
return@addOnSuccessListener
}
val scannedSetupCode = resolveScannedSetupCode(contents)
if (scannedSetupCode == null) {
gatewayError = "QR code did not contain a valid setup code."
val scannedSetupCode = resolveScannedSetupCodeResult(contents)
if (scannedSetupCode.setupCode == null) {
gatewayError =
gatewayEndpointValidationMessage(
scannedSetupCode.error ?: GatewayEndpointValidationError.INVALID_URL,
GatewayEndpointInputSource.QR_SCAN,
)
return@addOnSuccessListener
}
setupCode = scannedSetupCode
setupCode = scannedSetupCode.setupCode
gatewayInputMode = GatewayInputMode.SetupCode
gatewayError = null
attemptedConnect = false
@@ -732,7 +739,7 @@ fun OnboardingFlow(viewModel: MainViewModel, modifier: Modifier = Modifier) {
FinalStep(
parsedGateway = parseGatewayEndpoint(gatewayUrl),
statusText = statusText,
isConnected = isConnected,
isConnected = canFinishOnboarding,
serverName = serverName,
remoteAddress = remoteAddress,
attemptedConnect = attemptedConnect,
@@ -796,9 +803,13 @@ fun OnboardingFlow(viewModel: MainViewModel, modifier: Modifier = Modifier) {
gatewayError = "Scan QR code first, or use Advanced setup."
return@Button
}
val parsedGateway = parseGatewayEndpoint(parsedSetup.url)
if (parsedGateway == null) {
gatewayError = "Setup code has invalid gateway URL."
val parsedGateway = parseGatewayEndpointResult(parsedSetup.url)
if (parsedGateway.config == null) {
gatewayError =
gatewayEndpointValidationMessage(
parsedGateway.error ?: GatewayEndpointValidationError.INVALID_URL,
GatewayEndpointInputSource.SETUP_CODE,
)
return@Button
}
gatewayUrl = parsedSetup.url
@@ -816,12 +827,16 @@ fun OnboardingFlow(viewModel: MainViewModel, modifier: Modifier = Modifier) {
}
} else {
val manualUrl = composeGatewayManualUrl(manualHost, manualPort, manualTls)
val parsedGateway = manualUrl?.let(::parseGatewayEndpoint)
if (parsedGateway == null) {
gatewayError = "Manual endpoint is invalid."
val parsedGateway = manualUrl?.let(::parseGatewayEndpointResult)
if (parsedGateway?.config == null) {
gatewayError =
gatewayEndpointValidationMessage(
parsedGateway?.error ?: GatewayEndpointValidationError.INVALID_URL,
GatewayEndpointInputSource.MANUAL,
)
return@Button
}
gatewayUrl = parsedGateway.displayUrl
gatewayUrl = parsedGateway.config.displayUrl
viewModel.setGatewayBootstrapToken("")
}
step = OnboardingStep.Permissions
@@ -848,7 +863,7 @@ fun OnboardingFlow(viewModel: MainViewModel, modifier: Modifier = Modifier) {
}
}
OnboardingStep.FinalCheck -> {
if (isConnected) {
if (canFinishOnboarding) {
Button(
onClick = { viewModel.setOnboardingCompleted(true) },
modifier = Modifier.weight(1f).height(52.dp),
@@ -860,19 +875,23 @@ fun OnboardingFlow(viewModel: MainViewModel, modifier: Modifier = Modifier) {
} else {
Button(
onClick = {
val parsed = parseGatewayEndpoint(gatewayUrl)
if (parsed == null) {
val parsed = parseGatewayEndpointResult(gatewayUrl)
if (parsed.config == null) {
step = OnboardingStep.Gateway
gatewayError = "Invalid gateway URL."
gatewayError =
gatewayEndpointValidationMessage(
parsed.error ?: GatewayEndpointValidationError.INVALID_URL,
GatewayEndpointInputSource.MANUAL,
)
return@Button
}
val token = persistedGatewayToken.trim()
val password = gatewayPassword.trim()
attemptedConnect = true
viewModel.setManualEnabled(true)
viewModel.setManualHost(parsed.host)
viewModel.setManualPort(parsed.port)
viewModel.setManualTls(parsed.tls)
viewModel.setManualHost(parsed.config.host)
viewModel.setManualPort(parsed.config.port)
viewModel.setManualTls(parsed.config.tls)
if (gatewayInputMode == GatewayInputMode.Manual) {
viewModel.setGatewayBootstrapToken("")
}
@@ -882,7 +901,17 @@ fun OnboardingFlow(viewModel: MainViewModel, modifier: Modifier = Modifier) {
viewModel.setGatewayToken("")
}
viewModel.setGatewayPassword(password)
viewModel.connectManual()
viewModel.connect(
GatewayEndpoint.manual(host = parsed.config.host, port = parsed.config.port),
token = token.ifEmpty { null },
bootstrapToken =
if (gatewayInputMode == GatewayInputMode.SetupCode) {
decodeGatewaySetupCode(setupCode)?.bootstrapToken?.trim()?.ifEmpty { null }
} else {
null
},
password = password.ifEmpty { null },
)
},
modifier = Modifier.weight(1f).height(52.dp),
shape = RoundedCornerShape(14.dp),
@@ -898,6 +927,10 @@ fun OnboardingFlow(viewModel: MainViewModel, modifier: Modifier = Modifier) {
}
}
internal fun canFinishOnboarding(isConnected: Boolean, isNodeConnected: Boolean): Boolean {
return isConnected && isNodeConnected
}
@Composable
private fun onboardingPrimaryButtonColors() =
ButtonDefaults.buttonColors(
@@ -1023,7 +1056,7 @@ private fun GatewayStep(
StepShell(title = "Gateway Connection") {
Text(
"Run `openclaw qr` on your gateway host, then scan the code with this device.",
"Run `openclaw qr` on your gateway host, then scan the code with this device. For Tailscale or public hosts, use wss:// or Tailscale Serve.",
style = onboardingCalloutStyle,
color = onboardingTextSecondary,
)
@@ -1055,7 +1088,7 @@ private fun GatewayStep(
) {
Column(verticalArrangement = Arrangement.spacedBy(2.dp)) {
Text("Advanced setup", style = onboardingHeadlineStyle, color = onboardingText)
Text("Paste setup code or enter host/port manually.", style = onboardingCaption1Style, color = onboardingTextSecondary)
Text("Paste setup code or enter host/port manually. Private LAN ws:// is supported; Tailscale/public hosts need wss://.", style = onboardingCaption1Style, color = onboardingTextSecondary)
}
Icon(
imageVector = if (advancedOpen) Icons.Default.ExpandLess else Icons.Default.ExpandMore,
@@ -1136,7 +1169,11 @@ private fun GatewayStep(
) {
Column(verticalArrangement = Arrangement.spacedBy(2.dp)) {
Text("Use TLS", style = onboardingHeadlineStyle, color = onboardingText)
Text("Switch to secure websocket (`wss`).", style = onboardingCalloutStyle.copy(lineHeight = 18.sp), color = onboardingTextSecondary)
Text(
"Turn this on for Tailscale or public hosts. Private LAN ws:// remains supported.",
style = onboardingCalloutStyle.copy(lineHeight = 18.sp),
color = onboardingTextSecondary,
)
}
Switch(
checked = manualTls,
@@ -1677,21 +1714,22 @@ private fun FinalStep(
)
}
}
Text("Status", style = onboardingCaption1Style.copy(fontWeight = FontWeight.Bold), color = onboardingTextSecondary)
Surface(
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(12.dp),
color = onboardingCommandBg,
border = BorderStroke(1.dp, onboardingCommandBorder),
) {
Text(
statusLabel,
modifier = Modifier.padding(horizontal = 14.dp, vertical = 12.dp),
style = onboardingCalloutStyle.copy(fontFamily = FontFamily.Monospace),
color = onboardingCommandText,
)
}
if (showDiagnostics) {
Text("Error", style = onboardingCaption1Style.copy(fontWeight = FontWeight.Bold), color = onboardingTextSecondary)
Surface(
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(12.dp),
color = onboardingCommandBg,
border = BorderStroke(1.dp, onboardingCommandBorder),
) {
Text(
statusLabel,
modifier = Modifier.padding(horizontal = 14.dp, vertical = 12.dp),
style = onboardingCalloutStyle.copy(fontFamily = FontFamily.Monospace),
color = onboardingCommandText,
)
}
Text(
"OpenClaw Android ${openClawAndroidVersionLabel()}",
style = onboardingCaption1Style,

View File

@@ -46,6 +46,7 @@ import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import ai.openclaw.app.HomeDestination
import ai.openclaw.app.MainViewModel
private enum class HomeTab(
@@ -72,6 +73,20 @@ fun PostOnboardingTabs(viewModel: MainViewModel, modifier: Modifier = Modifier)
var activeTab by rememberSaveable { mutableStateOf(HomeTab.Connect) }
var chatTabStarted by rememberSaveable { mutableStateOf(false) }
var screenTabStarted by rememberSaveable { mutableStateOf(false) }
val requestedHomeDestination by viewModel.requestedHomeDestination.collectAsState()
LaunchedEffect(requestedHomeDestination) {
val destination = requestedHomeDestination ?: return@LaunchedEffect
activeTab =
when (destination) {
HomeDestination.Connect -> HomeTab.Connect
HomeDestination.Chat -> HomeTab.Chat
HomeDestination.Voice -> HomeTab.Voice
HomeDestination.Screen -> HomeTab.Screen
HomeDestination.Settings -> HomeTab.Settings
}
viewModel.clearRequestedHomeDestination()
}
// Stop TTS when user navigates away from voice tab, and lazily keep the Chat/Screen tabs
// alive after the first visit so repeated tab switches do not rebuild their UI trees.

View File

@@ -9,6 +9,7 @@ import android.hardware.SensorManager
import android.net.Uri
import android.os.Build
import android.provider.Settings
import android.app.role.RoleManager
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.background
@@ -150,6 +151,8 @@ fun SettingsSheet(viewModel: MainViewModel) {
versionName
}
}
var assistantRoleAvailable by remember(context) { mutableStateOf(isAssistantRoleAvailable(context)) }
var assistantRoleHeld by remember(context) { mutableStateOf(isAssistantRoleHeld(context)) }
val listItemColors =
ListItemDefaults.colors(
containerColor = Color.Transparent,
@@ -326,6 +329,12 @@ fun SettingsSheet(viewModel: MainViewModel) {
viewModel.refreshGatewayConnection()
}
val assistantRoleLauncher =
rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {
assistantRoleAvailable = isAssistantRoleAvailable(context)
assistantRoleHeld = isAssistantRoleHeld(context)
}
DisposableEffect(lifecycleOwner, context) {
val observer =
LifecycleEventObserver { _, event ->
@@ -362,6 +371,8 @@ fun SettingsSheet(viewModel: MainViewModel) {
||
ContextCompat.checkSelfPermission(context, Manifest.permission.READ_SMS) ==
PackageManager.PERMISSION_GRANTED
assistantRoleAvailable = isAssistantRoleAvailable(context)
assistantRoleHeld = isAssistantRoleHeld(context)
}
}
lifecycleOwner.lifecycle.addObserver(observer)
@@ -478,6 +489,42 @@ fun SettingsSheet(viewModel: MainViewModel) {
color = mobileTextTertiary,
)
}
if (assistantRoleAvailable) {
HorizontalDivider(color = mobileBorder)
ListItem(
modifier = Modifier.fillMaxWidth(),
colors = listItemColors,
headlineContent = { Text("Default Assistant", style = mobileHeadline) },
supportingContent = {
Text(
if (assistantRoleHeld) {
"OpenClaw is registered as the device assistant."
} else {
"Let Android launch OpenClaw from the assistant gesture. Google Assistant App Actions still work separately."
},
style = mobileCallout,
)
},
trailingContent = {
Button(
onClick = {
assistantRoleLauncher.launch(
context
.getSystemService(RoleManager::class.java)
.createRequestRoleIntent(RoleManager.ROLE_ASSISTANT),
)
},
colors = settingsPrimaryButtonColors(),
shape = RoundedCornerShape(14.dp),
) {
Text(
if (assistantRoleHeld) "Manage" else "Enable",
style = mobileCallout.copy(fontWeight = FontWeight.Bold),
)
}
},
)
}
}
}
@@ -1294,3 +1341,11 @@ private fun hasMotionCapabilities(context: Context): Boolean {
return sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) != null ||
sensorManager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER) != null
}
private fun isAssistantRoleAvailable(context: Context): Boolean {
return context.getSystemService(RoleManager::class.java).isRoleAvailable(RoleManager.ROLE_ASSISTANT)
}
private fun isAssistantRoleHeld(context: Context): Boolean {
return context.getSystemService(RoleManager::class.java).isRoleHeld(RoleManager.ROLE_ASSISTANT)
}

View File

@@ -33,6 +33,7 @@ import androidx.compose.material3.OutlinedTextFieldDefaults
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -59,12 +60,45 @@ import ai.openclaw.app.ui.mobileText
import ai.openclaw.app.ui.mobileTextSecondary
import ai.openclaw.app.ui.mobileTextTertiary
internal data class DraftApplication(
val input: String,
val lastAppliedDraft: String?,
val consumed: Boolean,
)
internal fun applyDraftText(
draftText: String?,
currentInput: String,
lastAppliedDraft: String?,
): DraftApplication {
val draft =
draftText?.trim()?.ifEmpty { null } ?: return DraftApplication(
input = currentInput,
lastAppliedDraft = null,
consumed = false,
)
if (draft == lastAppliedDraft) {
return DraftApplication(
input = currentInput,
lastAppliedDraft = lastAppliedDraft,
consumed = false,
)
}
return DraftApplication(
input = draft,
lastAppliedDraft = draft,
consumed = true,
)
}
@Composable
fun ChatComposer(
draftText: String?,
healthOk: Boolean,
thinkingLevel: String,
pendingRunCount: Int,
attachments: List<PendingImageAttachment>,
onDraftApplied: () -> Unit,
onPickImages: () -> Unit,
onRemoveAttachment: (id: String) -> Unit,
onSetThinkingLevel: (level: String) -> Unit,
@@ -73,8 +107,18 @@ fun ChatComposer(
onSend: (text: String) -> Unit,
) {
var input by rememberSaveable { mutableStateOf("") }
var lastAppliedDraft by rememberSaveable { mutableStateOf<String?>(null) }
var showThinkingMenu by remember { mutableStateOf(false) }
LaunchedEffect(draftText) {
val next = applyDraftText(draftText = draftText, currentInput = input, lastAppliedDraft = lastAppliedDraft)
input = next.input
lastAppliedDraft = next.lastAppliedDraft
if (next.consumed) {
onDraftApplied()
}
}
val canSend = pendingRunCount == 0 && (input.trim().isNotEmpty() || attachments.isNotEmpty()) && healthOk
val sendBusy = pendingRunCount > 0

View File

@@ -48,6 +48,31 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
internal fun resolvePendingAssistantAutoSend(
pendingPrompt: String?,
healthOk: Boolean,
pendingRunCount: Int,
): String? {
val prompt = pendingPrompt?.trim()?.ifEmpty { null } ?: return null
if (!healthOk || pendingRunCount > 0) return null
return prompt
}
internal suspend fun dispatchPendingAssistantAutoSend(
pendingPrompt: String?,
healthOk: Boolean,
pendingRunCount: Int,
dispatch: suspend (String) -> Boolean,
): Boolean {
val prompt =
resolvePendingAssistantAutoSend(
pendingPrompt = pendingPrompt,
healthOk = healthOk,
pendingRunCount = pendingRunCount,
) ?: return false
return dispatch(prompt)
}
@Composable
fun ChatSheetContent(viewModel: MainViewModel) {
val messages by viewModel.chatMessages.collectAsState()
@@ -60,11 +85,30 @@ fun ChatSheetContent(viewModel: MainViewModel) {
val streamingAssistantText by viewModel.chatStreamingAssistantText.collectAsState()
val pendingToolCalls by viewModel.chatPendingToolCalls.collectAsState()
val sessions by viewModel.chatSessions.collectAsState()
val chatDraft by viewModel.chatDraft.collectAsState()
val pendingAssistantAutoSend by viewModel.pendingAssistantAutoSend.collectAsState()
LaunchedEffect(Unit) {
viewModel.loadChat(mainSessionKey)
}
LaunchedEffect(pendingAssistantAutoSend, healthOk, pendingRunCount, thinkingLevel) {
val accepted =
dispatchPendingAssistantAutoSend(
pendingPrompt = pendingAssistantAutoSend,
healthOk = healthOk,
pendingRunCount = pendingRunCount,
) { prompt ->
viewModel.sendChatAwaitAcceptance(
message = prompt,
thinking = thinkingLevel,
attachments = emptyList(),
)
}
if (!accepted) return@LaunchedEffect
viewModel.clearPendingAssistantAutoSend()
}
val context = LocalContext.current
val resolver = context.contentResolver
val scope = rememberCoroutineScope()
@@ -118,10 +162,12 @@ fun ChatSheetContent(viewModel: MainViewModel) {
Row(modifier = Modifier.fillMaxWidth().imePadding()) {
ChatComposer(
draftText = chatDraft,
healthOk = healthOk,
thinkingLevel = thinkingLevel,
pendingRunCount = pendingRunCount,
attachments = attachments,
onDraftApplied = viewModel::clearChatDraft,
onPickImages = { pickImages.launch("image/*") },
onRemoveAttachment = { id -> attachments.removeAll { it.id == id } },
onSetThinkingLevel = { level -> viewModel.setChatThinkingLevel(level) },

View File

@@ -0,0 +1,7 @@
<resources>
<string-array name="ask_openclaw_query_patterns">
<item>ask OpenClaw $prompt</item>
<item>tell OpenClaw to $prompt</item>
<item>open OpenClaw and ask $prompt</item>
</string-array>
</resources>

View File

@@ -0,0 +1,17 @@
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<capability
android:name="custom.actions.intent.ASK_OPENCLAW"
app:queryPatterns="@array/ask_openclaw_query_patterns">
<intent
android:action="ai.openclaw.app.action.ASK_OPENCLAW"
android:targetPackage="ai.openclaw.app"
android:targetClass="ai.openclaw.app.MainActivity">
<parameter
android:name="prompt"
android:key="prompt"
android:mimeType="text/*"
android:required="true" />
</intent>
</capability>
</shortcuts>

View File

@@ -0,0 +1,43 @@
package ai.openclaw.app
import android.content.Intent
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
@RunWith(RobolectricTestRunner::class)
@Config(sdk = [34])
class AssistantLaunchTest {
@Test
fun parsesAssistGestureIntent() {
val parsed = parseAssistantLaunchIntent(Intent(Intent.ACTION_ASSIST))
requireNotNull(parsed)
assertEquals("assist", parsed.source)
assertNull(parsed.prompt)
assertFalse(parsed.autoSend)
}
@Test
fun parsesAppActionPrompt() {
val parsed =
parseAssistantLaunchIntent(
Intent(actionAskOpenClaw).putExtra(extraAssistantPrompt, " summarize my unread texts "),
)
requireNotNull(parsed)
assertEquals("app_action", parsed.source)
assertEquals("summarize my unread texts", parsed.prompt)
assertTrue(parsed.autoSend)
}
@Test
fun ignoresUnrelatedIntents() {
assertNull(parseAssistantLaunchIntent(Intent(Intent.ACTION_VIEW)))
}
}

View File

@@ -0,0 +1,165 @@
package ai.openclaw.app
import ai.openclaw.app.gateway.GatewayEndpoint
import ai.openclaw.app.gateway.GatewaySession
import ai.openclaw.app.gateway.GatewayTlsProbeFailure
import ai.openclaw.app.gateway.GatewayTlsProbeResult
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNull
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.lang.reflect.Field
import java.util.UUID
@RunWith(RobolectricTestRunner::class)
@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))
}
@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))
}
@Test
fun resolveGatewayConnectAuth_prefersExplicitSetupAuthOverStoredPrefs() {
val app = RuntimeEnvironment.getApplication()
val securePrefs =
app.getSharedPreferences(
"openclaw.node.secure.test.${UUID.randomUUID()}",
android.content.Context.MODE_PRIVATE,
)
val prefs = SecurePrefs(app, securePrefsOverride = securePrefs)
prefs.setGatewayToken("stale-shared-token")
prefs.setGatewayBootstrapToken("")
prefs.setGatewayPassword("stale-password")
val runtime = NodeRuntime(app, prefs)
val auth =
runtime.resolveGatewayConnectAuth(
NodeRuntime.GatewayConnectAuth(
token = null,
bootstrapToken = "setup-bootstrap-token",
password = null,
),
)
assertNull(auth.token)
assertEquals("setup-bootstrap-token", auth.bootstrapToken)
assertNull(auth.password)
}
@Test
fun acceptGatewayTrustPrompt_preservesExplicitSetupAuth() =
runBlocking {
val app = RuntimeEnvironment.getApplication()
val securePrefs =
app.getSharedPreferences(
"openclaw.node.secure.test.${UUID.randomUUID()}",
android.content.Context.MODE_PRIVATE,
)
val prefs = SecurePrefs(app, securePrefsOverride = securePrefs)
prefs.setGatewayToken("stale-shared-token")
prefs.setGatewayBootstrapToken("")
prefs.setGatewayPassword("stale-password")
val runtime =
NodeRuntime(
app,
prefs,
tlsFingerprintProbe = { _, _ -> GatewayTlsProbeResult(fingerprintSha256 = "fp-1") },
)
val endpoint = GatewayEndpoint.manual(host = "gateway.example", port = 18789)
val explicitAuth =
NodeRuntime.GatewayConnectAuth(
token = null,
bootstrapToken = "setup-bootstrap-token",
password = null,
)
runtime.connect(endpoint, explicitAuth)
val prompt = waitForGatewayTrustPrompt(runtime)
assertEquals("setup-bootstrap-token", prompt.auth.bootstrapToken)
runtime.acceptGatewayTrustPrompt()
assertEquals("fp-1", prefs.loadGatewayTlsFingerprint(endpoint.stableId))
assertEquals("setup-bootstrap-token", desiredBootstrapToken(runtime, "nodeSession"))
assertEquals("setup-bootstrap-token", desiredBootstrapToken(runtime, "operatorSession"))
}
@Test
fun connect_showsSecureEndpointGuidanceWhenTlsProbeFails() {
val app = RuntimeEnvironment.getApplication()
val runtime =
NodeRuntime(
app,
tlsFingerprintProbe = { _, _ ->
GatewayTlsProbeResult(failure = GatewayTlsProbeFailure.TLS_UNAVAILABLE)
},
)
runtime.connect(
GatewayEndpoint.manual(host = "gateway.example", port = 18789),
NodeRuntime.GatewayConnectAuth(token = "shared-token", bootstrapToken = null, password = null),
)
assertEquals(
"Failed: this host requires wss:// or Tailscale Serve. No TLS endpoint detected.",
waitForStatusText(runtime),
)
assertNull(runtime.pendingGatewayTrust.value)
}
private fun waitForGatewayTrustPrompt(runtime: NodeRuntime): NodeRuntime.GatewayTrustPrompt {
repeat(50) {
runtime.pendingGatewayTrust.value?.let { return it }
Thread.sleep(10)
}
error("Expected pending gateway trust prompt")
}
private fun waitForStatusText(runtime: NodeRuntime): String {
repeat(50) {
val status = runtime.statusText.value
if (status != "Verify gateway TLS fingerprint…") {
return status
}
Thread.sleep(10)
}
error("Expected status text update")
}
private fun desiredBootstrapToken(runtime: NodeRuntime, sessionFieldName: String): String? {
val session = readField<GatewaySession>(runtime, sessionFieldName)
val desired = readField<Any?>(session, "desired") ?: return null
return readField(desired, "bootstrapToken")
}
private fun <T> readField(target: Any, name: String): T {
var type: Class<*>? = target.javaClass
while (type != null) {
try {
val field: Field = type.getDeclaredField(name)
field.isAccessible = true
@Suppress("UNCHECKED_CAST")
return field.get(target) as T
} catch (_: NoSuchFieldException) {
type = type.superclass
}
}
error("Field $name not found on ${target.javaClass.name}")
}
}

View File

@@ -4,6 +4,23 @@ import org.junit.Assert.assertEquals
import org.junit.Test
class GatewaySessionInvokeTimeoutTest {
@Test
fun formatGatewayAuthority_bracketsIpv6Hosts() {
assertEquals("[::1]:18789", formatGatewayAuthority("::1", 18_789))
}
@Test
fun buildGatewayWebSocketUrl_bracketsIpv6Hosts() {
assertEquals("ws://[::1]:18789", buildGatewayWebSocketUrl("::1", 18_789, useTls = false))
assertEquals("wss://[::1]:443", buildGatewayWebSocketUrl("::1", 443, useTls = true))
}
@Test
fun buildGatewayWebSocketUrl_normalizesPersistedBracketedIpv6Hosts() {
assertEquals("ws://[::1]:18789", buildGatewayWebSocketUrl("[::1]", 18_789, useTls = false))
assertEquals("wss://[::1]:443", buildGatewayWebSocketUrl("[::1]", 443, useTls = true))
}
@Test
fun resolveInvokeResultAckTimeoutMs_usesFloorWhenMissingOrTooSmall() {
assertEquals(15_000L, resolveInvokeResultAckTimeoutMs(null))

View File

@@ -10,6 +10,8 @@ import ai.openclaw.app.protocol.OpenClawLocationCommand
import ai.openclaw.app.protocol.OpenClawMotionCommand
import ai.openclaw.app.protocol.OpenClawSmsCommand
import ai.openclaw.app.gateway.GatewayEndpoint
import ai.openclaw.app.gateway.isLoopbackGatewayHost
import ai.openclaw.app.gateway.isPrivateLanGatewayHost
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNull
@@ -69,7 +71,7 @@ class ConnectionManagerTest {
@Test
fun resolveTlsParamsForEndpoint_manualRespectsManualTlsToggle() {
val endpoint = GatewayEndpoint.manual(host = "example.com", port = 443)
val endpoint = GatewayEndpoint.manual(host = "127.0.0.1", port = 443)
val off =
ConnectionManager.resolveTlsParamsForEndpoint(
@@ -89,6 +91,282 @@ class ConnectionManagerTest {
assertEquals(false, on?.allowTOFU)
}
@Test
fun resolveTlsParamsForEndpoint_manualNonLoopbackForcesTlsWhenToggleIsOff() {
val endpoint = GatewayEndpoint.manual(host = "example.com", port = 443)
val params =
ConnectionManager.resolveTlsParamsForEndpoint(
endpoint,
storedFingerprint = null,
manualTlsEnabled = false,
)
assertEquals(true, params?.required)
assertNull(params?.expectedFingerprint)
assertEquals(false, params?.allowTOFU)
}
@Test
fun resolveTlsParamsForEndpoint_manualPrivateLanRequiresTlsWhenToggleIsOff() {
val endpoint = GatewayEndpoint.manual(host = "192.168.1.20", port = 18789)
val params =
ConnectionManager.resolveTlsParamsForEndpoint(
endpoint,
storedFingerprint = null,
manualTlsEnabled = false,
)
assertEquals(true, params?.required)
assertNull(params?.expectedFingerprint)
assertEquals(false, params?.allowTOFU)
}
@Test
fun resolveTlsParamsForEndpoint_discoveryTailnetWithoutHintsStillRequiresTls() {
val endpoint =
GatewayEndpoint(
stableId = "_openclaw-gw._tcp.|local.|Test",
name = "Test",
host = "100.64.0.9",
port = 18789,
tlsEnabled = false,
tlsFingerprintSha256 = null,
)
val params =
ConnectionManager.resolveTlsParamsForEndpoint(
endpoint,
storedFingerprint = null,
manualTlsEnabled = false,
)
assertEquals(true, params?.required)
assertNull(params?.expectedFingerprint)
assertEquals(false, params?.allowTOFU)
}
@Test
fun resolveTlsParamsForEndpoint_discoveryPrivateLanWithoutHintsRequiresTls() {
val endpoint =
GatewayEndpoint(
stableId = "_openclaw-gw._tcp.|local.|Test",
name = "Test",
host = "192.168.1.20",
port = 18789,
tlsEnabled = false,
tlsFingerprintSha256 = null,
)
val params =
ConnectionManager.resolveTlsParamsForEndpoint(
endpoint,
storedFingerprint = null,
manualTlsEnabled = false,
)
assertEquals(true, params?.required)
assertNull(params?.expectedFingerprint)
assertEquals(false, params?.allowTOFU)
}
@Test
fun resolveTlsParamsForEndpoint_discoveryLoopbackWithoutHintsCanStayCleartext() {
val endpoint =
GatewayEndpoint(
stableId = "_openclaw-gw._tcp.|local.|Test",
name = "Test",
host = "127.0.0.1",
port = 18789,
tlsEnabled = false,
tlsFingerprintSha256 = null,
)
val params =
ConnectionManager.resolveTlsParamsForEndpoint(
endpoint,
storedFingerprint = null,
manualTlsEnabled = false,
)
assertNull(params)
}
@Test
fun resolveTlsParamsForEndpoint_discoveryLocalhostWithoutHintsCanStayCleartext() {
val endpoint =
GatewayEndpoint(
stableId = "_openclaw-gw._tcp.|local.|Test",
name = "Test",
host = "localhost",
port = 18789,
tlsEnabled = false,
tlsFingerprintSha256 = null,
)
val params =
ConnectionManager.resolveTlsParamsForEndpoint(
endpoint,
storedFingerprint = null,
manualTlsEnabled = false,
)
assertNull(params)
}
@Test
fun resolveTlsParamsForEndpoint_discoveryAndroidEmulatorWithoutHintsCanStayCleartext() {
val endpoint =
GatewayEndpoint(
stableId = "_openclaw-gw._tcp.|local.|Test",
name = "Test",
host = "10.0.2.2",
port = 18789,
tlsEnabled = false,
tlsFingerprintSha256 = null,
)
val params =
ConnectionManager.resolveTlsParamsForEndpoint(
endpoint,
storedFingerprint = null,
manualTlsEnabled = false,
)
assertNull(params)
}
@Test
fun isLoopbackGatewayHost_onlyTreatsEmulatorBridgeAsLocalWhenAllowed() {
assertTrue(isLoopbackGatewayHost("10.0.2.2", allowEmulatorBridgeAlias = true))
assertFalse(isLoopbackGatewayHost("10.0.2.2", allowEmulatorBridgeAlias = false))
}
@Test
fun isPrivateLanGatewayHost_acceptsLanHostsButRejectsTailnetHosts() {
assertTrue(isPrivateLanGatewayHost("192.168.1.20"))
assertTrue(isPrivateLanGatewayHost("gateway.local"))
assertFalse(isPrivateLanGatewayHost("100.64.0.9"))
assertFalse(isPrivateLanGatewayHost("gateway.tailnet.ts.net"))
}
@Test
fun resolveTlsParamsForEndpoint_discoveryIpv6LoopbackWithoutHintsCanStayCleartext() {
val endpoint =
GatewayEndpoint(
stableId = "_openclaw-gw._tcp.|local.|Test",
name = "Test",
host = "::1",
port = 18789,
tlsEnabled = false,
tlsFingerprintSha256 = null,
)
val params =
ConnectionManager.resolveTlsParamsForEndpoint(
endpoint,
storedFingerprint = null,
manualTlsEnabled = false,
)
assertNull(params)
}
@Test
fun resolveTlsParamsForEndpoint_discoveryMappedIpv4LoopbackWithoutHintsCanStayCleartext() {
val endpoint =
GatewayEndpoint(
stableId = "_openclaw-gw._tcp.|local.|Test",
name = "Test",
host = "::ffff:127.0.0.1",
port = 18789,
tlsEnabled = false,
tlsFingerprintSha256 = null,
)
val params =
ConnectionManager.resolveTlsParamsForEndpoint(
endpoint,
storedFingerprint = null,
manualTlsEnabled = false,
)
assertNull(params)
}
@Test
fun resolveTlsParamsForEndpoint_discoveryNonLoopbackIpv6WithoutHintsRequiresTls() {
val endpoint =
GatewayEndpoint(
stableId = "_openclaw-gw._tcp.|local.|Test",
name = "Test",
host = "2001:db8::1",
port = 18789,
tlsEnabled = false,
tlsFingerprintSha256 = null,
)
val params =
ConnectionManager.resolveTlsParamsForEndpoint(
endpoint,
storedFingerprint = null,
manualTlsEnabled = false,
)
assertEquals(true, params?.required)
assertNull(params?.expectedFingerprint)
assertEquals(false, params?.allowTOFU)
}
@Test
fun resolveTlsParamsForEndpoint_discoveryUnspecifiedIpv4WithoutHintsRequiresTls() {
val endpoint =
GatewayEndpoint(
stableId = "_openclaw-gw._tcp.|local.|Test",
name = "Test",
host = "0.0.0.0",
port = 18789,
tlsEnabled = false,
tlsFingerprintSha256 = null,
)
val params =
ConnectionManager.resolveTlsParamsForEndpoint(
endpoint,
storedFingerprint = null,
manualTlsEnabled = false,
)
assertEquals(true, params?.required)
assertNull(params?.expectedFingerprint)
assertEquals(false, params?.allowTOFU)
}
@Test
fun resolveTlsParamsForEndpoint_discoveryUnspecifiedIpv6WithoutHintsRequiresTls() {
val endpoint =
GatewayEndpoint(
stableId = "_openclaw-gw._tcp.|local.|Test",
name = "Test",
host = "::",
port = 18789,
tlsEnabled = false,
tlsFingerprintSha256 = null,
)
val params =
ConnectionManager.resolveTlsParamsForEndpoint(
endpoint,
storedFingerprint = null,
manualTlsEnabled = false,
)
assertEquals(true, params?.required)
assertNull(params?.expectedFingerprint)
assertEquals(false, params?.allowTOFU)
}
@Test
fun buildNodeConnectOptions_advertisesRequestableSmsSearchWithoutSmsCapability() {
val options =

View File

@@ -25,18 +25,17 @@ class GatewayConfigResolverTest {
}
@Test
fun parseGatewayEndpointUsesDefaultCleartextPortForBareWsUrls() {
fun parseGatewayEndpointRejectsNonLoopbackCleartextWsUrls() {
val parsed = parseGatewayEndpoint("ws://gateway.example")
assertEquals(
GatewayEndpointConfig(
host = "gateway.example",
port = 18789,
tls = false,
displayUrl = "http://gateway.example:18789",
),
parsed,
)
assertNull(parsed)
}
@Test
fun parseGatewayEndpointRejectsTailnetCleartextWsUrls() {
val parsed = parseGatewayEndpoint("ws://100.64.0.9:18789")
assertNull(parsed)
}
@Test
@@ -55,30 +54,158 @@ class GatewayConfigResolverTest {
}
@Test
fun parseGatewayEndpointKeepsExplicitNonDefaultPortInDisplayUrl() {
val parsed = parseGatewayEndpoint("http://gateway.example:8080")
fun parseGatewayEndpointAllowsLoopbackCleartextWsUrls() {
val parsed = parseGatewayEndpoint("ws://127.0.0.1")
assertEquals(
GatewayEndpointConfig(
host = "gateway.example",
port = 8080,
host = "127.0.0.1",
port = 18789,
tls = false,
displayUrl = "http://gateway.example:8080",
displayUrl = "http://127.0.0.1:18789",
),
parsed,
)
}
@Test
fun parseGatewayEndpointKeepsExplicitCleartextPort80InDisplayUrl() {
val parsed = parseGatewayEndpoint("http://gateway.example:80")
fun parseGatewayEndpointAllowsLocalhostCleartextWsUrls() {
val parsed = parseGatewayEndpoint("ws://localhost:18789")
assertEquals(
GatewayEndpointConfig(
host = "gateway.example",
host = "localhost",
port = 18789,
tls = false,
displayUrl = "http://localhost:18789",
),
parsed,
)
}
@Test
fun parseGatewayEndpointAllowsAndroidEmulatorCleartextWsUrls() {
val parsed = parseGatewayEndpoint("ws://10.0.2.2:18789")
assertEquals(
GatewayEndpointConfig(
host = "10.0.2.2",
port = 18789,
tls = false,
displayUrl = "http://10.0.2.2:18789",
),
parsed,
)
}
@Test
fun parseGatewayEndpointAllowsPrivateLanCleartextWsUrls() {
val parsed = parseGatewayEndpoint("ws://192.168.1.20:18789")
assertEquals(
GatewayEndpointConfig(
host = "192.168.1.20",
port = 18789,
tls = false,
displayUrl = "http://192.168.1.20:18789",
),
parsed,
)
}
@Test
fun parseGatewayEndpointAllowsMdnsCleartextWsUrls() {
val parsed = parseGatewayEndpoint("ws://gateway.local:18789")
assertEquals(
GatewayEndpointConfig(
host = "gateway.local",
port = 18789,
tls = false,
displayUrl = "http://gateway.local:18789",
),
parsed,
)
}
@Test
fun parseGatewayEndpointAllowsIpv6LoopbackCleartextWsUrls() {
val parsed = parseGatewayEndpoint("ws://[::1]")
assertEquals("::1", parsed?.host)
assertEquals(18789, parsed?.port)
assertEquals(false, parsed?.tls)
assertEquals("http://[::1]:18789", parsed?.displayUrl)
}
@Test
fun parseGatewayEndpointAllowsIpv4MappedIpv6LoopbackCleartextWsUrls() {
val parsed = parseGatewayEndpoint("ws://[::ffff:127.0.0.1]")
assertEquals("::ffff:127.0.0.1", parsed?.host)
assertEquals(18789, parsed?.port)
assertEquals(false, parsed?.tls)
assertEquals("http://[::ffff:127.0.0.1]:18789", parsed?.displayUrl)
}
@Test
fun parseGatewayEndpointRejectsCleartextLoopbackPrefixBypassHost() {
val parsed = parseGatewayEndpoint("http://127.attacker.example:80")
assertNull(parsed)
}
@Test
fun parseGatewayEndpointRejectsNonLoopbackIpv6CleartextWsUrls() {
val parsed = parseGatewayEndpoint("ws://[2001:db8::1]")
assertNull(parsed)
}
@Test
fun parseGatewayEndpointAllowsLinkLocalIpv6ZoneCleartextWsUrls() {
val parsed = parseGatewayEndpoint("ws://[fe80::1%25eth0]")
assertEquals("fe80::1%25eth0", parsed?.host)
assertEquals(18789, parsed?.port)
assertEquals(false, parsed?.tls)
assertEquals("http://[fe80::1%25eth0]:18789", parsed?.displayUrl)
}
@Test
fun parseGatewayEndpointAllowsSecureIpv6ZoneUrls() {
val parsed = parseGatewayEndpoint("wss://[fe80::1%25wlan0]:443")
assertEquals("fe80::1%25wlan0", parsed?.host)
assertEquals(443, parsed?.port)
assertEquals(true, parsed?.tls)
assertEquals("https://[fe80::1%25wlan0]", parsed?.displayUrl)
}
@Test
fun parseGatewayEndpointRejectsUnspecifiedIpv4CleartextHttpUrls() {
val parsed = parseGatewayEndpoint("http://0.0.0.0:80")
assertNull(parsed)
}
@Test
fun parseGatewayEndpointRejectsUnspecifiedIpv6CleartextWsUrls() {
val parsed = parseGatewayEndpoint("ws://[::]")
assertNull(parsed)
}
@Test
fun parseGatewayEndpointAllowsLoopbackCleartextHttpUrls() {
val parsed = parseGatewayEndpoint("http://localhost:80")
assertEquals(
GatewayEndpointConfig(
host = "localhost",
port = 80,
tls = false,
displayUrl = "http://gateway.example:80",
displayUrl = "http://localhost:80",
),
parsed,
)
@@ -133,6 +260,43 @@ class GatewayConfigResolverTest {
assertNull(resolved)
}
@Test
fun resolveScannedSetupCodeRejectsNonLoopbackCleartextGateway() {
val setupCode =
encodeSetupCode("""{"url":"ws://attacker.example:18789","bootstrapToken":"bootstrap-1"}""")
val resolved = resolveScannedSetupCode(setupCode)
assertNull(resolved)
}
@Test
fun resolveScannedSetupCodeResultFlagsInsecureRemoteGateway() {
val setupCode =
encodeSetupCode("""{"url":"ws://attacker.example:18789","bootstrapToken":"bootstrap-1"}""")
val resolved = resolveScannedSetupCodeResult(setupCode)
assertNull(resolved.setupCode)
assertEquals(GatewayEndpointValidationError.INSECURE_REMOTE_URL, resolved.error)
}
@Test
fun parseGatewayEndpointResultFlagsInsecureRemoteGateway() {
val parsed = parseGatewayEndpointResult("ws://gateway.example:18789")
assertNull(parsed.config)
assertEquals(GatewayEndpointValidationError.INSECURE_REMOTE_URL, parsed.error)
}
@Test
fun parseGatewayEndpointResultFlagsInsecureLanCleartextGateway() {
val parsed = parseGatewayEndpointResult("ws://192.168.1.20:18789")
assertNull(parsed.config)
assertEquals(GatewayEndpointValidationError.INSECURE_REMOTE_URL, parsed.error)
}
@Test
fun decodeGatewaySetupCodeParsesBootstrapToken() {
val setupCode =
@@ -155,9 +319,13 @@ class GatewayConfigResolverTest {
resolveGatewayConnectConfig(
useSetupCode = true,
setupCode = setupCode,
manualHost = "",
manualPort = "",
manualTls = true,
savedManualHost = "",
savedManualPort = "",
savedManualTls = true,
manualHostInput = "",
manualPortInput = "",
manualTlsInput = true,
fallbackBootstrapToken = "",
fallbackToken = "shared-token",
fallbackPassword = "shared-password",
)
@@ -179,9 +347,13 @@ class GatewayConfigResolverTest {
resolveGatewayConnectConfig(
useSetupCode = true,
setupCode = setupCode,
manualHost = "",
manualPort = "",
manualTls = true,
savedManualHost = "",
savedManualPort = "",
savedManualTls = true,
manualHostInput = "",
manualPortInput = "",
manualTlsInput = true,
fallbackBootstrapToken = "",
fallbackToken = "shared-token",
fallbackPassword = "shared-password",
)
@@ -194,6 +366,96 @@ class GatewayConfigResolverTest {
assertNull(resolved?.password?.takeIf { it.isNotEmpty() })
}
@Test
fun resolveGatewayConnectConfigManualPreservesBootstrapTokenWhenNoReplacementAuthExists() {
val resolved =
resolveGatewayConnectConfig(
useSetupCode = false,
setupCode = "",
savedManualHost = "127.0.0.1",
savedManualPort = "18789",
savedManualTls = false,
manualHostInput = "127.0.0.1",
manualPortInput = "18789",
manualTlsInput = false,
fallbackBootstrapToken = "bootstrap-1",
fallbackToken = "",
fallbackPassword = "",
)
assertEquals("127.0.0.1", resolved?.host)
assertEquals(18789, resolved?.port)
assertEquals(false, resolved?.tls)
assertEquals("bootstrap-1", resolved?.bootstrapToken)
assertEquals("", resolved?.token)
assertEquals("", resolved?.password)
}
@Test
fun resolveGatewayConnectConfigManualDropsBootstrapTokenWhenReplacementPasswordExists() {
val resolved =
resolveGatewayConnectConfig(
useSetupCode = false,
setupCode = "",
savedManualHost = "127.0.0.1",
savedManualPort = "18789",
savedManualTls = false,
manualHostInput = "127.0.0.1",
manualPortInput = "18789",
manualTlsInput = false,
fallbackBootstrapToken = "bootstrap-1",
fallbackToken = "",
fallbackPassword = "password-1",
)
assertEquals("", resolved?.bootstrapToken)
assertEquals("", resolved?.token)
assertEquals("password-1", resolved?.password)
}
@Test
fun resolveGatewayConnectConfigManualDropsBootstrapTokenWhenEndpointChanges() {
val resolved =
resolveGatewayConnectConfig(
useSetupCode = false,
setupCode = "",
savedManualHost = "127.0.0.1",
savedManualPort = "18789",
savedManualTls = false,
manualHostInput = "127.0.0.2",
manualPortInput = "18789",
manualTlsInput = false,
fallbackBootstrapToken = "bootstrap-1",
fallbackToken = "",
fallbackPassword = "",
)
assertEquals("", resolved?.bootstrapToken)
assertEquals("127.0.0.2", resolved?.host)
}
@Test
fun resolveGatewayConnectConfigAllowsPrivateLanManualCleartextEndpoint() {
val resolved =
resolveGatewayConnectConfig(
useSetupCode = false,
setupCode = "",
savedManualHost = "",
savedManualPort = "",
savedManualTls = false,
manualHostInput = "192.168.31.100",
manualPortInput = "18789",
manualTlsInput = false,
fallbackBootstrapToken = "bootstrap-1",
fallbackToken = "",
fallbackPassword = "",
)
assertEquals("192.168.31.100", resolved?.host)
assertEquals(18789, resolved?.port)
assertEquals(false, resolved?.tls)
}
private fun encodeSetupCode(payloadJson: String): String {
return Base64.getUrlEncoder().withoutPadding().encodeToString(payloadJson.toByteArray(Charsets.UTF_8))
}

View File

@@ -0,0 +1,27 @@
package ai.openclaw.app.ui
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
class OnboardingFlowLogicTest {
@Test
fun blocksFinishWhenOnlyOperatorIsConnected() {
assertFalse(canFinishOnboarding(isConnected = true, isNodeConnected = false))
}
@Test
fun blocksFinishWhenDisconnected() {
assertFalse(canFinishOnboarding(isConnected = false, isNodeConnected = false))
}
@Test
fun blocksFinishWhenOnlyNodeIsConnected() {
assertFalse(canFinishOnboarding(isConnected = false, isNodeConnected = true))
}
@Test
fun allowsFinishOnlyWhenOperatorAndNodeAreConnected() {
assertTrue(canFinishOnboarding(isConnected = true, isNodeConnected = true))
}
}

View File

@@ -0,0 +1,44 @@
package ai.openclaw.app.ui.chat
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
class ChatComposerDraftTest {
@Test
fun clearsLastAppliedDraftWhenViewModelDraftResets() {
val consumed =
applyDraftText(
draftText = "repeat this",
currentInput = "",
lastAppliedDraft = null,
)
assertTrue(consumed.consumed)
assertEquals("repeat this", consumed.input)
assertEquals("repeat this", consumed.lastAppliedDraft)
val cleared =
applyDraftText(
draftText = null,
currentInput = consumed.input,
lastAppliedDraft = consumed.lastAppliedDraft,
)
assertFalse(cleared.consumed)
assertEquals("repeat this", cleared.input)
assertEquals(null, cleared.lastAppliedDraft)
val repeated =
applyDraftText(
draftText = "repeat this",
currentInput = cleared.input,
lastAppliedDraft = cleared.lastAppliedDraft,
)
assertTrue(repeated.consumed)
assertEquals("repeat this", repeated.input)
assertEquals("repeat this", repeated.lastAppliedDraft)
}
}

View File

@@ -0,0 +1,72 @@
package ai.openclaw.app.ui.chat
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
import kotlinx.coroutines.runBlocking
class ChatSheetContentTest {
@Test
fun resolvesPendingAssistantAutoSendOnlyWhenChatIsReady() {
assertNull(
resolvePendingAssistantAutoSend(
pendingPrompt = "summarize mail",
healthOk = false,
pendingRunCount = 0,
),
)
assertNull(
resolvePendingAssistantAutoSend(
pendingPrompt = "summarize mail",
healthOk = true,
pendingRunCount = 1,
),
)
assertEquals(
"summarize mail",
resolvePendingAssistantAutoSend(
pendingPrompt = " summarize mail ",
healthOk = true,
pendingRunCount = 0,
),
)
}
@Test
fun keepsPendingAssistantAutoSendWhenDispatchRejected() = runBlocking {
var dispatchedPrompt: String? = null
val consumed =
dispatchPendingAssistantAutoSend(
pendingPrompt = "summarize mail",
healthOk = true,
pendingRunCount = 0,
) { prompt ->
dispatchedPrompt = prompt
false
}
assertFalse(consumed)
assertEquals("summarize mail", dispatchedPrompt)
}
@Test
fun clearsPendingAssistantAutoSendOnlyAfterAcceptedDispatch() = runBlocking {
var dispatchedPrompt: String? = null
val consumed =
dispatchPendingAssistantAutoSend(
pendingPrompt = "summarize mail",
healthOk = true,
pendingRunCount = 0,
) { prompt ->
dispatchedPrompt = prompt
true
}
assertTrue(consumed)
assertEquals("summarize mail", dispatchedPrompt)
}
}

View File

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

View File

@@ -65,9 +65,9 @@ Release behavior:
- Beta release also switches the app to `OpenClawPushTransport=relay`, `OpenClawPushDistribution=official`, and `OpenClawPushAPNsEnvironment=production`.
- The beta flow does not modify `apps/ios/.local-signing.xcconfig` or `apps/ios/LocalSigning.xcconfig`.
- Root `package.json.version` is the only version source for iOS.
- A root version like `2026.3.30-beta.1` becomes:
- `CFBundleShortVersionString = 2026.3.30`
- `CFBundleVersion = next TestFlight build number for 2026.3.30`
- A root version like `2026.4.1-beta.1` becomes:
- `CFBundleShortVersionString = 2026.4.1`
- `CFBundleVersion = next TestFlight build number for 2026.4.1`
Required env for beta builds:

View File

@@ -69,6 +69,13 @@ enum GatewaySettingsStore {
account: self.preferredGatewayStableIDAccount)
}
static func clearPreferredGatewayStableID(defaults: UserDefaults = .standard) {
_ = KeychainStore.delete(
service: self.gatewayService,
account: self.preferredGatewayStableIDAccount)
defaults.removeObject(forKey: self.preferredGatewayStableIDDefaultsKey)
}
static func loadLastDiscoveredGatewayStableID() -> String? {
if let value = KeychainStore.loadString(
service: self.gatewayService,
@@ -89,6 +96,13 @@ enum GatewaySettingsStore {
account: self.lastDiscoveredGatewayStableIDAccount)
}
static func clearLastDiscoveredGatewayStableID(defaults: UserDefaults = .standard) {
_ = KeychainStore.delete(
service: self.gatewayService,
account: self.lastDiscoveredGatewayStableIDAccount)
defaults.removeObject(forKey: self.lastDiscoveredGatewayStableIDDefaultsKey)
}
static func loadGatewayToken(instanceId: String) -> String? {
let account = self.gatewayTokenAccount(instanceId: instanceId)
let token = KeychainStore.loadString(service: self.gatewayService, account: account)?
@@ -119,6 +133,12 @@ enum GatewaySettingsStore {
account: self.gatewayBootstrapTokenAccount(instanceId: instanceId))
}
static func clearGatewayBootstrapToken(instanceId: String) {
_ = KeychainStore.delete(
service: self.gatewayService,
account: self.gatewayBootstrapTokenAccount(instanceId: instanceId))
}
static func loadGatewayPassword(instanceId: String) -> String? {
KeychainStore.loadString(
service: self.gatewayService,

View File

@@ -33,6 +33,19 @@ extension NodeAppModel {
return base.appendingPathComponent("__openclaw__/a2ui/").absoluteString + "?platform=ios"
}
/// Normalize a URL string for trust comparison: lowercase scheme/host and strip fragment.
/// This matches the normalization applied by ScreenController.isTrustedCanvasUIURL so that
/// SPA hash-routing fragments and scheme/host casing do not silently prevent trust being set.
static func normalizeURLForTrustComparison(_ raw: String) -> String {
guard let url = URL(string: raw),
var components = URLComponents(url: url, resolvingAgainstBaseURL: false)
else { return raw }
components.fragment = nil
components.scheme = components.scheme?.lowercased()
components.host = components.host?.lowercased()
return components.url?.absoluteString ?? raw
}
func showA2UIOnConnectIfNeeded() async {
await MainActor.run {
// Keep the bundled home canvas as the default connected view.
@@ -46,7 +59,7 @@ extension NodeAppModel {
guard let initialUrl = await self.resolveA2UIHostURLWithCapabilityRefresh() else {
return .hostNotConfigured
}
self.screen.navigate(to: initialUrl)
self.screen.navigate(to: initialUrl, trustA2UIActions: true)
if await self.screen.waitForA2UIReady(timeoutMs: timeoutMs) {
return .ready(initialUrl)
}
@@ -54,7 +67,7 @@ extension NodeAppModel {
// First render can fail when scoped capability rotates between reconnects.
guard await self.gatewaySession.refreshNodeCanvasCapability() else { return .hostUnavailable }
guard let refreshedUrl = await self.resolveA2UIHostURL() else { return .hostUnavailable }
self.screen.navigate(to: refreshedUrl)
self.screen.navigate(to: refreshedUrl, trustA2UIActions: true)
if await self.screen.waitForA2UIReady(timeoutMs: timeoutMs) {
return .ready(refreshedUrl)
}

View File

@@ -851,7 +851,8 @@ final class NodeAppModel {
if url.isEmpty {
self.screen.showDefaultCanvas()
} else {
self.screen.navigate(to: url)
let trustedA2UIURL = await self.resolveA2UIHostURL()
self.screen.navigate(to: url, trustA2UIActions: trustedA2UIURL == Self.normalizeURLForTrustComparison(url))
}
return BridgeInvokeResponse(id: req.id, ok: true)
case OpenClawCanvasCommand.hide.rawValue:
@@ -859,7 +860,9 @@ final class NodeAppModel {
return BridgeInvokeResponse(id: req.id, ok: true)
case OpenClawCanvasCommand.navigate.rawValue:
let params = try Self.decodeParams(OpenClawCanvasNavigateParams.self, from: req.paramsJSON)
self.screen.navigate(to: params.url)
let trimmedURL = params.url.trimmingCharacters(in: .whitespacesAndNewlines)
let trustedA2UIURL = await self.resolveA2UIHostURL()
self.screen.navigate(to: trimmedURL, trustA2UIActions: trustedA2UIURL == Self.normalizeURLForTrustComparison(trimmedURL))
return BridgeInvokeResponse(id: req.id, ok: true)
case OpenClawCanvasCommand.evalJS.rawValue:
let params = try Self.decodeParams(OpenClawCanvasEvalParams.self, from: req.paramsJSON)
@@ -1697,14 +1700,24 @@ extension NodeAppModel {
password: password,
nodeOptions: connectOptions)
self.prepareForGatewayConnect(url: url, stableID: effectiveStableID)
self.startOperatorGatewayLoop(
url: url,
stableID: effectiveStableID,
if self.shouldStartOperatorGatewayLoop(
token: token,
bootstrapToken: bootstrapToken,
password: password,
nodeOptions: connectOptions,
sessionBox: sessionBox)
stableID: effectiveStableID)
{
self.startOperatorGatewayLoop(
url: url,
stableID: effectiveStableID,
token: token,
bootstrapToken: bootstrapToken,
password: password,
nodeOptions: connectOptions,
sessionBox: sessionBox)
} else {
self.operatorGatewayTask = nil
Task { await self.operatorGateway.disconnect() }
}
self.startNodeGatewayLoop(
url: url,
stableID: effectiveStableID,
@@ -1785,6 +1798,86 @@ private extension NodeAppModel {
self.apnsLastRegisteredTokenHex = nil
}
func shouldStartOperatorGatewayLoop(
token: String?,
bootstrapToken: String?,
password: String?,
stableID _: String) -> Bool
{
Self.shouldStartOperatorGatewayLoop(
token: token,
bootstrapToken: bootstrapToken,
password: password,
hasStoredOperatorToken: self.hasStoredGatewayRoleToken("operator"))
}
func hasStoredGatewayRoleToken(_ role: String) -> Bool {
let identity = DeviceIdentityStore.loadOrCreate()
return DeviceAuthStore.loadToken(deviceId: identity.deviceId, role: role) != nil
}
static func shouldStartOperatorGatewayLoop(
token: String?,
bootstrapToken: String?,
password: String?,
hasStoredOperatorToken: Bool) -> Bool
{
let trimmedToken = token?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
if !trimmedToken.isEmpty {
return true
}
let trimmedPassword = password?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
if !trimmedPassword.isEmpty {
return true
}
let trimmedBootstrapToken = bootstrapToken?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
if !trimmedBootstrapToken.isEmpty {
return false
}
return hasStoredOperatorToken
}
static func clearingBootstrapToken(in config: GatewayConnectConfig?) -> GatewayConnectConfig? {
guard let config else { return nil }
let trimmedBootstrapToken = config.bootstrapToken?
.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
guard !trimmedBootstrapToken.isEmpty else { return config }
return GatewayConnectConfig(
url: config.url,
stableID: config.stableID,
tls: config.tls,
token: config.token,
bootstrapToken: nil,
password: config.password,
nodeOptions: config.nodeOptions)
}
func currentGatewayReconnectAuth(
fallbackToken: String?,
fallbackBootstrapToken: String?,
fallbackPassword: String?) -> (token: String?, bootstrapToken: String?, password: String?)
{
if let cfg = self.activeGatewayConnectConfig {
return (cfg.token, cfg.bootstrapToken, cfg.password)
}
return (fallbackToken, fallbackBootstrapToken, fallbackPassword)
}
func clearPersistedGatewayBootstrapTokenIfNeeded() {
// Always drop the in-memory bootstrap token after the first successful
// bootstrap connect so reconnect loops cannot reuse a spent token.
self.activeGatewayConnectConfig = Self.clearingBootstrapToken(in: self.activeGatewayConnectConfig)
let trimmedInstanceId = UserDefaults.standard.string(forKey: "node.instanceId")?
.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
guard !trimmedInstanceId.isEmpty else { return }
guard
GatewaySettingsStore.loadGatewayBootstrapToken(instanceId: trimmedInstanceId) != nil
else { return }
GatewaySettingsStore.clearGatewayBootstrapToken(instanceId: trimmedInstanceId)
}
func refreshBackgroundReconnectSuppressionIfNeeded(source: String) {
guard self.isBackgrounded else { return }
guard !self.backgroundReconnectSuppressed else { return }
@@ -1841,11 +1934,15 @@ private extension NodeAppModel {
displayName: nodeOptions.clientDisplayName)
do {
let reconnectAuth = self.currentGatewayReconnectAuth(
fallbackToken: token,
fallbackBootstrapToken: bootstrapToken,
fallbackPassword: password)
try await self.operatorGateway.connect(
url: url,
token: token,
bootstrapToken: bootstrapToken,
password: password,
token: reconnectAuth.token,
bootstrapToken: reconnectAuth.bootstrapToken,
password: reconnectAuth.password,
connectOptions: operatorOptions,
sessionBox: sessionBox,
onConnected: { [weak self] in
@@ -1948,12 +2045,16 @@ private extension NodeAppModel {
do {
let epochMs = Int(Date().timeIntervalSince1970 * 1000)
let reconnectAuth = self.currentGatewayReconnectAuth(
fallbackToken: token,
fallbackBootstrapToken: bootstrapToken,
fallbackPassword: password)
GatewayDiagnostics.log("connect attempt epochMs=\(epochMs) url=\(url.absoluteString)")
try await self.nodeGateway.connect(
url: url,
token: token,
bootstrapToken: bootstrapToken,
password: password,
token: reconnectAuth.token,
bootstrapToken: reconnectAuth.bootstrapToken,
password: reconnectAuth.password,
connectOptions: currentOptions,
sessionBox: sessionBox,
onConnected: { [weak self] in
@@ -1965,6 +2066,30 @@ private extension NodeAppModel {
self.screen.errorText = nil
UserDefaults.standard.set(true, forKey: "gateway.autoconnect")
}
let usedBootstrapToken =
reconnectAuth.token?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty != false &&
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)
}
}
}
let relayData = await MainActor.run {
(
sessionKey: self.mainSessionKey,
@@ -1975,8 +2100,8 @@ private extension NodeAppModel {
ShareGatewayRelaySettings.saveConfig(
ShareGatewayRelayConfig(
gatewayURLString: url.absoluteString,
token: token,
password: password,
token: reconnectAuth.token,
password: reconnectAuth.password,
sessionKey: relayData.sessionKey,
deliveryChannel: relayData.deliveryChannel,
deliveryTo: relayData.deliveryTo))
@@ -3015,6 +3140,20 @@ extension NodeAppModel {
static func _test_currentDeepLinkKey() -> String {
self.expectedDeepLinkKey()
}
static func _test_shouldStartOperatorGatewayLoop(
token: String?,
bootstrapToken: String?,
password: String?,
hasStoredOperatorToken: Bool) -> Bool
{
self.shouldStartOperatorGatewayLoop(
token: token,
bootstrapToken: bootstrapToken,
password: password,
hasStoredOperatorToken: hasStoredOperatorToken)
}
}
#endif
// swiftlint:enable type_body_length file_length

View File

@@ -7,6 +7,7 @@ import WebKit
@Observable
final class ScreenController {
private weak var activeWebView: WKWebView?
private var trustedRemoteA2UIURL: URL?
var urlString: String = ""
var errorText: String?
@@ -26,10 +27,11 @@ final class ScreenController {
self.reload()
}
func navigate(to urlString: String) {
func navigate(to urlString: String, trustA2UIActions: Bool = false) {
let trimmed = urlString.trimmingCharacters(in: .whitespacesAndNewlines)
if trimmed.isEmpty {
self.urlString = ""
self.trustedRemoteA2UIURL = nil
self.reload()
return
}
@@ -43,6 +45,7 @@ final class ScreenController {
return
}
self.urlString = (trimmed == "/" ? "" : trimmed)
self.trustedRemoteA2UIURL = trustA2UIActions ? Self.normalizeTrustedRemoteA2UIURL(from: trimmed) : nil
self.reload()
}
@@ -72,6 +75,7 @@ final class ScreenController {
func showDefaultCanvas() {
self.urlString = ""
self.trustedRemoteA2UIURL = nil
self.reload()
}
@@ -237,28 +241,17 @@ final class ScreenController {
subdirectory: "CanvasScaffold")
func isTrustedCanvasUIURL(_ url: URL) -> Bool {
guard url.isFileURL else { return false }
let std = url.standardizedFileURL
if let expected = Self.canvasScaffoldURL,
std == expected.standardizedFileURL
{
return true
if url.isFileURL {
let std = url.standardizedFileURL
if let expected = Self.canvasScaffoldURL,
std == expected.standardizedFileURL
{
return true
}
return false
}
return false
}
private func applyScrollBehavior() {
guard let webView = self.activeWebView else { return }
let trimmed = self.urlString.trimmingCharacters(in: .whitespacesAndNewlines)
let allowScroll = !trimmed.isEmpty
let scrollView = webView.scrollView
// Default canvas needs raw touch events; external pages should scroll.
scrollView.isScrollEnabled = allowScroll
scrollView.bounces = allowScroll
}
func isLocalNetworkCanvasURL(_ url: URL) -> Bool {
LocalNetworkURLSupport.isLocalNetworkHTTPURL(url)
guard let trusted = self.trustedRemoteA2UIURL else { return false }
return Self.normalizeTrustedRemoteA2UIURL(from: url) == trusted
}
nonisolated static func parseA2UIActionBody(_ body: Any) -> [String: Any]? {
@@ -278,6 +271,36 @@ final class ScreenController {
}
return nil
}
private func applyScrollBehavior() {
guard let webView = self.activeWebView else { return }
let trimmed = self.urlString.trimmingCharacters(in: .whitespacesAndNewlines)
let allowScroll = !trimmed.isEmpty
let scrollView = webView.scrollView
// Default canvas needs raw touch events; external pages should scroll.
scrollView.isScrollEnabled = allowScroll
scrollView.bounces = allowScroll
}
private static func normalizeTrustedRemoteA2UIURL(from raw: String) -> URL? {
guard let url = URL(string: raw) else { return nil }
return self.normalizeTrustedRemoteA2UIURL(from: url)
}
private static func normalizeTrustedRemoteA2UIURL(from url: URL) -> URL? {
guard !url.isFileURL else { return nil }
guard let scheme = url.scheme?.lowercased(), scheme == "http" || scheme == "https" else {
return nil
}
guard let host = url.host?.trimmingCharacters(in: .whitespacesAndNewlines), !host.isEmpty else {
return nil
}
var components = URLComponents(url: url, resolvingAgainstBaseURL: false)
components?.scheme = scheme
components?.host = host.lowercased()
components?.fragment = nil
return components?.url
}
}
extension Double {

View File

@@ -180,12 +180,7 @@ private final class CanvasA2UIActionMessageHandler: NSObject, WKScriptMessageHan
guard let controller else { return }
guard let url = message.webView?.url else { return }
if url.isFileURL {
guard controller.isTrustedCanvasUIURL(url) else { return }
} else {
// For security, only accept actions from local-network pages (e.g. the canvas host).
guard controller.isLocalNetworkCanvasURL(url) else { return }
}
guard controller.isTrustedCanvasUIURL(url) else { return }
guard let body = ScreenController.parseA2UIActionBody(message.body) else { return }

View File

@@ -1008,6 +1008,11 @@ struct SettingsTab: View {
// Reset onboarding state + clear saved gateway connection (the two things RootCanvas checks).
GatewaySettingsStore.clearLastGatewayConnection()
GatewaySettingsStore.clearPreferredGatewayStableID()
GatewaySettingsStore.clearLastDiscoveredGatewayStableID()
// Resetting onboarding should also forget trusted gateway TLS fingerprints.
// Otherwise a restarted dev gateway can stay stuck in a local TLS cancel loop.
GatewayTLSStore.clearAllFingerprints()
OnboardingStateStore.reset()
// RootCanvas also short-circuits onboarding when these are true.

View File

@@ -5,6 +5,7 @@ import Testing
@testable import OpenClaw
@Suite(.serialized) struct GatewayConnectionSecurityTests {
@MainActor
private func makeController() -> GatewayConnectionController {
GatewayConnectionController(appModel: NodeAppModel(), startDiscovery: false)
}
@@ -32,8 +33,7 @@ import Testing
}
private func clearTLSFingerprint(stableID: String) {
let suite = UserDefaults(suiteName: "ai.openclaw.shared") ?? .standard
suite.removeObject(forKey: "gateway.tls.\(stableID)")
GatewayTLSStore.clearFingerprint(stableID: stableID)
}
@Test @MainActor func discoveredTLSParams_prefersStoredPinOverAdvertisedTXT() async {
@@ -126,4 +126,21 @@ import Testing
#expect(controller._test_resolveManualPort(host: "device.sample.ts.net.", port: 0, useTLS: true) == 443)
#expect(controller._test_resolveManualPort(host: "device.sample.ts.net", port: 18789, useTLS: true) == 18789)
}
@Test @MainActor func clearAllTLSFingerprints_removesStoredPins() async {
let stableID1 = "test|\(UUID().uuidString)"
let stableID2 = "test|\(UUID().uuidString)"
defer { GatewayTLSStore.clearAllFingerprints() }
GatewayTLSStore.saveFingerprint("11", stableID: stableID1)
GatewayTLSStore.saveFingerprint("22", stableID: stableID2)
#expect(GatewayTLSStore.loadFingerprint(stableID: stableID1) == "11")
#expect(GatewayTLSStore.loadFingerprint(stableID: stableID2) == "22")
GatewayTLSStore.clearAllFingerprints()
#expect(GatewayTLSStore.loadFingerprint(stableID: stableID1) == nil)
#expect(GatewayTLSStore.loadFingerprint(stableID: stableID2) == nil)
}
}

View File

@@ -96,6 +96,64 @@ private final class MockWatchMessagingService: @preconcurrency WatchMessagingSer
#expect(appModel.mainSessionKey == "agent:agent-123:main")
}
@Test func operatorLoopWaitsForBootstrapHandoffBeforeUsingStoredToken() {
#expect(
!NodeAppModel._test_shouldStartOperatorGatewayLoop(
token: nil,
bootstrapToken: "fresh-bootstrap-token",
password: nil,
hasStoredOperatorToken: true)
)
#expect(
!NodeAppModel._test_shouldStartOperatorGatewayLoop(
token: nil,
bootstrapToken: nil,
password: nil,
hasStoredOperatorToken: false)
)
#expect(
NodeAppModel._test_shouldStartOperatorGatewayLoop(
token: nil,
bootstrapToken: nil,
password: nil,
hasStoredOperatorToken: true)
)
#expect(
NodeAppModel._test_shouldStartOperatorGatewayLoop(
token: "shared-token",
bootstrapToken: "fresh-bootstrap-token",
password: nil,
hasStoredOperatorToken: false)
)
}
@Test func clearingBootstrapTokenStripsReconnectConfigEvenWithoutPersistence() {
let config = GatewayConnectConfig(
url: URL(string: "wss://gateway.example")!,
stableID: "test-gateway",
tls: nil,
token: nil,
bootstrapToken: "spent-bootstrap-token",
password: nil,
nodeOptions: GatewayConnectOptions(
role: "node",
scopes: [],
caps: [],
commands: [],
permissions: [:],
clientId: "openclaw-ios",
clientMode: "node",
clientDisplayName: nil))
let cleared = NodeAppModel.clearingBootstrapToken(in: config)
#expect(cleared?.bootstrapToken == nil)
#expect(cleared?.url == config.url)
#expect(cleared?.stableID == config.stableID)
#expect(cleared?.token == config.token)
#expect(cleared?.password == config.password)
#expect(cleared?.nodeOptions.role == config.nodeOptions.role)
}
@Test @MainActor func handleInvokeRejectsBackgroundCommands() async {
let appModel = NodeAppModel()
appModel.setScenePhase(.background)

View File

@@ -66,17 +66,26 @@ private func mountScreen(_ screen: ScreenController) throws -> (ScreenWebViewCoo
}
}
@Test @MainActor func localNetworkCanvasURLsAreAllowed() {
@Test @MainActor func trustedRemoteA2UIURLMustMatchExactly() {
let screen = ScreenController()
#expect(screen.isLocalNetworkCanvasURL(URL(string: "http://localhost:18789/")!) == true)
#expect(screen.isLocalNetworkCanvasURL(URL(string: "http://openclaw.local:18789/")!) == true)
#expect(screen.isLocalNetworkCanvasURL(URL(string: "http://peters-mac-studio-1:18789/")!) == true)
#expect(screen.isLocalNetworkCanvasURL(URL(string: "https://peters-mac-studio-1.ts.net:18789/")!) == true)
#expect(screen.isLocalNetworkCanvasURL(URL(string: "http://192.168.0.10:18789/")!) == true)
#expect(screen.isLocalNetworkCanvasURL(URL(string: "http://10.0.0.10:18789/")!) == true)
#expect(screen.isLocalNetworkCanvasURL(URL(string: "http://100.123.224.76:18789/")!) == true) // Tailscale CGNAT
#expect(screen.isLocalNetworkCanvasURL(URL(string: "https://example.com/")!) == false)
#expect(screen.isLocalNetworkCanvasURL(URL(string: "http://8.8.8.8/")!) == false)
let trusted = "https://node.ts.net:18789/__openclaw__/a2ui/?platform=ios"
screen.navigate(to: trusted, trustA2UIActions: true)
#expect(screen.isTrustedCanvasUIURL(URL(string: trusted)!) == true)
// Fragment differences must not affect trust (SPA hash routing).
#expect(screen.isTrustedCanvasUIURL(URL(string: "https://node.ts.net:18789/__openclaw__/a2ui/?platform=ios#step2")!) == true)
#expect(screen.isTrustedCanvasUIURL(URL(string: "https://node.ts.net:18789/__openclaw__/a2ui/?platform=android")!) == false)
#expect(screen.isTrustedCanvasUIURL(URL(string: "https://node.ts.net:18789/__openclaw__/canvas/")!) == false)
#expect(screen.isTrustedCanvasUIURL(URL(string: "https://evil.ts.net:18789/__openclaw__/a2ui/?platform=ios")!) == false)
#expect(screen.isTrustedCanvasUIURL(URL(string: "http://192.168.0.10:18789/")!) == false)
}
@Test @MainActor func genericNavigationClearsTrustedRemoteA2UIURL() {
let screen = ScreenController()
screen.navigate(to: "https://node.ts.net:18789/__openclaw__/a2ui/?platform=ios", trustA2UIActions: true)
screen.navigate(to: "https://evil.ts.net:18789/")
#expect(screen.isTrustedCanvasUIURL(URL(string: "https://node.ts.net:18789/__openclaw__/a2ui/?platform=ios")!) == false)
}
@Test func parseA2UIActionBodyAcceptsJSONString() throws {

View File

@@ -136,6 +136,17 @@ final class AppState {
forKey: voicePushToTalkEnabledKey) } }
}
var voiceWakeTriggersTalkMode: Bool {
didSet {
self.ifNotPreview {
UserDefaults.standard.set(self.voiceWakeTriggersTalkMode, forKey: voiceWakeTriggersTalkModeKey)
if self.swabbleEnabled {
Task { await VoiceWakeRuntime.shared.refresh(state: self) }
}
}
}
}
var talkEnabled: Bool {
didSet {
self.ifNotPreview {
@@ -275,6 +286,8 @@ final class AppState {
.stringArray(forKey: voiceWakeAdditionalLocalesKey) ?? []
self.voicePushToTalkEnabled = UserDefaults.standard
.object(forKey: voicePushToTalkEnabledKey) as? Bool ?? false
self.voiceWakeTriggersTalkMode = UserDefaults.standard
.object(forKey: voiceWakeTriggersTalkModeKey) as? Bool ?? false
self.talkEnabled = UserDefaults.standard.bool(forKey: talkEnabledKey)
self.seamColorHex = nil
if let storedHeartbeats = UserDefaults.standard.object(forKey: heartbeatsEnabledKey) as? Bool {

View File

@@ -22,6 +22,7 @@ let voiceWakeMicNameKey = "openclaw.voiceWakeMicName"
let voiceWakeLocaleKey = "openclaw.voiceWakeLocaleID"
let voiceWakeAdditionalLocalesKey = "openclaw.voiceWakeAdditionalLocaleIDs"
let voicePushToTalkEnabledKey = "openclaw.voicePushToTalkEnabled"
let voiceWakeTriggersTalkModeKey = "openclaw.voiceWakeTriggersTalkMode"
let talkEnabledKey = "openclaw.talkEnabled"
let iconOverrideKey = "openclaw.iconOverride"
let connectionModeKey = "openclaw.connectionMode"

View File

@@ -558,12 +558,16 @@ extension GatewayConnection {
func skillsInstall(
name: String,
installId: String,
dangerouslyForceUnsafeInstall: Bool? = nil,
timeoutMs: Int? = nil) async throws -> SkillInstallResult
{
var params: [String: AnyCodable] = [
"name": AnyCodable(name),
"installId": AnyCodable(installId),
]
if let dangerouslyForceUnsafeInstall {
params["dangerouslyForceUnsafeInstall"] = AnyCodable(dangerouslyForceUnsafeInstall)
}
if let timeoutMs {
params["timeoutMs"] = AnyCodable(timeoutMs)
}
@@ -614,11 +618,13 @@ extension GatewayConnection {
func chatHistory(
sessionKey: String,
limit: Int? = nil,
maxChars: Int? = nil,
timeoutMs: Int? = nil) async throws -> OpenClawChatHistoryPayload
{
let resolvedKey = self.canonicalizeSessionKey(sessionKey)
var params: [String: AnyCodable] = ["sessionKey": AnyCodable(resolvedKey)]
if let limit { params["limit"] = AnyCodable(limit) }
if let maxChars { params["maxChars"] = AnyCodable(maxChars) }
let timeout = timeoutMs.map { Double($0) }
return try await self.requestDecoded(
method: .chatHistory,

View File

@@ -16,9 +16,20 @@ enum HostEnvSecurityPolicy {
"RUBYOPT",
"BASH_ENV",
"ENV",
"BROWSER",
"GIT_EDITOR",
"GIT_EXTERNAL_DIFF",
"GIT_EXEC_PATH",
"GIT_SEQUENCE_EDITOR",
"GIT_TEMPLATE_DIR",
"GIT_SSL_NO_VERIFY",
"GIT_SSL_CAINFO",
"GIT_SSL_CAPATH",
"CC",
"CXX",
"CARGO_BUILD_RUSTC",
"CMAKE_C_COMPILER",
"CMAKE_CXX_COMPILER",
"SHELL",
"SHELLOPTS",
"PS4",
@@ -46,6 +57,9 @@ enum HostEnvSecurityPolicy {
"GIT_SSH",
"GIT_PROXY_COMMAND",
"GIT_ASKPASS",
"GIT_SSL_NO_VERIFY",
"GIT_SSL_CAINFO",
"GIT_SSL_CAPATH",
"SSH_ASKPASS",
"LESSOPEN",
"LESSCLOSE",
@@ -74,6 +88,54 @@ enum HostEnvSecurityPolicy {
"PHP_INI_SCAN_DIR",
"DENO_DIR",
"BUN_CONFIG_REGISTRY",
"YARN_RC_FILENAME",
"HTTP_PROXY",
"HTTPS_PROXY",
"ALL_PROXY",
"NO_PROXY",
"NODE_TLS_REJECT_UNAUTHORIZED",
"NODE_EXTRA_CA_CERTS",
"SSL_CERT_FILE",
"SSL_CERT_DIR",
"REQUESTS_CA_BUNDLE",
"CURL_CA_BUNDLE",
"DOCKER_HOST",
"DOCKER_TLS_VERIFY",
"DOCKER_CERT_PATH",
"PIP_INDEX_URL",
"PIP_PYPI_URL",
"PIP_EXTRA_INDEX_URL",
"PIP_CONFIG_FILE",
"PIP_FIND_LINKS",
"PIP_TRUSTED_HOST",
"UV_INDEX",
"UV_INDEX_URL",
"UV_PYTHON",
"UV_EXTRA_INDEX_URL",
"UV_DEFAULT_INDEX",
"DOCKER_HOST",
"DOCKER_TLS_VERIFY",
"DOCKER_CERT_PATH",
"DOCKER_CONTEXT",
"LIBRARY_PATH",
"CPATH",
"C_INCLUDE_PATH",
"CPLUS_INCLUDE_PATH",
"OBJC_INCLUDE_PATH",
"NODE_EXTRA_CA_CERTS",
"SSL_CERT_FILE",
"SSL_CERT_DIR",
"REQUESTS_CA_BUNDLE",
"CURL_CA_BUNDLE",
"GOPROXY",
"GONOSUMCHECK",
"GONOSUMDB",
"GONOPROXY",
"GOPRIVATE",
"GOENV",
"GOPATH",
"PYTHONUSERBASE",
"VIRTUAL_ENV",
"LUA_PATH",
"LUA_CPATH",
"GEM_HOME",
@@ -86,7 +148,8 @@ enum HostEnvSecurityPolicy {
static let blockedOverridePrefixes: [String] = [
"GIT_CONFIG_",
"NPM_CONFIG_"
"NPM_CONFIG_",
"CARGO_REGISTRIES_"
]
static let blockedPrefixes: [String] = [

View File

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

View File

@@ -18,6 +18,12 @@ final class TalkModeController {
TalkOverlayController.shared.dismiss()
}
await TalkModeRuntime.shared.setEnabled(enabled)
// Resume voice wake listener *after* TalkMode audio is fully torn down.
// Check swabbleEnabled (not voiceWakeTriggersTalkMode) so the paused wake listener
// resumes even if the user toggled "Trigger Talk Mode" off during the session.
if !enabled, AppStateStore.shared.swabbleEnabled {
Task { await VoiceWakeRuntime.shared.refresh(state: AppStateStore.shared) }
}
}
func updatePhase(_ phase: TalkModePhase) {

View File

@@ -335,6 +335,11 @@ actor TalkModeRuntime {
self.lastHeard = nil
self.phase = .thinking
await MainActor.run { TalkModeController.shared.updatePhase(.thinking) }
// Play "send" chime when the user's speech is finalized and about to be sent
let sendChime = await MainActor.run { AppStateStore.shared.voiceWakeSendChime }
if sendChime != .none {
await MainActor.run { VoiceWakeChimePlayer.play(sendChime, reason: "talk.send") }
}
await self.stopRecognition()
await self.sendAndSpeak(text)
}
@@ -456,6 +461,7 @@ actor TalkModeRuntime {
private func playAssistant(text: String) async {
guard let input = await self.preparePlaybackInput(text: text) else { return }
switch Self.playbackPlan(apiKey: input.apiKey, voiceId: input.voiceId) {
case let .elevenLabsThenSystemVoice(apiKey, voiceId):
do {

View File

@@ -82,6 +82,7 @@ actor VoiceWakeRuntime {
let localeID: String?
let triggerChime: VoiceWakeChime
let sendChime: VoiceWakeChime
let triggersTalkMode: Bool
}
private struct RecognitionUpdate {
@@ -100,7 +101,8 @@ actor VoiceWakeRuntime {
micID: state.voiceWakeMicID.isEmpty ? nil : state.voiceWakeMicID,
localeID: state.voiceWakeLocaleID.isEmpty ? nil : state.voiceWakeLocaleID,
triggerChime: state.voiceWakeTriggerChime,
sendChime: state.voiceWakeSendChime)
sendChime: state.voiceWakeSendChime,
triggersTalkMode: state.voiceWakeTriggersTalkMode)
return (enabled, config)
}
@@ -529,6 +531,21 @@ actor VoiceWakeRuntime {
}
private func beginCapture(command: String, triggerEndTime: TimeInterval?, config: RuntimeConfig) async {
// When "Trigger Talk Mode" is enabled, skip the capture/overlay flow entirely
// and activate Talk Mode immediately. Talk Mode handles its own STT pipeline.
// Pause the wake listener to avoid two audio pipelines competing on the mic
// (mirrors the push-to-talk coordination pattern).
if config.triggersTalkMode {
self.logger.info("voicewake trigger -> activating Talk Mode (skipping capture)")
DiagnosticsFileLog.shared.log(category: "voicewake.runtime", event: "triggerTalkMode")
if config.triggerChime != .none {
await MainActor.run { VoiceWakeChimePlayer.play(config.triggerChime, reason: "voicewake.trigger") }
}
self.pauseForPushToTalk()
await AppStateStore.shared.setTalkEnabled(true)
return
}
self.listeningState = .voiceWake
self.isCapturing = true
DiagnosticsFileLog.shared.log(category: "voicewake.runtime", event: "beginCapture")

View File

@@ -53,6 +53,16 @@ struct VoiceWakeSettings: View {
binding: self.voiceWakeBinding)
.disabled(!voiceWakeSupported)
SettingsToggleRow(
title: "Trigger Talk Mode",
subtitle: """
When a wake phrase is detected, activate Talk Mode for a full voice \
conversation (STT, LLM response, TTS playback) instead of sending a \
text message to the chat.
""",
binding: self.$state.voiceWakeTriggersTalkMode)
.disabled(!self.state.swabbleEnabled)
SettingsToggleRow(
title: "Hold Right Option to talk",
subtitle: """

View File

@@ -14,10 +14,11 @@ struct WideAreaGatewayBeacon: Equatable {
}
enum WideAreaGatewayDiscovery {
private static let maxCandidates = 40
private static let digPath = "/usr/bin/dig"
private static let defaultTimeoutSeconds: TimeInterval = 0.2
private static let nameserverProbeConcurrency = 6
// Security: wide-area discovery must trust only the Tailscale MagicDNS resolver.
// Probing arbitrary tailnet peers lets the fastest responder become DNS-SD authority.
private static let tailscaleDNSResolver = "100.100.100.100"
struct DiscoveryContext {
var tailscaleStatus: @Sendable () -> String?
@@ -39,27 +40,16 @@ enum WideAreaGatewayDiscovery {
timeoutSeconds - Date().timeIntervalSince(startedAt)
}
guard let ips = collectTailnetIPv4s(
statusJson: context.tailscaleStatus()).nonEmpty else { return [] }
var candidates = Array(ips.prefix(self.maxCandidates))
guard let nameserver = findNameserver(
candidates: &candidates,
guard let statusJson = context.tailscaleStatus(),
!collectTailnetIPv4s(statusJson: statusJson).isEmpty,
let discovery = loadWideAreaPtrRecords(
remaining: remaining,
dig: context.dig)
else {
return []
}
else { return [] }
guard let domain = OpenClawBonjour.wideAreaGatewayServiceDomain else { return [] }
let domainTrimmed = domain.trimmingCharacters(in: CharacterSet(charactersIn: "."))
let probeName = "_openclaw-gw._tcp.\(domainTrimmed)"
guard let ptrLines = context.dig(
["+short", "+time=1", "+tries=1", "@\(nameserver)", probeName, "PTR"],
min(defaultTimeoutSeconds, remaining()))?.split(whereSeparator: \.isNewline),
!ptrLines.isEmpty
else {
return []
}
let domainTrimmed = discovery.domainTrimmed
let ptrLines = discovery.ptrLines
let nameserver = self.tailscaleDNSResolver
var beacons: [WideAreaGatewayBeacon] = []
for raw in ptrLines {
@@ -148,68 +138,26 @@ enum WideAreaGatewayDiscovery {
return output
}
private static func findNameserver(
candidates: inout [String],
private static func loadWideAreaPtrRecords(
remaining: () -> TimeInterval,
dig: @escaping @Sendable (_ args: [String], _ timeout: TimeInterval) -> String?) -> String?
dig: @escaping @Sendable (_ args: [String], _ timeout: TimeInterval) -> String?)
-> (domainTrimmed: String, ptrLines: [Substring])?
{
guard let domain = OpenClawBonjour.wideAreaGatewayServiceDomain else { return nil }
let domainTrimmed = domain.trimmingCharacters(in: CharacterSet(charactersIn: "."))
let probeName = "_openclaw-gw._tcp.\(domainTrimmed)"
let budget = max(0, remaining())
if budget <= 0 { return nil }
let ips = candidates
candidates.removeAll(keepingCapacity: true)
if ips.isEmpty { return nil }
final class ProbeState: @unchecked Sendable {
let lock = NSLock()
var nextIndex = 0
var found: String?
guard let stdout = dig(
["+short", "+time=1", "+tries=1", "@\(self.tailscaleDNSResolver)", probeName, "PTR"],
min(defaultTimeoutSeconds, budget)),
let ptrLines = stdout.split(whereSeparator: \.isNewline).nonEmpty
else {
return nil
}
let state = ProbeState()
let deadline = Date().addingTimeInterval(max(0, remaining()))
let workerCount = min(self.nameserverProbeConcurrency, ips.count)
let group = DispatchGroup()
for _ in 0..<workerCount {
group.enter()
DispatchQueue.global(qos: .utility).async {
defer { group.leave() }
while Date() < deadline {
state.lock.lock()
if state.found != nil {
state.lock.unlock()
return
}
let i = state.nextIndex
state.nextIndex += 1
state.lock.unlock()
if i >= ips.count { return }
let ip = ips[i]
let budget = deadline.timeIntervalSinceNow
if budget <= 0 { return }
if let stdout = dig(
["+short", "+time=1", "+tries=1", "@\(ip)", probeName, "PTR"],
min(defaultTimeoutSeconds, budget)),
stdout.split(whereSeparator: \.isNewline).isEmpty == false
{
state.lock.lock()
if state.found == nil {
state.found = ip
}
state.lock.unlock()
return
}
}
}
}
_ = group.wait(timeout: .now() + max(0.0, remaining()))
return state.found
return (domainTrimmed, ptrLines)
}
private static func runDig(args: [String], timeout: TimeInterval) -> String? {

View File

@@ -2893,6 +2893,78 @@ public struct SkillsBinsResult: Codable, Sendable {
}
}
public struct SkillsSearchParams: Codable, Sendable {
public let query: String?
public let limit: Int?
public init(
query: String?,
limit: Int?)
{
self.query = query
self.limit = limit
}
private enum CodingKeys: String, CodingKey {
case query
case limit
}
}
public struct SkillsSearchResult: Codable, Sendable {
public let results: [[String: AnyCodable]]
public init(
results: [[String: AnyCodable]])
{
self.results = results
}
private enum CodingKeys: String, CodingKey {
case results
}
}
public struct SkillsDetailParams: Codable, Sendable {
public let slug: String
public init(
slug: String)
{
self.slug = slug
}
private enum CodingKeys: String, CodingKey {
case slug
}
}
public struct SkillsDetailResult: Codable, Sendable {
public let skill: AnyCodable
public let latestversion: AnyCodable?
public let metadata: AnyCodable?
public let owner: AnyCodable?
public init(
skill: AnyCodable,
latestversion: AnyCodable?,
metadata: AnyCodable?,
owner: AnyCodable?)
{
self.skill = skill
self.latestversion = latestversion
self.metadata = metadata
self.owner = owner
}
private enum CodingKeys: String, CodingKey {
case skill
case latestversion = "latestVersion"
case metadata
case owner
}
}
public struct CronJob: Codable, Sendable {
public let id: String
public let agentid: String?
@@ -3710,18 +3782,22 @@ public struct DevicePairResolvedEvent: Codable, Sendable {
public struct ChatHistoryParams: Codable, Sendable {
public let sessionkey: String
public let limit: Int?
public let maxchars: Int?
public init(
sessionkey: String,
limit: Int?)
limit: Int?,
maxchars: Int?)
{
self.sessionkey = sessionkey
self.limit = limit
self.maxchars = maxchars
}
private enum CodingKeys: String, CodingKey {
case sessionkey = "sessionKey"
case limit
case maxchars = "maxChars"
}
}

View File

@@ -1,10 +1,37 @@
import Darwin
import Foundation
import Testing
@testable import OpenClawDiscovery
private final class NameserverQueryLog: @unchecked Sendable {
private let lock = NSLock()
private var nameservers: [String] = []
func record(_ nameserver: String) {
self.lock.lock()
defer { self.lock.unlock() }
self.nameservers.append(nameserver)
}
func count(matching nameserver: String) -> Int {
self.lock.lock()
defer { self.lock.unlock() }
return self.nameservers.filter { $0 == nameserver }.count
}
}
@Suite(.serialized)
struct WideAreaGatewayDiscoveryTests {
@Test func `discovers beacon from tailnet dns sd fallback`() {
let originalWideAreaDomain = getenv("OPENCLAW_WIDE_AREA_DOMAIN").map { String(cString: $0) }
setenv("OPENCLAW_WIDE_AREA_DOMAIN", "openclaw.internal", 1)
defer {
if let originalWideAreaDomain {
setenv("OPENCLAW_WIDE_AREA_DOMAIN", originalWideAreaDomain, 1)
} else {
unsetenv("OPENCLAW_WIDE_AREA_DOMAIN")
}
}
let statusJson = """
{
"Self": { "TailscaleIPs": ["100.69.232.64"] },
@@ -20,7 +47,7 @@ struct WideAreaGatewayDiscoveryTests {
let recordType = args.last ?? ""
let nameserver = args.first(where: { $0.hasPrefix("@") }) ?? ""
if recordType == "PTR" {
if nameserver == "@100.123.224.76" {
if nameserver == "@100.100.100.100" {
return "steipetacstudio-gateway._openclaw-gw._tcp.openclaw.internal.\n"
}
return ""
@@ -47,4 +74,55 @@ struct WideAreaGatewayDiscoveryTests {
#expect(beacon.tailnetDns == "peters-mac-studio-1.sheep-coho.ts.net")
#expect(beacon.cliPath == "/Users/steipete/openclaw/src/entry.ts")
}
@Test func `attacker peer cannot become nameserver`() {
let originalWideAreaDomain = getenv("OPENCLAW_WIDE_AREA_DOMAIN").map { String(cString: $0) }
setenv("OPENCLAW_WIDE_AREA_DOMAIN", "openclaw.internal", 1)
defer {
if let originalWideAreaDomain {
setenv("OPENCLAW_WIDE_AREA_DOMAIN", originalWideAreaDomain, 1)
} else {
unsetenv("OPENCLAW_WIDE_AREA_DOMAIN")
}
}
let statusJson = """
{
"Self": { "TailscaleIPs": ["100.64.0.1"] },
"Peer": {
"attacker": { "TailscaleIPs": ["100.64.0.2"] }
}
}
"""
let queriedNameservers = NameserverQueryLog()
let context = WideAreaGatewayDiscovery.DiscoveryContext(
tailscaleStatus: { statusJson },
dig: { args, _ in
let nameserver = args.first(where: { $0.hasPrefix("@") }) ?? ""
queriedNameservers.record(nameserver)
let recordType = args.last ?? ""
if recordType == "PTR" {
if nameserver == "@100.64.0.2" {
return "evil._openclaw-gw._tcp.openclaw.internal.\n"
}
return ""
}
if recordType == "SRV" {
return "0 0 443 evil.ts.net."
}
if recordType == "TXT" {
return "\"displayName=Evil\""
}
return ""
})
let beacons = WideAreaGatewayDiscovery.discover(
timeoutSeconds: 2.0,
context: context)
#expect(queriedNameservers.count(matching: "@100.64.0.2") == 0)
#expect(queriedNameservers.count(matching: "@100.100.100.100") == 1)
#expect(beacons.isEmpty)
}
}

View File

@@ -35,6 +35,25 @@ public enum GatewayTLSStore {
_ = GenericPasswordKeychainStore.saveString(value, service: self.keychainService, account: stableID)
}
@discardableResult
public static func clearFingerprint(stableID: String) -> Bool {
let removedKeychain = GenericPasswordKeychainStore.delete(
service: self.keychainService,
account: stableID)
self.clearLegacyFingerprint(stableID: stableID)
return removedKeychain
}
@discardableResult
public static func clearAllFingerprints() -> Bool {
let removedKeychain = SecItemDelete([
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: self.keychainService,
] as CFDictionary)
self.clearAllLegacyFingerprints()
return removedKeychain == errSecSuccess || removedKeychain == errSecItemNotFound
}
// MARK: - Migration
/// On first Keychain read for a given stableID, move any legacy UserDefaults
@@ -53,6 +72,18 @@ public enum GatewayTLSStore {
}
defaults.removeObject(forKey: legacyKey)
}
private static func clearLegacyFingerprint(stableID: String) {
guard let defaults = UserDefaults(suiteName: self.legacySuiteName) else { return }
defaults.removeObject(forKey: self.legacyKeyPrefix + stableID)
}
private static func clearAllLegacyFingerprints() {
guard let defaults = UserDefaults(suiteName: self.legacySuiteName) else { return }
for key in defaults.dictionaryRepresentation().keys where key.hasPrefix(self.legacyKeyPrefix) {
defaults.removeObject(forKey: key)
}
}
}
public final class GatewayTLSPinningSession: NSObject, WebSocketSessioning, URLSessionDelegate, @unchecked Sendable {

View File

@@ -2893,6 +2893,78 @@ public struct SkillsBinsResult: Codable, Sendable {
}
}
public struct SkillsSearchParams: Codable, Sendable {
public let query: String?
public let limit: Int?
public init(
query: String?,
limit: Int?)
{
self.query = query
self.limit = limit
}
private enum CodingKeys: String, CodingKey {
case query
case limit
}
}
public struct SkillsSearchResult: Codable, Sendable {
public let results: [[String: AnyCodable]]
public init(
results: [[String: AnyCodable]])
{
self.results = results
}
private enum CodingKeys: String, CodingKey {
case results
}
}
public struct SkillsDetailParams: Codable, Sendable {
public let slug: String
public init(
slug: String)
{
self.slug = slug
}
private enum CodingKeys: String, CodingKey {
case slug
}
}
public struct SkillsDetailResult: Codable, Sendable {
public let skill: AnyCodable
public let latestversion: AnyCodable?
public let metadata: AnyCodable?
public let owner: AnyCodable?
public init(
skill: AnyCodable,
latestversion: AnyCodable?,
metadata: AnyCodable?,
owner: AnyCodable?)
{
self.skill = skill
self.latestversion = latestversion
self.metadata = metadata
self.owner = owner
}
private enum CodingKeys: String, CodingKey {
case skill
case latestversion = "latestVersion"
case metadata
case owner
}
}
public struct CronJob: Codable, Sendable {
public let id: String
public let agentid: String?
@@ -3710,18 +3782,22 @@ public struct DevicePairResolvedEvent: Codable, Sendable {
public struct ChatHistoryParams: Codable, Sendable {
public let sessionkey: String
public let limit: Int?
public let maxchars: Int?
public init(
sessionkey: String,
limit: Int?)
limit: Int?,
maxchars: Int?)
{
self.sessionkey = sessionkey
self.limit = limit
self.maxchars = maxchars
}
private enum CodingKeys: String, CodingKey {
case sessionkey = "sessionKey"
case limit
case maxchars = "maxChars"
}
}

View File

@@ -3,7 +3,9 @@
These baseline artifacts are generated from the repo-owned OpenClaw config schema and bundled channel/plugin metadata.
- Do not edit `config-baseline.json` by hand.
- Do not edit `config-baseline.jsonl` 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`.

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

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 9.8 KiB

View File

@@ -0,0 +1,16 @@
<svg width="126" height="20" viewBox="0 0 126 20" fill="black" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_5_2)">
<path d="M3.18483 17.4674C1.30005 15.782 0.357666 13.2908 0.357666 10.0003C0.357666 6.70977 1.31835 4.2186 3.24278 2.53321C5.16415 0.847812 7.79308 0.00350952 11.1265 0.00350952C12.5111 0.00350952 13.7341 0.103028 14.7985 0.308486C15.8629 0.510733 16.8815 0.854231 17.8544 1.34219V6.68088C16.3417 5.92646 14.6246 5.54765 12.7033 5.54765C11.0106 5.54765 9.76021 5.88473 8.95506 6.55889C8.14686 7.23304 7.74429 8.37911 7.74429 10.0003C7.74429 11.5669 8.14076 12.7001 8.93676 13.4C9.72971 14.103 10.9862 14.453 12.7063 14.453C14.527 14.453 16.2563 14.0067 17.8971 13.1175V18.7034C16.0763 19.5669 13.8073 19.9971 11.0899 19.9971C7.70159 19.9971 5.06961 19.1528 3.18483 17.4674Z" />
<path d="M19.538 9.99679C19.538 6.73194 20.4224 4.2504 22.1913 2.54896C23.9602 0.847512 26.6257 0 30.1909 0C33.7805 0 36.4644 0.850722 38.2485 2.54896C40.0296 4.24719 40.9201 6.73194 40.9201 9.99679C40.9201 16.6613 37.3427 19.9936 30.1909 19.9936C23.0879 19.9968 19.538 16.6645 19.538 9.99679ZM32.7497 13.3997C33.2743 12.6966 33.5365 11.5634 33.5365 10C33.5365 8.46228 33.2743 7.33547 32.7497 6.61958C32.2251 5.90369 31.3712 5.54735 30.1909 5.54735C29.0381 5.54735 28.2024 5.9069 27.6901 6.61958C27.1777 7.33547 26.9215 8.46228 26.9215 10C26.9215 11.5666 27.1777 12.6998 27.6901 13.3997C28.2024 14.1027 29.035 14.4526 30.1909 14.4526C31.3712 14.4526 32.2221 14.0995 32.7497 13.3997Z" />
<path d="M42.6029 0.404494H49.3704L49.5626 1.86196C50.3067 1.32263 51.2552 0.876404 52.408 0.526485C53.5608 0.176565 54.7533 0 55.9854 0C58.2667 0 59.9319 0.5939 60.9841 1.7817C62.0363 2.9695 62.5608 4.80257 62.5608 7.28732V19.5923H55.3328V8.05458C55.3328 7.19101 55.1467 6.57143 54.7747 6.19262C54.4026 5.8138 53.7804 5.62761 52.9082 5.62761C52.3714 5.62761 51.8194 5.75602 51.2552 6.01284C50.691 6.26966 50.2183 6.60032 49.8309 7.00482V19.5923H42.6029V0.404494Z" />
<path d="M62.5818 0.404617H70.1178L73.5794 11.6566L77.0409 0.404617H84.5769L77.3855 19.5924H69.7702L62.5818 0.404617Z" />
<path d="M86.8523 17.9422C84.6809 16.2279 83.6653 13.252 83.6653 10.0385C83.6653 6.90851 84.4735 4.33066 86.3186 2.54896C88.1637 0.767255 90.9757 0 94.5256 0C97.792 0 100.36 0.796147 102.236 2.38844C104.108 3.98074 105.047 6.15409 105.047 8.9053V12.2665H91.302C91.6436 13.2648 92.0766 13.9872 93.141 14.4334C94.2054 14.8796 95.6907 15.1011 97.5907 15.1011C98.7252 15.1011 99.8841 15.008 101.061 14.8186C101.476 14.7512 102.159 14.6453 102.519 14.565V19.2295C100.723 19.7432 98.3287 20 95.6297 20C91.9973 19.9968 89.0238 19.6565 86.8523 17.9422ZM97.4534 8.13804C97.4534 7.1878 96.4135 5.14286 94.3243 5.14286C92.4396 5.14286 91.1952 7.1557 91.1952 8.13804H97.4534Z" />
<path d="M110.723 9.8364L103.955 0.404617H111.799L125.642 19.5924H117.722L114.645 15.3003L111.567 19.5924H103.684L110.723 9.8364Z" />
<path d="M117.548 0.404617H125.356L119.363 8.8059L115.398 3.42227L117.548 0.404617Z" />
</g>
<defs>
<clipPath id="clip0_5_2">
<rect width="126" height="20" />
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 164 30" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g>
<path d="M160.352,24.069L160.352,23.62L160.64,23.62C160.797,23.62 161.011,23.632 161.011,23.824C161.011,24.032 160.901,24.069 160.715,24.069L160.352,24.069M160.352,24.384L160.544,24.384L160.991,25.168L161.481,25.168L160.987,24.352C161.242,24.333 161.452,24.212 161.452,23.868C161.452,23.441 161.157,23.303 160.659,23.303L159.938,23.303L159.938,25.168L160.352,25.168L160.352,24.384M162.45,24.238C162.45,23.143 161.599,22.508 160.65,22.508C159.695,22.508 158.845,23.143 158.845,24.238C158.845,25.333 159.695,25.971 160.65,25.971C161.598,25.971 162.45,25.333 162.45,24.238M161.93,24.238C161.93,25.036 161.343,25.572 160.65,25.572L160.65,25.566C159.937,25.572 159.361,25.036 159.361,24.238C159.361,23.441 159.938,22.907 160.65,22.907C161.344,22.907 161.93,23.441 161.93,24.238" style="fill:white;"/>
<path d="M96.374,5.707L96.376,25.367L101.928,25.367L101.928,5.707L96.374,5.707ZM52.697,5.681L52.697,25.367L58.3,25.367L58.3,10.086L62.67,10.1C64.107,10.1 65.1,10.445 65.793,11.184C66.672,12.12 67.03,13.628 67.03,16.389L67.03,25.367L72.457,25.367L72.457,14.49C72.457,6.727 67.509,5.68 62.668,5.68L52.698,5.68L52.697,5.681ZM105.314,5.708L105.314,25.367L114.32,25.367C119.118,25.367 120.684,24.569 122.377,22.78C123.575,21.524 124.348,18.766 124.348,15.753C124.348,12.99 123.693,10.525 122.551,8.99C120.494,6.245 117.531,5.708 113.106,5.708L105.314,5.708ZM110.822,9.988L113.209,9.988C116.672,9.988 118.912,11.544 118.912,15.579C118.912,19.616 116.672,21.171 113.209,21.171L110.822,21.171L110.822,9.988ZM88.369,5.708L83.735,21.288L79.295,5.709L73.302,5.708L79.642,25.367L87.645,25.367L94.036,5.708L88.369,5.708ZM126.932,25.367L132.485,25.367L132.485,5.709L126.93,5.708L126.932,25.367ZM142.496,5.715L134.743,25.36L140.218,25.36L141.445,21.888L150.62,21.888L151.781,25.36L157.725,25.36L149.913,5.714L142.496,5.715ZM146.1,9.3L149.464,18.504L142.631,18.504L146.101,9.3L146.1,9.3Z" style="fill:white;"/>
<path d="M16.889,8.985L16.889,6.28C17.151,6.26 17.417,6.247 17.687,6.238C25.087,6.006 29.942,12.597 29.942,12.597C29.942,12.597 24.698,19.879 19.076,19.879C18.333,19.882 17.594,19.764 16.889,19.529L16.889,11.325C19.769,11.673 20.349,12.945 22.081,15.833L25.933,12.585C25.933,12.585 23.121,8.897 18.381,8.897C17.866,8.897 17.373,8.933 16.889,8.985ZM16.889,0.047L16.889,4.09C17.154,4.069 17.42,4.052 17.687,4.042C27.977,3.696 34.682,12.482 34.682,12.482C34.682,12.482 26.982,21.846 18.959,21.846C18.224,21.846 17.535,21.778 16.889,21.663L16.889,24.161C17.442,24.231 18.015,24.273 18.613,24.273C26.078,24.273 31.477,20.461 36.705,15.948C37.572,16.642 41.121,18.331 41.85,19.071C36.879,23.231 25.295,26.586 18.727,26.586C18.113,26.584 17.5,26.552 16.889,26.49L16.889,30L45.264,30L45.264,0.047L16.889,0.047ZM16.889,19.529L16.889,21.662C9.984,20.432 8.067,13.254 8.067,13.254C8.067,13.254 11.383,9.58 16.889,8.985L16.889,11.325L16.878,11.324C13.988,10.977 11.731,13.677 11.731,13.677C11.731,13.677 12.996,18.221 16.889,19.529ZM4.625,12.943C4.625,12.943 8.717,6.903 16.889,6.28L16.889,4.088C7.838,4.815 0,12.48 0,12.48C0,12.48 4.439,25.313 16.889,26.488L16.889,24.16C7.753,23.011 4.625,12.943 4.625,12.943Z" style="fill:rgb(118,185,0);"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 164 30" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g>
<path d="M160.352,24.069L160.352,23.62L160.64,23.62C160.797,23.62 161.011,23.632 161.011,23.824C161.011,24.032 160.901,24.069 160.715,24.069L160.352,24.069M160.352,24.384L160.544,24.384L160.991,25.168L161.481,25.168L160.987,24.352C161.242,24.333 161.452,24.212 161.452,23.868C161.452,23.441 161.157,23.303 160.659,23.303L159.938,23.303L159.938,25.168L160.352,25.168L160.352,24.384M162.45,24.238C162.45,23.143 161.599,22.508 160.65,22.508C159.695,22.508 158.845,23.143 158.845,24.238C158.845,25.333 159.695,25.971 160.65,25.971C161.598,25.971 162.45,25.333 162.45,24.238M161.93,24.238C161.93,25.036 161.343,25.572 160.65,25.572L160.65,25.566C159.937,25.572 159.361,25.036 159.361,24.238C159.361,23.441 159.938,22.907 160.65,22.907C161.344,22.907 161.93,23.441 161.93,24.238" style="fill:black;"/>
<path d="M96.374,5.707L96.376,25.367L101.928,25.367L101.928,5.707L96.374,5.707ZM52.697,5.681L52.697,25.367L58.3,25.367L58.3,10.086L62.67,10.1C64.107,10.1 65.1,10.445 65.793,11.184C66.672,12.12 67.03,13.628 67.03,16.389L67.03,25.367L72.457,25.367L72.457,14.49C72.457,6.727 67.509,5.68 62.668,5.68L52.698,5.68L52.697,5.681ZM105.314,5.708L105.314,25.367L114.32,25.367C119.118,25.367 120.684,24.569 122.377,22.78C123.575,21.524 124.348,18.766 124.348,15.753C124.348,12.99 123.693,10.525 122.551,8.99C120.494,6.245 117.531,5.708 113.106,5.708L105.314,5.708ZM110.822,9.988L113.209,9.988C116.672,9.988 118.912,11.544 118.912,15.579C118.912,19.616 116.672,21.171 113.209,21.171L110.822,21.171L110.822,9.988ZM88.369,5.708L83.735,21.288L79.295,5.709L73.302,5.708L79.642,25.367L87.645,25.367L94.036,5.708L88.369,5.708ZM126.932,25.367L132.485,25.367L132.485,5.709L126.93,5.708L126.932,25.367ZM142.496,5.715L134.743,25.36L140.218,25.36L141.445,21.888L150.62,21.888L151.781,25.36L157.725,25.36L149.913,5.714L142.496,5.715ZM146.1,9.3L149.464,18.504L142.631,18.504L146.101,9.3L146.1,9.3Z" style="fill:black;"/>
<path d="M16.889,8.985L16.889,6.28C17.151,6.26 17.417,6.247 17.687,6.238C25.087,6.006 29.942,12.597 29.942,12.597C29.942,12.597 24.698,19.879 19.076,19.879C18.333,19.882 17.594,19.764 16.889,19.529L16.889,11.325C19.769,11.673 20.349,12.945 22.081,15.833L25.933,12.585C25.933,12.585 23.121,8.897 18.381,8.897C17.866,8.897 17.373,8.933 16.889,8.985ZM16.889,0.047L16.889,4.09C17.154,4.069 17.42,4.052 17.687,4.042C27.977,3.696 34.682,12.482 34.682,12.482C34.682,12.482 26.982,21.846 18.959,21.846C18.224,21.846 17.535,21.778 16.889,21.663L16.889,24.161C17.442,24.231 18.015,24.273 18.613,24.273C26.078,24.273 31.477,20.461 36.705,15.948C37.572,16.642 41.121,18.331 41.85,19.071C36.879,23.231 25.295,26.586 18.727,26.586C18.113,26.584 17.5,26.552 16.889,26.49L16.889,30L45.264,30L45.264,0.047L16.889,0.047ZM16.889,19.529L16.889,21.662C9.984,20.432 8.067,13.254 8.067,13.254C8.067,13.254 11.383,9.58 16.889,8.985L16.889,11.325L16.878,11.324C13.988,10.977 11.731,13.677 11.731,13.677C11.731,13.677 12.996,18.221 16.889,19.529ZM4.625,12.943C4.625,12.943 8.717,6.903 16.889,6.28L16.889,4.088C7.838,4.815 0,12.48 0,12.48C0,12.48 4.439,25.313 16.889,26.488L16.889,24.16C7.753,23.011 4.625,12.943 4.625,12.943Z" style="fill:rgb(118,185,0);"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="black">
<path d="M22.282 9.821a5.985 5.985 0 0 0-.516-4.91 6.046 6.046 0 0 0-6.51-2.9A6.065 6.065 0 0 0 4.981 4.18a5.985 5.985 0 0 0-3.998 2.9 6.046 6.046 0 0 0 .743 7.097 5.98 5.98 0 0 0 .51 4.911 6.051 6.051 0 0 0 6.515 2.9A5.985 5.985 0 0 0 13.26 24a6.056 6.056 0 0 0 5.772-4.206 5.99 5.99 0 0 0 3.997-2.9 6.056 6.056 0 0 0-.747-7.073zM13.26 22.43a4.476 4.476 0 0 1-2.876-1.04l.141-.081 4.779-2.758a.795.795 0 0 0 .392-.681v-6.737l2.02 1.168a.071.071 0 0 1 .038.052v5.583a4.504 4.504 0 0 1-4.494 4.494zM3.6 18.304a4.47 4.47 0 0 1-.535-3.014l.142.085 4.783 2.759a.771.771 0 0 0 .78 0l5.843-3.369v2.332a.08.08 0 0 1-.033.062L9.74 19.95a4.5 4.5 0 0 1-6.14-1.646zM2.34 7.896a4.485 4.485 0 0 1 2.366-1.973V11.6a.766.766 0 0 0 .388.676l5.815 3.355-2.02 1.168a.076.076 0 0 1-.071 0l-4.83-2.786A4.504 4.504 0 0 1 2.34 7.872zm16.597 3.855l-5.833-3.387L15.119 7.2a.076.076 0 0 1 .071 0l4.83 2.791a4.494 4.494 0 0 1-.676 8.105v-5.678a.79.79 0 0 0-.407-.667zm2.01-3.023l-.141-.085-4.774-2.782a.776.776 0 0 0-.785 0L9.409 9.23V6.897a.066.066 0 0 1 .028-.061l4.83-2.787a4.5 4.5 0 0 1 6.68 4.66zm-12.64 4.135l-2.02-1.164a.08.08 0 0 1-.038-.057V6.075a4.5 4.5 0 0 1 7.375-3.453l-.142.08L8.704 5.46a.795.795 0 0 0-.393.681zm1.097-2.365l2.602-1.5 2.607 1.5v2.999l-2.597 1.5-2.607-1.5z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,5 @@
<svg width="256" height="51" viewBox="0 0 256 50.875" fill="none" xmlns="http://www.w3.org/2000/svg">
<g transform="scale(0.125)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M467.444 406.809L233.722 0.335938L0 406.809H467.444ZM703.186 388.306L898.51 18.813H814.024L679.286 287.152L544.547 18.813H460.061L655.385 388.306H703.186ZM2034.31 18.813V388.307H1964.37V18.813H2034.31ZM1644.98 250.395C1644.98 221.599 1650.99 196.272 1663.01 174.415C1675.03 152.557 1691.79 135.731 1713.28 123.935C1734.77 112.139 1759.91 106.241 1788.69 106.241C1814.19 106.241 1837.14 111.792 1857.54 122.894C1877.94 133.996 1894.15 150.476 1906.17 172.333C1918.19 194.191 1924.39 220.905 1924.75 252.477V268.61H1718.75C1720.2 291.508 1726.94 309.549 1738.96 322.733C1751.35 335.57 1767.93 341.988 1788.69 341.988C1801.8 341.988 1813.83 338.519 1824.75 331.58C1835.68 324.641 1843.88 315.274 1849.34 303.478L1920.93 308.682C1912.18 334.702 1895.79 355.519 1871.75 371.131C1847.7 386.744 1820.02 394.55 1788.69 394.55C1759.91 394.55 1734.77 388.652 1713.28 376.856C1691.79 365.06 1675.03 348.233 1663.01 326.376C1650.99 304.518 1644.98 279.192 1644.98 250.395ZM1852.62 224.375C1850.07 201.823 1842.97 185.344 1831.31 174.935C1819.65 164.18 1805.45 158.802 1788.69 158.802C1769.38 158.802 1753.72 164.527 1741.7 175.976C1729.67 187.425 1722.21 203.558 1719.29 224.375H1852.62ZM1526.96 174.935C1538.62 184.303 1545.9 197.313 1548.82 213.966L1620.94 210.323C1618.39 189.16 1610.93 170.772 1598.54 155.159C1586.15 139.547 1570.13 127.578 1550.45 119.251C1531.15 110.577 1509.84 106.241 1486.52 106.241C1457.74 106.241 1432.61 112.139 1411.11 123.935C1389.62 135.731 1372.86 152.557 1360.84 174.415C1348.82 196.272 1342.81 221.599 1342.81 250.395C1342.81 279.192 1348.82 304.518 1360.84 326.376C1372.86 348.233 1389.62 365.06 1411.11 376.856C1432.61 388.652 1457.74 394.55 1486.52 394.55C1510.56 394.55 1532.42 390.213 1552.09 381.54C1571.77 372.519 1587.79 359.856 1600.18 343.549C1612.57 327.243 1620.03 308.161 1622.58 286.304L1549.91 283.181C1547.36 301.569 1540.25 315.794 1528.6 325.855C1516.94 335.57 1502.91 340.427 1486.52 340.427C1463.94 340.427 1446.45 332.621 1434.06 317.008C1421.68 301.396 1415.49 279.192 1415.49 250.395C1415.49 221.599 1421.68 199.395 1434.06 183.782C1446.45 168.17 1463.94 160.364 1486.52 160.364C1502.19 160.364 1515.66 165.221 1526.96 174.935ZM1172.15 112.473H1237.24L1239.12 165.559C1243.74 150.533 1250.16 138.864 1258.39 130.552C1270.32 118.5 1286.96 112.473 1308.29 112.473H1334.87V169.293H1307.75C1292.56 169.293 1280.09 171.359 1270.32 175.491C1260.92 179.624 1253.69 186.166 1248.63 195.12C1243.93 204.073 1241.58 215.437 1241.58 229.211V388.306H1172.15V112.473ZM871.925 174.415C859.904 196.272 853.893 221.599 853.893 250.395C853.893 279.192 859.904 304.518 871.925 326.376C883.947 348.233 900.704 365.06 922.198 376.856C943.691 388.652 968.827 394.55 997.606 394.55C1028.93 394.55 1056.62 386.744 1080.66 371.131C1104.71 355.519 1121.1 334.702 1129.84 308.682L1058.26 303.478C1052.8 315.274 1044.6 324.641 1033.67 331.58C1022.74 338.519 1010.72 341.988 997.606 341.988C976.841 341.988 960.266 335.57 947.88 322.733C935.858 309.549 929.119 291.508 927.662 268.61H1133.67V252.477C1133.3 220.905 1127.11 194.191 1115.09 172.333C1103.07 150.476 1086.86 133.996 1066.46 122.894C1046.06 111.792 1023.11 106.241 997.606 106.241C968.827 106.241 943.691 112.139 922.198 123.935C900.704 135.731 883.947 152.557 871.925 174.415ZM1040.23 174.935C1051.88 185.344 1058.99 201.823 1061.54 224.375H928.208C931.123 203.558 938.591 187.425 950.612 175.976C962.634 164.527 978.298 158.802 997.606 158.802C1014.36 158.802 1028.57 164.18 1040.23 174.935Z" fill="black"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@@ -44,6 +44,13 @@ Token credentials (`type: "token"`) support inline `token` and/or `tokenRef`.
2. For eligible profiles, token material may be resolved from inline value or `tokenRef`.
3. Unresolvable refs produce `unresolved_ref` in `models status --probe` output.
## OAuth SecretRef Policy Guard
- SecretRef input is for static credentials only.
- If a profile credential is `type: "oauth"`, SecretRef objects are not supported for that profile credential material.
- If `auth.profiles.<id>.mode` is `"oauth"`, SecretRef-backed `keyRef`/`tokenRef` input for that profile is rejected.
- Violations are hard failures in startup/reload auth resolution paths.
## Legacy-Compatible Messaging
For script compatibility, probe errors keep this first line unchanged:

View File

@@ -1,44 +1,8 @@
---
summary: "Monitor OAuth expiry for model providers"
read_when:
- Setting up auth expiry monitoring or alerts
- Automating Claude Code / Codex OAuth refresh checks
summary: "Redirect to /gateway/authentication"
title: "Auth Monitoring"
---
# Auth monitoring
# Auth Monitoring
OpenClaw exposes OAuth expiry health via `openclaw models status`. Use that for
automation and alerting; scripts are optional extras for phone workflows.
## Preferred: CLI check (portable)
```bash
openclaw models status --check
```
Exit codes:
- `0`: OK
- `1`: expired or missing credentials
- `2`: expiring soon (within 24h)
This works in cron/systemd and requires no extra scripts.
## Optional scripts (ops / phone workflows)
These live under `scripts/` and are **optional**. They assume SSH access to the
gateway host and are tuned for systemd + Termux.
- `scripts/claude-auth-status.sh` now uses `openclaw models status --json` as the
source of truth (falling back to direct file reads if the CLI is unavailable),
so keep `openclaw` on `PATH` for timers.
- `scripts/auth-monitor.sh`: cron/systemd timer target; sends alerts (ntfy or phone).
- `scripts/systemd/openclaw-auth-monitor.{service,timer}`: systemd user timer.
- `scripts/claude-auth-status.sh`: Claude Code + OpenClaw auth checker (full/json/simple).
- `scripts/mobile-reauth.sh`: guided reauth flow over SSH.
- `scripts/termux-quick-auth.sh`: onetap widget status + open auth URL.
- `scripts/termux-auth-widget.sh`: full guided widget flow.
- `scripts/termux-sync-widget.sh`: sync Claude Code creds → OpenClaw.
If you dont need phone automation or systemd timers, skip these scripts.
This page moved to [Authentication](/gateway/authentication). See [Authentication](/gateway/authentication) for auth monitoring documentation.

View File

@@ -0,0 +1,8 @@
---
summary: "Redirect to Task Flow"
title: "ClawFlow"
---
# ClawFlow
ClawFlow was renamed to [Task Flow](/automation/taskflow). See [Task Flow](/automation/taskflow) for the current documentation.

View File

@@ -1,48 +1,20 @@
---
summary: "Cron jobs + wakeups for the Gateway scheduler"
summary: "Scheduled jobs, webhooks, and Gmail PubSub triggers for the Gateway scheduler"
read_when:
- Scheduling background jobs or wakeups
- Wiring automation that should run with or alongside heartbeats
- Wiring external triggers (webhooks, Gmail) into OpenClaw
- Deciding between heartbeat and cron for scheduled tasks
title: "Cron Jobs"
title: "Scheduled Tasks"
---
# Cron jobs (Gateway scheduler)
# Scheduled Tasks (Cron)
> **Cron vs Heartbeat?** See [Cron vs Heartbeat](/automation/cron-vs-heartbeat) for guidance on when to use each.
Cron is the Gateway's built-in scheduler. It persists jobs, wakes the agent at the right time, and can deliver output back to a chat channel or webhook endpoint.
Cron is the Gateways built-in scheduler. It persists jobs, wakes the agent at
the right time, and can optionally deliver output back to a chat.
All cron executions create [background task](/automation/tasks) records. The key difference is visibility:
- `sessionTarget: "main"` creates a task with `silent` notify policy — it schedules a system event for the main session and heartbeat flow but does not generate notifications.
- `sessionTarget: "isolated"` or `sessionTarget: "session:..."` creates a visible task that shows up in `openclaw tasks` with delivery notifications.
If you want _“run this every morning”_ or _“poke the agent in 20 minutes”_,
cron is the mechanism.
Troubleshooting: [/automation/troubleshooting](/automation/troubleshooting)
## TL;DR
- Cron runs **inside the Gateway** (not inside the model).
- Jobs persist under `~/.openclaw/cron/` so restarts dont lose schedules.
- Two execution styles:
- **Main session**: enqueue a system event, then run on the next heartbeat.
- **Isolated**: run a dedicated agent turn in `cron:<jobId>` or a custom session, with delivery (announce by default or none).
- **Current session**: bind to the session where the cron is created (`sessionTarget: "current"`).
- **Custom session**: run in a persistent named session (`sessionTarget: "session:custom-id"`).
- Wakeups are first-class: a job can request “wake now” vs “next heartbeat”.
- Webhook posting is per job via `delivery.mode = "webhook"` + `delivery.to = "<url>"`.
- Legacy fallback remains for stored jobs with `notify: true` when `cron.webhook` is set, migrate those jobs to webhook delivery mode.
- For upgrades, `openclaw doctor --fix` can normalize legacy cron store fields before the scheduler touches them.
## Quick start (actionable)
Create a one-shot reminder, verify it exists, and run it immediately:
## Quick start
```bash
# Add a one-shot reminder
openclaw cron add \
--name "Reminder" \
--at "2026-02-01T16:00:00Z" \
@@ -51,12 +23,74 @@ openclaw cron add \
--wake now \
--delete-after-run
# Check your jobs
openclaw cron list
openclaw cron run <job-id>
# See run history
openclaw cron runs --id <job-id>
```
Schedule a recurring isolated job with delivery:
## How cron works
- Cron runs **inside the Gateway** process (not inside the model).
- Jobs persist at `~/.openclaw/cron/jobs.json` so restarts do not lose schedules.
- All cron executions create [background task](/automation/tasks) records.
- One-shot jobs (`--at`) auto-delete after success by default.
## Schedule types
| Kind | CLI flag | Description |
| ------- | --------- | ------------------------------------------------------- |
| `at` | `--at` | One-shot timestamp (ISO 8601 or relative like `20m`) |
| `every` | `--every` | Fixed interval |
| `cron` | `--cron` | 5-field or 6-field cron expression with optional `--tz` |
Timestamps without a timezone are treated as UTC. Add `--tz America/New_York` for local wall-clock scheduling.
Recurring top-of-hour expressions are automatically staggered by up to 5 minutes to reduce load spikes. Use `--exact` to force precise timing or `--stagger 30s` for an explicit window.
## Execution styles
| Style | `--session` value | Runs in | Best for |
| --------------- | ------------------- | ------------------------ | ------------------------------- |
| Main session | `main` | Next heartbeat turn | Reminders, system events |
| Isolated | `isolated` | Dedicated `cron:<jobId>` | Reports, background chores |
| Current session | `current` | Bound at creation time | Context-aware recurring work |
| Custom session | `session:custom-id` | Persistent named session | Workflows that build on history |
**Main session** jobs enqueue a system event and optionally wake the heartbeat (`--wake now` or `--wake next-heartbeat`). **Isolated** jobs run a dedicated agent turn with a fresh session. **Custom sessions** (`session:xxx`) persist context across runs, enabling workflows like daily standups that build on previous summaries.
### Payload options for isolated jobs
- `--message`: prompt text (required for isolated)
- `--model` / `--thinking`: model and thinking level overrides
- `--light-context`: skip workspace bootstrap file injection
- `--tools exec,read`: restrict which tools the job can use
## Delivery and output
| Mode | What happens |
| ---------- | -------------------------------------------------------- |
| `announce` | Deliver summary to target channel (default for isolated) |
| `webhook` | POST finished event payload to a URL |
| `none` | Internal only, no delivery |
Use `--announce --channel telegram --to "-1001234567890"` for channel delivery. For Telegram forum topics, use `-1001234567890:topic:123`. Slack/Discord/Mattermost targets should use explicit prefixes (`channel:<id>`, `user:<id>`).
## CLI examples
One-shot reminder (main session):
```bash
openclaw cron add \
--name "Calendar check" \
--at "20m" \
--session main \
--system-event "Next heartbeat: check calendar." \
--wake now
```
Recurring isolated job with delivery:
```bash
openclaw cron add \
@@ -70,574 +104,6 @@ openclaw cron add \
--to "channel:C1234567890"
```
## Tool-call equivalents (Gateway cron tool)
For the canonical JSON shapes and examples, see [JSON schema for tool calls](/automation/cron-jobs#json-schema-for-tool-calls).
## Where cron jobs are stored
Cron jobs are persisted on the Gateway host at `~/.openclaw/cron/jobs.json` by default.
The Gateway loads the file into memory and writes it back on changes, so manual edits
are only safe when the Gateway is stopped. Prefer `openclaw cron add/edit` or the cron
tool call API for changes.
## Beginner-friendly overview
Think of a cron job as: **when** to run + **what** to do.
1. **Choose a schedule**
- One-shot reminder → `schedule.kind = "at"` (CLI: `--at`)
- Repeating job → `schedule.kind = "every"` or `schedule.kind = "cron"`
- If your ISO timestamp omits a timezone, it is treated as **UTC**.
2. **Choose where it runs**
- `sessionTarget: "main"` → run during the next heartbeat with main context.
- `sessionTarget: "isolated"` → run a dedicated agent turn in `cron:<jobId>`.
- `sessionTarget: "current"` → bind to the current session (resolved at creation time to `session:<sessionKey>`).
- `sessionTarget: "session:custom-id"` → run in a persistent named session that maintains context across runs.
Default behavior (unchanged):
- `systemEvent` payloads default to `main`
- `agentTurn` payloads default to `isolated`
To use current session binding, explicitly set `sessionTarget: "current"`.
3. **Choose the payload**
- Main session → `payload.kind = "systemEvent"`
- Isolated session → `payload.kind = "agentTurn"`
Optional: one-shot jobs (`schedule.kind = "at"`) delete after success by default. Set
`deleteAfterRun: false` to keep them (they will disable after success).
## Concepts
### Jobs
A cron job is a stored record with:
- a **schedule** (when it should run),
- a **payload** (what it should do),
- optional **delivery mode** (`announce`, `webhook`, or `none`).
- optional **agent binding** (`agentId`): run the job under a specific agent; if
missing or unknown, the gateway falls back to the default agent.
Jobs are identified by a stable `jobId` (used by CLI/Gateway APIs).
In agent tool calls, `jobId` is canonical; legacy `id` is accepted for compatibility.
One-shot jobs auto-delete after success by default; set `deleteAfterRun: false` to keep them.
### Schedules
Cron supports three schedule kinds:
- `at`: one-shot timestamp via `schedule.at` (ISO 8601).
- `every`: fixed interval (ms).
- `cron`: 5-field cron expression (or 6-field with seconds) with optional IANA timezone.
Cron expressions use `croner`. If a timezone is omitted, the Gateway hosts
local timezone is used.
To reduce top-of-hour load spikes across many gateways, OpenClaw applies a
deterministic per-job stagger window of up to 5 minutes for recurring
top-of-hour expressions (for example `0 * * * *`, `0 */2 * * *`). Fixed-hour
expressions such as `0 7 * * *` remain exact.
For any cron schedule, you can set an explicit stagger window with `schedule.staggerMs`
(`0` keeps exact timing). CLI shortcuts:
- `--stagger 30s` (or `1m`, `5m`) to set an explicit stagger window.
- `--exact` to force `staggerMs = 0`.
### Main vs isolated execution
#### Main session jobs (system events)
Main jobs enqueue a system event and optionally wake the heartbeat runner.
They must use `payload.kind = "systemEvent"`.
- `wakeMode: "now"` (default): event triggers an immediate heartbeat run.
- `wakeMode: "next-heartbeat"`: event waits for the next scheduled heartbeat.
This is the best fit when you want the normal heartbeat prompt + main-session context.
See [Heartbeat](/gateway/heartbeat).
Main-session cron jobs create [background task](/automation/tasks) records with `silent` notify policy (no notifications by default). They appear in `openclaw tasks list` but do not generate delivery messages.
#### Isolated jobs (dedicated cron sessions)
Isolated jobs run a dedicated agent turn in session `cron:<jobId>` or a custom session.
Key behaviors:
- Prompt is prefixed with `[cron:<jobId> <job name>]` for traceability.
- Each run starts a **fresh session id** (no prior conversation carry-over), unless using a custom session.
- Custom sessions (`session:xxx`) persist context across runs, enabling workflows like daily standups that build on previous summaries.
- Default behavior: if `delivery` is omitted, isolated jobs announce a summary (`delivery.mode = "announce"`).
- `delivery.mode` chooses what happens:
- `announce`: deliver a summary to the target channel and post a brief summary to the main session.
- `webhook`: POST the finished event payload to `delivery.to` when the finished event includes a summary.
- `none`: internal only (no delivery, no main-session summary).
- `wakeMode` controls when the main-session summary posts:
- `now`: immediate heartbeat.
- `next-heartbeat`: waits for the next scheduled heartbeat.
Use isolated jobs for noisy, frequent, or "background chores" that shouldn't spam
your main chat history.
These detached runs create [background task](/automation/tasks) records visible in `openclaw tasks` and subject to task audit and maintenance.
### Payload shapes (what runs)
Two payload kinds are supported:
- `systemEvent`: main-session only, routed through the heartbeat prompt.
- `agentTurn`: isolated-session only, runs a dedicated agent turn.
Common `agentTurn` fields:
- `message`: required text prompt.
- `model` / `thinking`: optional overrides (see below).
- `timeoutSeconds`: optional timeout override.
- `lightContext`: optional lightweight bootstrap mode for jobs that do not need workspace bootstrap file injection.
Delivery config:
- `delivery.mode`: `none` | `announce` | `webhook`.
- `delivery.channel`: `last` or a specific channel.
- `delivery.to`: channel-specific target (announce) or webhook URL (webhook mode).
- `delivery.bestEffort`: avoid failing the job if announce delivery fails.
Announce delivery suppresses messaging tool sends for the run; use `delivery.channel`/`delivery.to`
to target the chat instead. When `delivery.mode = "none"`, no summary is posted to the main session.
If `delivery` is omitted for isolated jobs, OpenClaw defaults to `announce`.
#### Announce delivery flow
When `delivery.mode = "announce"`, cron delivers directly via the outbound channel adapters.
The main agent is not spun up to craft or forward the message.
Behavior details:
- Content: delivery uses the isolated run's outbound payloads (text/media) with normal chunking and
channel formatting.
- Heartbeat-only responses (`HEARTBEAT_OK` with no real content) are not delivered.
- If the isolated run already sent a message to the same target via the message tool, delivery is
skipped to avoid duplicates.
- Missing or invalid delivery targets fail the job unless `delivery.bestEffort = true`.
- A short summary is posted to the main session only when `delivery.mode = "announce"`.
- The main-session summary respects `wakeMode`: `now` triggers an immediate heartbeat and
`next-heartbeat` waits for the next scheduled heartbeat.
#### Webhook delivery flow
When `delivery.mode = "webhook"`, cron posts the finished event payload to `delivery.to` when the finished event includes a summary.
Behavior details:
- The endpoint must be a valid HTTP(S) URL.
- No channel delivery is attempted in webhook mode.
- No main-session summary is posted in webhook mode.
- If `cron.webhookToken` is set, auth header is `Authorization: Bearer <cron.webhookToken>`.
- Deprecated fallback: stored legacy jobs with `notify: true` still post to `cron.webhook` (if configured), with a warning so you can migrate to `delivery.mode = "webhook"`.
### Model and thinking overrides
Isolated jobs (`agentTurn`) can override the model and thinking level:
- `model`: Provider/model string (e.g., `anthropic/claude-sonnet-4-20250514`) or alias (e.g., `opus`)
- `thinking`: Thinking level (`off`, `minimal`, `low`, `medium`, `high`, `xhigh`; GPT-5.2 + Codex models only)
Note: You can set `model` on main-session jobs too, but it changes the shared main
session model. We recommend model overrides only for isolated jobs to avoid
unexpected context shifts.
Resolution priority:
1. Job payload override (highest)
2. Hook-specific defaults (e.g., `hooks.gmail.model`)
3. Agent config default
### Lightweight bootstrap context
Isolated jobs (`agentTurn`) can set `lightContext: true` to run with lightweight bootstrap context.
- Use this for scheduled chores that do not need workspace bootstrap file injection.
- In practice, the embedded runtime runs with `bootstrapContextMode: "lightweight"`, which keeps cron bootstrap context empty on purpose.
- CLI equivalents: `openclaw cron add --light-context ...` and `openclaw cron edit --light-context`.
### Delivery (channel + target)
Isolated jobs can deliver output to a channel via the top-level `delivery` config:
- `delivery.mode`: `announce` (channel delivery), `webhook` (HTTP POST), or `none`.
- `delivery.channel`: `whatsapp` / `telegram` / `discord` / `slack` / `signal` / `imessage` / `irc` / `googlechat` / `line` / `last`, plus extension channels like `msteams` / `mattermost` (plugins).
- `delivery.to`: channel-specific recipient target.
`announce` delivery is only valid for isolated jobs (`sessionTarget: "isolated"`).
`webhook` delivery is valid for both main and isolated jobs.
If `delivery.channel` or `delivery.to` is omitted, cron can fall back to the main sessions
“last route” (the last place the agent replied).
Target format reminders:
- Slack/Discord/Mattermost (plugin) targets should use explicit prefixes (e.g. `channel:<id>`, `user:<id>`) to avoid ambiguity.
Mattermost bare 26-char IDs are resolved **user-first** (DM if user exists, channel otherwise) — use `user:<id>` or `channel:<id>` for deterministic routing.
- Telegram topics should use the `:topic:` form (see below).
#### Telegram delivery targets (topics / forum threads)
Telegram supports forum topics via `message_thread_id`. For cron delivery, you can encode
the topic/thread into the `to` field:
- `-1001234567890` (chat id only)
- `-1001234567890:topic:123` (preferred: explicit topic marker)
- `-1001234567890:123` (shorthand: numeric suffix)
Prefixed targets like `telegram:...` / `telegram:group:...` are also accepted:
- `telegram:group:-1001234567890:topic:123`
## JSON schema for tool calls
Use these shapes when calling Gateway `cron.*` tools directly (agent tool calls or RPC).
CLI flags accept human durations like `20m`, but tool calls should use an ISO 8601 string
for `schedule.at` and milliseconds for `schedule.everyMs`.
### cron.add params
One-shot, main session job (system event):
```json
{
"name": "Reminder",
"schedule": { "kind": "at", "at": "2026-02-01T16:00:00Z" },
"sessionTarget": "main",
"wakeMode": "now",
"payload": { "kind": "systemEvent", "text": "Reminder text" },
"deleteAfterRun": true
}
```
Recurring, isolated job with delivery:
```json
{
"name": "Morning brief",
"schedule": { "kind": "cron", "expr": "0 7 * * *", "tz": "America/Los_Angeles" },
"sessionTarget": "isolated",
"wakeMode": "next-heartbeat",
"payload": {
"kind": "agentTurn",
"message": "Summarize overnight updates.",
"lightContext": true
},
"delivery": {
"mode": "announce",
"channel": "slack",
"to": "channel:C1234567890",
"bestEffort": true
}
}
```
Recurring job bound to current session (auto-resolved at creation):
```json
{
"name": "Daily standup",
"schedule": { "kind": "cron", "expr": "0 9 * * *" },
"sessionTarget": "current",
"payload": {
"kind": "agentTurn",
"message": "Summarize yesterday's progress."
}
}
```
Recurring job in a custom persistent session:
```json
{
"name": "Project monitor",
"schedule": { "kind": "every", "everyMs": 300000 },
"sessionTarget": "session:project-alpha-monitor",
"payload": {
"kind": "agentTurn",
"message": "Check project status and update the running log."
}
}
```
Notes:
- `schedule.kind`: `at` (`at`), `every` (`everyMs`), or `cron` (`expr`, optional `tz`).
- `schedule.at` accepts ISO 8601. Tool/API values without a timezone are treated as UTC; the CLI also accepts `openclaw cron add|edit --at "<offset-less-iso>" --tz <iana>` for local wall-clock one-shots.
- `everyMs` is milliseconds.
- `sessionTarget`: `"main"`, `"isolated"`, `"current"`, or `"session:<custom-id>"`.
- `"current"` is resolved to `"session:<sessionKey>"` at creation time.
- Custom sessions (`session:xxx`) maintain persistent context across runs.
- Optional fields: `agentId`, `description`, `enabled`, `deleteAfterRun` (defaults to true for `at`),
`delivery`.
- `wakeMode` defaults to `"now"` when omitted.
### cron.update params
```json
{
"jobId": "job-123",
"patch": {
"enabled": false,
"schedule": { "kind": "every", "everyMs": 3600000 }
}
}
```
Notes:
- `jobId` is canonical; `id` is accepted for compatibility.
- Use `agentId: null` in the patch to clear an agent binding.
### cron.run and cron.remove params
```json
{ "jobId": "job-123", "mode": "force" }
```
```json
{ "jobId": "job-123" }
```
## Storage & history
- Job store: `~/.openclaw/cron/jobs.json` (Gateway-managed JSON).
- Run history: `~/.openclaw/cron/runs/<jobId>.jsonl` (JSONL, auto-pruned by size and line count).
- Isolated cron run sessions in `sessions.json` are pruned by `cron.sessionRetention` (default `24h`; set `false` to disable).
- Override store path: `cron.store` in config.
## Retry policy
When a job fails, OpenClaw classifies errors as **transient** (retryable) or **permanent** (disable immediately).
### Transient errors (retried)
- Rate limit (429, too many requests, resource exhausted)
- Provider overload (for example Anthropic `529 overloaded_error`, overload fallback summaries)
- Network errors (timeout, ECONNRESET, fetch failed, socket)
- Server errors (5xx)
- Cloudflare-related errors
### Permanent errors (no retry)
- Auth failures (invalid API key, unauthorized)
- Config or validation errors
- Other non-transient errors
### Default behavior (no config)
**One-shot jobs (`schedule.kind: "at"`):**
- On transient error: retry up to 3 times with exponential backoff (30s → 1m → 5m).
- On permanent error: disable immediately.
- On success or skip: disable (or delete if `deleteAfterRun: true`).
**Recurring jobs (`cron` / `every`):**
- On any error: apply exponential backoff (30s → 1m → 5m → 15m → 60m) before the next scheduled run.
- Job stays enabled; backoff resets after the next successful run.
Configure `cron.retry` to override these defaults (see [Configuration](/automation/cron-jobs#configuration)).
## Configuration
```json5
{
cron: {
enabled: true, // default true
store: "~/.openclaw/cron/jobs.json",
maxConcurrentRuns: 1, // default 1
// Optional: override retry policy for one-shot jobs
retry: {
maxAttempts: 3,
backoffMs: [60000, 120000, 300000],
retryOn: ["rate_limit", "overloaded", "network", "server_error"],
},
webhook: "https://example.invalid/legacy", // deprecated fallback for stored notify:true jobs
webhookToken: "replace-with-dedicated-webhook-token", // optional bearer token for webhook mode
sessionRetention: "24h", // duration string or false
runLog: {
maxBytes: "2mb", // default 2_000_000 bytes
keepLines: 2000, // default 2000
},
},
}
```
Run-log pruning behavior:
- `cron.runLog.maxBytes`: max run-log file size before pruning.
- `cron.runLog.keepLines`: when pruning, keep only the newest N lines.
- Both apply to `cron/runs/<jobId>.jsonl` files.
Webhook behavior:
- Preferred: set `delivery.mode: "webhook"` with `delivery.to: "https://..."` per job.
- Webhook URLs must be valid `http://` or `https://` URLs.
- When posted, payload is the cron finished event JSON.
- If `cron.webhookToken` is set, auth header is `Authorization: Bearer <cron.webhookToken>`.
- If `cron.webhookToken` is not set, no `Authorization` header is sent.
- Deprecated fallback: stored legacy jobs with `notify: true` still use `cron.webhook` when present.
Disable cron entirely:
- `cron.enabled: false` (config)
- `OPENCLAW_SKIP_CRON=1` (env)
## Maintenance
Cron has two built-in maintenance paths: isolated run-session retention and run-log pruning.
### Defaults
- `cron.sessionRetention`: `24h` (set `false` to disable run-session pruning)
- `cron.runLog.maxBytes`: `2_000_000` bytes
- `cron.runLog.keepLines`: `2000`
### How it works
- Isolated runs create session entries (`...:cron:<jobId>:run:<uuid>`) and transcript files.
- The reaper removes expired run-session entries older than `cron.sessionRetention`.
- For removed run sessions no longer referenced by the session store, OpenClaw archives transcript files and purges old deleted archives on the same retention window.
- After each run append, `cron/runs/<jobId>.jsonl` is size-checked:
- if file size exceeds `runLog.maxBytes`, it is trimmed to the newest `runLog.keepLines` lines.
### Performance caveat for high volume schedulers
High-frequency cron setups can generate large run-session and run-log footprints. Maintenance is built in, but loose limits can still create avoidable IO and cleanup work.
What to watch:
- long `cron.sessionRetention` windows with many isolated runs
- high `cron.runLog.keepLines` combined with large `runLog.maxBytes`
- many noisy recurring jobs writing to the same `cron/runs/<jobId>.jsonl`
What to do:
- keep `cron.sessionRetention` as short as your debugging/audit needs allow
- keep run logs bounded with moderate `runLog.maxBytes` and `runLog.keepLines`
- move noisy background jobs to isolated mode with delivery rules that avoid unnecessary chatter
- review growth periodically with `openclaw cron runs` and adjust retention before logs become large
### Customize examples
Keep run sessions for a week and allow bigger run logs:
```json5
{
cron: {
sessionRetention: "7d",
runLog: {
maxBytes: "10mb",
keepLines: 5000,
},
},
}
```
Disable isolated run-session pruning but keep run-log pruning:
```json5
{
cron: {
sessionRetention: false,
runLog: {
maxBytes: "5mb",
keepLines: 3000,
},
},
}
```
Tune for high-volume cron usage (example):
```json5
{
cron: {
sessionRetention: "12h",
runLog: {
maxBytes: "3mb",
keepLines: 1500,
},
},
}
```
## CLI quickstart
One-shot reminder (UTC ISO, auto-delete after success):
```bash
openclaw cron add \
--name "Send reminder" \
--at "2026-01-12T18:00:00Z" \
--session main \
--system-event "Reminder: submit expense report." \
--wake now \
--delete-after-run
```
One-shot reminder (main session, wake immediately):
```bash
openclaw cron add \
--name "Calendar check" \
--at "20m" \
--session main \
--system-event "Next heartbeat: check calendar." \
--wake now
```
Recurring isolated job (announce to WhatsApp):
```bash
openclaw cron add \
--name "Morning status" \
--cron "0 7 * * *" \
--tz "America/Los_Angeles" \
--session isolated \
--message "Summarize inbox + calendar for today." \
--announce \
--channel whatsapp \
--to "+15551234567"
```
Recurring cron job with explicit 30-second stagger:
```bash
openclaw cron add \
--name "Minute watcher" \
--cron "0 * * * * *" \
--tz "UTC" \
--stagger 30s \
--session isolated \
--message "Run minute watcher checks." \
--announce
```
Recurring isolated job (deliver to a Telegram topic):
```bash
openclaw cron add \
--name "Nightly summary (topic)" \
--cron "0 22 * * *" \
--tz "America/Los_Angeles" \
--session isolated \
--message "Summarize today; send to the nightly topic." \
--announce \
--channel telegram \
--to "-1001234567890:topic:123"
```
Isolated job with model and thinking override:
```bash
@@ -649,96 +115,221 @@ openclaw cron add \
--message "Weekly deep analysis of project progress." \
--model "opus" \
--thinking high \
--announce \
--channel whatsapp \
--to "+15551234567"
--announce
```
Agent selection (multi-agent setups):
## Webhooks
Gateway can expose HTTP webhook endpoints for external triggers. Enable in config:
```json5
{
hooks: {
enabled: true,
token: "shared-secret",
path: "/hooks",
},
}
```
### Authentication
Every request must include the hook token via header:
- `Authorization: Bearer <token>` (recommended)
- `x-openclaw-token: <token>`
Query-string tokens are rejected.
### POST /hooks/wake
Enqueue a system event for the main session:
```bash
# Pin a job to agent "ops" (falls back to default if that agent is missing)
openclaw cron add --name "Ops sweep" --cron "0 6 * * *" --session isolated --message "Check ops queue" --agent ops
curl -X POST http://127.0.0.1:18789/hooks/wake \
-H 'Authorization: Bearer SECRET' \
-H 'Content-Type: application/json' \
-d '{"text":"New email received","mode":"now"}'
```
# Switch or clear the agent on an existing job
openclaw cron edit <jobId> --agent ops
- `text` (required): event description
- `mode` (optional): `now` (default) or `next-heartbeat`
### POST /hooks/agent
Run an isolated agent turn:
```bash
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"}'
```
Fields: `message` (required), `name`, `agentId`, `wakeMode`, `deliver`, `channel`, `to`, `model`, `thinking`, `timeoutSeconds`.
### Mapped hooks (POST /hooks/\<name\>)
Custom hook names are resolved via `hooks.mappings` in config. Mappings can transform arbitrary payloads into `wake` or `agent` actions with templates or code transforms.
### Security
- Keep hook endpoints behind loopback, tailnet, or trusted reverse proxy.
- Use a dedicated hook token; do not reuse gateway auth tokens.
- Set `hooks.allowedAgentIds` to limit explicit `agentId` routing.
- Keep `hooks.allowRequestSessionKey=false` unless you require caller-selected sessions.
- Hook payloads are wrapped with safety boundaries by default.
## Gmail PubSub integration
Wire Gmail inbox triggers to OpenClaw via Google PubSub.
**Prerequisites**: `gcloud` CLI, `gog` (gogcli), OpenClaw hooks enabled, Tailscale for the public HTTPS endpoint.
### Wizard setup (recommended)
```bash
openclaw webhooks gmail setup --account openclaw@gmail.com
```
This writes `hooks.gmail` config, enables the Gmail preset, and uses Tailscale Funnel for the push endpoint.
### Gateway auto-start
When `hooks.enabled=true` and `hooks.gmail.account` is set, the Gateway starts `gog gmail watch serve` on boot and auto-renews the watch. Set `OPENCLAW_SKIP_GMAIL_WATCHER=1` to opt out.
### Manual one-time setup
1. Select the GCP project that owns the OAuth client used by `gog`:
```bash
gcloud auth login
gcloud config set project <project-id>
gcloud services enable gmail.googleapis.com pubsub.googleapis.com
```
2. Create topic and grant Gmail push access:
```bash
gcloud pubsub topics create gog-gmail-watch
gcloud pubsub topics add-iam-policy-binding gog-gmail-watch \
--member=serviceAccount:gmail-api-push@system.gserviceaccount.com \
--role=roles/pubsub.publisher
```
3. Start the watch:
```bash
gog gmail watch start \
--account openclaw@gmail.com \
--label INBOX \
--topic projects/<project-id>/topics/gog-gmail-watch
```
### Gmail model override
```json5
{
hooks: {
gmail: {
model: "openrouter/meta-llama/llama-3.3-70b-instruct:free",
thinking: "off",
},
},
}
```
## Managing jobs
```bash
# List all jobs
openclaw cron list
# Edit a job
openclaw cron edit <jobId> --message "Updated prompt" --model "opus"
# Force run a job now
openclaw cron run <jobId>
# Run only if due
openclaw cron run <jobId> --due
# View run history
openclaw cron runs --id <jobId> --limit 50
# Delete a job
openclaw cron remove <jobId>
# Agent selection (multi-agent setups)
openclaw cron add --name "Ops sweep" --cron "0 6 * * *" --session isolated --message "Check ops queue" --agent ops
openclaw cron edit <jobId> --clear-agent
```
Manual run (force is the default, use `--due` to only run when due):
## Configuration
```bash
openclaw cron run <jobId>
openclaw cron run <jobId> --due
```json5
{
cron: {
enabled: true,
store: "~/.openclaw/cron/jobs.json",
maxConcurrentRuns: 1,
retry: {
maxAttempts: 3,
backoffMs: [60000, 120000, 300000],
retryOn: ["rate_limit", "overloaded", "network", "server_error"],
},
webhookToken: "replace-with-dedicated-webhook-token",
sessionRetention: "24h",
runLog: { maxBytes: "2mb", keepLines: 2000 },
},
}
```
`cron.run` now acknowledges once the manual run is queued, not after the job finishes. Successful queue responses look like `{ ok: true, enqueued: true, runId }`. If the job is already running or `--due` finds nothing due, the response stays `{ ok: true, ran: false, reason }`. Use `openclaw cron runs --id <jobId>` or the `cron.runs` gateway method to inspect the eventual finished entry.
Disable cron: `cron.enabled: false` or `OPENCLAW_SKIP_CRON=1`.
Edit an existing job (patch fields):
**One-shot retry**: transient errors (rate limit, overload, network, server error) retry up to 3 times with exponential backoff. Permanent errors disable immediately.
```bash
openclaw cron edit <jobId> \
--message "Updated prompt" \
--model "opus" \
--thinking low
```
**Recurring retry**: exponential backoff (30s to 60m) between retries. Backoff resets after the next successful run.
Force an existing cron job to run exactly on schedule (no stagger):
```bash
openclaw cron edit <jobId> --exact
```
Run history:
```bash
openclaw cron runs --id <jobId> --limit 50
```
Immediate system event without creating a job:
```bash
openclaw system event --mode now --text "Next heartbeat: check battery."
```
## Gateway API surface
- `cron.list`, `cron.status`, `cron.add`, `cron.update`, `cron.remove`
- `cron.run` (force or due), `cron.runs`
For immediate system events without a job, use [`openclaw system event`](/cli/system).
**Maintenance**: `cron.sessionRetention` (default `24h`) prunes isolated run-session entries. `cron.runLog.maxBytes` / `cron.runLog.keepLines` auto-prune run-log files.
## Troubleshooting
### "Nothing runs"
### Command ladder
- Check cron is enabled: `cron.enabled` and `OPENCLAW_SKIP_CRON`.
- Check the Gateway is running continuously (cron runs inside the Gateway process).
- For `cron` schedules: confirm timezone (`--tz`) vs the host timezone.
```bash
openclaw status
openclaw gateway status
openclaw cron status
openclaw cron list
openclaw cron runs --id <jobId> --limit 20
openclaw system heartbeat last
openclaw logs --follow
openclaw doctor
```
### A recurring job keeps delaying after failures
### Cron not firing
- OpenClaw applies exponential retry backoff for recurring jobs after consecutive errors:
30s, 1m, 5m, 15m, then 60m between retries.
- Backoff resets automatically after the next successful run.
- One-shot (`at`) jobs retry transient errors (rate limit, overloaded, network, server_error) up to 3 times with backoff; permanent errors disable immediately. See [Retry policy](/automation/cron-jobs#retry-policy).
- 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`.
### Telegram delivers to the wrong place
### Cron fired but no delivery
- For forum topics, use `-100…:topic:<id>` so its explicit and unambiguous.
- If you see `telegram:...` prefixes in logs or stored “last route” targets, thats normal;
cron delivery accepts them and still parses topic IDs correctly.
- Delivery mode is `none` means no external message is expected.
- Delivery target missing/invalid (`channel`/`to`) means outbound was skipped.
- Channel auth errors (`unauthorized`, `Forbidden`) mean delivery was blocked by credentials.
### Subagent announce delivery retries
### Timezone gotchas
- When a subagent run completes, the gateway announces the result to the requester session.
- If the announce flow returns `false` (e.g. requester session is busy), the gateway retries up to 3 times with tracking via `announceRetryCount`.
- Announces older than 5 minutes past `endedAt` are force-expired to prevent stale entries from looping indefinitely.
- If you see repeated announce deliveries in logs, check the subagent registry for entries with high `announceRetryCount` values.
- Cron without `--tz` uses the gateway host timezone.
- `at` schedules without timezone are treated as UTC.
- Heartbeat `activeHours` uses configured timezone resolution.
## Related
- [Automation Overview](/automation) — all automation mechanisms at a glance
- [Cron vs Heartbeat](/automation/cron-vs-heartbeat) — when to use each
- [Automation & Tasks](/automation) — all automation mechanisms at a glance
- [Background Tasks](/automation/tasks) — task ledger for cron executions
- [Heartbeat](/gateway/heartbeat) — periodic main-session turns
- [Troubleshooting](/automation/troubleshooting) — debugging automation issues
- [Timezone](/concepts/timezone) — timezone configuration

View File

@@ -1,299 +1,8 @@
---
summary: "Guidance for choosing between heartbeat and cron jobs for automation"
read_when:
- Deciding how to schedule recurring tasks
- Setting up background monitoring or notifications
- Optimizing token usage for periodic checks
summary: "Redirect to /automation"
title: "Cron vs Heartbeat"
---
# Cron vs Heartbeat: When to Use Each
# Cron vs Heartbeat
Both heartbeats and cron jobs let you run tasks on a schedule. This guide helps you choose the right mechanism for your use case.
One important distinction:
- **Heartbeat** is a scheduled **main-session turn** — no task record created.
- **Cron (main)** is a scheduled **system event into the main session** — creates a task record with `silent` notify policy.
- **Cron (isolated)** is a scheduled **background run** — creates a task record tracked in `openclaw tasks`.
All cron job executions (main and isolated) create [task records](/automation/tasks). Heartbeat turns do not. Main-session cron tasks use `silent` notify policy by default so they do not generate notifications.
## Quick Decision Guide
| Use Case | Recommended | Why |
| ------------------------------------ | ------------------- | ---------------------------------------- |
| Check inbox every 30 min | Heartbeat | Batches with other checks, context-aware |
| Send daily report at 9am sharp | Cron (isolated) | Exact timing needed |
| Monitor calendar for upcoming events | Heartbeat | Natural fit for periodic awareness |
| Run weekly deep analysis | Cron (isolated) | Standalone task, can use different model |
| Remind me in 20 minutes | Cron (main, `--at`) | One-shot with precise timing |
| Background project health check | Heartbeat | Piggybacks on existing cycle |
## Heartbeat: Periodic Awareness
Heartbeats run in the **main session** at a regular interval (default: 30 min). They're designed for the agent to check on things and surface anything important.
### When to use heartbeat
- **Multiple periodic checks**: Instead of 5 separate cron jobs checking inbox, calendar, weather, notifications, and project status, a single heartbeat can batch all of these.
- **Context-aware decisions**: The agent has full main-session context, so it can make smart decisions about what's urgent vs. what can wait.
- **Conversational continuity**: Heartbeat runs share the same session, so the agent remembers recent conversations and can follow up naturally.
- **Low-overhead monitoring**: One heartbeat replaces many small polling tasks.
### Heartbeat advantages
- **Batches multiple checks**: One agent turn can review inbox, calendar, and notifications together.
- **Reduces API calls**: A single heartbeat is cheaper than 5 isolated cron jobs.
- **Context-aware**: The agent knows what you've been working on and can prioritize accordingly.
- **Smart suppression**: If nothing needs attention, the agent replies `HEARTBEAT_OK` and no message is delivered.
- **Natural timing**: Drifts slightly based on queue load, which is fine for most monitoring.
- **No task record**: heartbeat turns stay in main-session history (see [Background Tasks](/automation/tasks)).
### Heartbeat example: HEARTBEAT.md checklist
```md
# Heartbeat checklist
- Check email for urgent messages
- Review calendar for events in next 2 hours
- If a background task finished, summarize results
- If idle for 8+ hours, send a brief check-in
```
The agent reads this on each heartbeat and handles all items in one turn.
### Configuring heartbeat
```json5
{
agents: {
defaults: {
heartbeat: {
every: "30m", // interval
target: "last", // explicit alert delivery target (default is "none")
activeHours: { start: "08:00", end: "22:00" }, // optional
},
},
},
}
```
See [Heartbeat](/gateway/heartbeat) for full configuration.
## Cron: Precise Scheduling
Cron jobs run at precise times and can run in isolated sessions without affecting main context.
Recurring top-of-hour schedules are automatically spread by a deterministic
per-job offset in a 0-5 minute window.
### When to use cron
- **Exact timing required**: "Send this at 9:00 AM every Monday" (not "sometime around 9").
- **Standalone tasks**: Tasks that don't need conversational context.
- **Different model/thinking**: Heavy analysis that warrants a more powerful model.
- **One-shot reminders**: "Remind me in 20 minutes" with `--at`.
- **Noisy/frequent tasks**: Tasks that would clutter main session history.
- **External triggers**: Tasks that should run independently of whether the agent is otherwise active.
### Cron advantages
- **Precise timing**: 5-field or 6-field (seconds) cron expressions with timezone support.
- **Built-in load spreading**: recurring top-of-hour schedules are staggered by up to 5 minutes by default.
- **Per-job control**: override stagger with `--stagger <duration>` or force exact timing with `--exact`.
- **Session isolation**: Runs in `cron:<jobId>` without polluting main history.
- **Model overrides**: Use a cheaper or more powerful model per job.
- **Delivery control**: Isolated jobs default to `announce` (summary); choose `none` as needed.
- **Immediate delivery**: Announce mode posts directly without waiting for heartbeat.
- **No agent context needed**: Runs even if main session is idle or compacted.
- **One-shot support**: `--at` for precise future timestamps.
- **Task tracking**: isolated jobs create [background task](/automation/tasks) records visible in `openclaw tasks` and `openclaw tasks audit`.
### Cron example: Daily morning briefing
```bash
openclaw cron add \
--name "Morning briefing" \
--cron "0 7 * * *" \
--tz "America/New_York" \
--session isolated \
--message "Generate today's briefing: weather, calendar, top emails, news summary." \
--model opus \
--announce \
--channel whatsapp \
--to "+15551234567"
```
This runs at exactly 7:00 AM New York time, uses Opus for quality, and announces a summary directly to WhatsApp.
### Cron example: One-shot reminder
```bash
openclaw cron add \
--name "Meeting reminder" \
--at "20m" \
--session main \
--system-event "Reminder: standup meeting starts in 10 minutes." \
--wake now \
--delete-after-run
```
See [Cron jobs](/automation/cron-jobs) for full CLI reference.
## Decision Flowchart
```
Does the task need to run at an EXACT time?
YES -> Use cron
NO -> Continue...
Does the task need isolation from main session?
YES -> Use cron (isolated)
NO -> Continue...
Can this task be batched with other periodic checks?
YES -> Use heartbeat (add to HEARTBEAT.md)
NO -> Use cron
Is this a one-shot reminder?
YES -> Use cron with --at
NO -> Continue...
Does it need a different model or thinking level?
YES -> Use cron (isolated) with --model/--thinking
NO -> Use heartbeat
```
## Combining Both
The most efficient setup uses **both**:
1. **Heartbeat** handles routine monitoring (inbox, calendar, notifications) in one batched turn every 30 minutes.
2. **Cron** handles precise schedules (daily reports, weekly reviews) and one-shot reminders.
### Example: Efficient automation setup
**HEARTBEAT.md** (checked every 30 min):
```md
# Heartbeat checklist
- Scan inbox for urgent emails
- Check calendar for events in next 2h
- Review any pending tasks
- Light check-in if quiet for 8+ hours
```
**Cron jobs** (precise timing):
```bash
# Daily morning briefing at 7am
openclaw cron add --name "Morning brief" --cron "0 7 * * *" --session isolated --message "..." --announce
# Weekly project review on Mondays at 9am
openclaw cron add --name "Weekly review" --cron "0 9 * * 1" --session isolated --message "..." --model opus
# One-shot reminder
openclaw cron add --name "Call back" --at "2h" --session main --system-event "Call back the client" --wake now
```
## Lobster: Deterministic workflows with approvals
Lobster is the workflow runtime for **multi-step tool pipelines** that need deterministic execution and explicit approvals.
Use it when the task is more than a single agent turn, and you want a resumable workflow with human checkpoints.
### When Lobster fits
- **Multi-step automation**: You need a fixed pipeline of tool calls, not a one-off prompt.
- **Approval gates**: Side effects should pause until you approve, then resume.
- **Resumable runs**: Continue a paused workflow without re-running earlier steps.
### How it pairs with heartbeat and cron
- **Heartbeat/cron** decide _when_ a run happens.
- **Lobster** defines _what steps_ happen once the run starts.
For scheduled workflows, use cron or heartbeat to trigger an agent turn that calls Lobster.
For ad-hoc workflows, call Lobster directly.
### Operational notes (from the code)
- Lobster runs as a **local subprocess** (`lobster` CLI) in tool mode and returns a **JSON envelope**.
- If the tool returns `needs_approval`, you resume with a `resumeToken` and `approve` flag.
- The tool is an **optional plugin**; enable it additively via `tools.alsoAllow: ["lobster"]` (recommended).
- Lobster expects the `lobster` CLI to be available on `PATH`.
See [Lobster](/tools/lobster) for full usage and examples.
## Main Session vs Isolated Session
Both heartbeat and cron can interact with the main session, but differently:
| | Heartbeat | Cron (main) | Cron (isolated) |
| -------------------------- | ------------------------------- | ------------------------ | ----------------------------------------------- |
| Session | Main | Main (via system event) | `cron:<jobId>` or custom session |
| History | Shared | Shared | Fresh each run (isolated) / Persistent (custom) |
| Context | Full | Full | None (isolated) / Cumulative (custom) |
| Model | Main session model | Main session model | Can override |
| Output | Delivered if not `HEARTBEAT_OK` | Heartbeat prompt + event | Announce summary (default) |
| [Tasks](/automation/tasks) | No task record | Task record (silent) | Task record (visible in `openclaw tasks`) |
### When to use main session cron
Use `--session main` with `--system-event` when you want:
- The reminder/event to appear in main session context
- The agent to handle it during the next heartbeat with full context
- No separate isolated run
```bash
openclaw cron add \
--name "Check project" \
--every "4h" \
--session main \
--system-event "Time for a project health check" \
--wake now
```
### When to use isolated cron
Use `--session isolated` when you want:
- A clean slate without prior context
- Different model or thinking settings
- Announce summaries directly to a channel
- History that doesn't clutter main session
```bash
openclaw cron add \
--name "Deep analysis" \
--cron "0 6 * * 0" \
--session isolated \
--message "Weekly codebase analysis..." \
--model opus \
--thinking high \
--announce
```
## Cost Considerations
| Mechanism | Cost Profile |
| --------------- | ------------------------------------------------------- |
| Heartbeat | One turn every N minutes; scales with HEARTBEAT.md size |
| Cron (main) | Adds event to next heartbeat (no isolated turn) |
| Cron (isolated) | Full agent turn per job; can use cheaper model |
**Tips**:
- Keep `HEARTBEAT.md` small to minimize token overhead.
- Batch similar checks into heartbeat instead of multiple cron jobs.
- Use `target: "none"` on heartbeat if you only want internal processing.
- Use isolated cron with a cheaper model for routine tasks.
## Related
- [Automation Overview](/automation) — all automation mechanisms at a glance
- [Heartbeat](/gateway/heartbeat) — full heartbeat configuration
- [Cron jobs](/automation/cron-jobs) — full cron CLI and API reference
- [Background Tasks](/automation/tasks) — task ledger, audit, and lifecycle
- [System](/cli/system) — system events + heartbeat controls
This page moved to [Automation & Tasks](/automation). See [Automation & Tasks](/automation) for the decision guide comparing cron and heartbeat.

View File

@@ -1,256 +1,8 @@
---
summary: "Gmail Pub/Sub push wired into OpenClaw webhooks via gogcli"
read_when:
- Wiring Gmail inbox triggers to OpenClaw
- Setting up Pub/Sub push for agent wake
summary: "Redirect to /automation/cron-jobs"
title: "Gmail PubSub"
---
# Gmail Pub/Sub -> OpenClaw
# Gmail PubSub
Goal: Gmail watch -> Pub/Sub push -> `gog gmail watch serve` -> OpenClaw webhook.
## Prereqs
- `gcloud` installed and logged in ([install guide](https://docs.cloud.google.com/sdk/docs/install-sdk)).
- `gog` (gogcli) installed and authorized for the Gmail account ([gogcli.sh](https://gogcli.sh/)).
- OpenClaw hooks enabled (see [Webhooks](/automation/webhook)).
- `tailscale` logged in ([tailscale.com](https://tailscale.com/)). Supported setup uses Tailscale Funnel for the public HTTPS endpoint.
Other tunnel services can work, but are DIY/unsupported and require manual wiring.
Right now, Tailscale is what we support.
Example hook config (enable Gmail preset mapping):
```json5
{
hooks: {
enabled: true,
token: "OPENCLAW_HOOK_TOKEN",
path: "/hooks",
presets: ["gmail"],
},
}
```
To deliver the Gmail summary to a chat surface, override the preset with a mapping
that sets `deliver` + optional `channel`/`to`:
```json5
{
hooks: {
enabled: true,
token: "OPENCLAW_HOOK_TOKEN",
presets: ["gmail"],
mappings: [
{
match: { path: "gmail" },
action: "agent",
wakeMode: "now",
name: "Gmail",
sessionKey: "hook:gmail:{{messages[0].id}}",
messageTemplate: "New email from {{messages[0].from}}\nSubject: {{messages[0].subject}}\n{{messages[0].snippet}}\n{{messages[0].body}}",
model: "openai/gpt-5.2-mini",
deliver: true,
channel: "last",
// to: "+15551234567"
},
],
},
}
```
If you want a fixed channel, set `channel` + `to`. Otherwise `channel: "last"`
uses the last delivery route (falls back to WhatsApp).
To force a cheaper model for Gmail runs, set `model` in the mapping
(`provider/model` or alias). If you enforce `agents.defaults.models`, include it there.
To set a default model and thinking level specifically for Gmail hooks, add
`hooks.gmail.model` / `hooks.gmail.thinking` in your config:
```json5
{
hooks: {
gmail: {
model: "openrouter/meta-llama/llama-3.3-70b-instruct:free",
thinking: "off",
},
},
}
```
Notes:
- Per-hook `model`/`thinking` in the mapping still overrides these defaults.
- Fallback order: `hooks.gmail.model``agents.defaults.model.fallbacks` → primary (auth/rate-limit/timeouts).
- If `agents.defaults.models` is set, the Gmail model must be in the allowlist.
- Gmail hook content is wrapped with external-content safety boundaries by default.
To disable (dangerous), set `hooks.gmail.allowUnsafeExternalContent: true`.
To customize payload handling further, add `hooks.mappings` or a JS/TS transform module
under `~/.openclaw/hooks/transforms` (see [Webhooks](/automation/webhook)).
## Wizard (recommended)
Use the OpenClaw helper to wire everything together (installs deps on macOS via brew):
```bash
openclaw webhooks gmail setup \
--account openclaw@gmail.com
```
Defaults:
- Uses Tailscale Funnel for the public push endpoint.
- Writes `hooks.gmail` config for `openclaw webhooks gmail run`.
- Enables the Gmail hook preset (`hooks.presets: ["gmail"]`).
Path note: when `tailscale.mode` is enabled, OpenClaw automatically sets
`hooks.gmail.serve.path` to `/` and keeps the public path at
`hooks.gmail.tailscale.path` (default `/gmail-pubsub`) because Tailscale
strips the set-path prefix before proxying.
If you need the backend to receive the prefixed path, set
`hooks.gmail.tailscale.target` (or `--tailscale-target`) to a full URL like
`http://127.0.0.1:8788/gmail-pubsub` and match `hooks.gmail.serve.path`.
Want a custom endpoint? Use `--push-endpoint <url>` or `--tailscale off`.
Platform note: on macOS the wizard installs `gcloud`, `gogcli`, and `tailscale`
via Homebrew; on Linux install them manually first.
Gateway auto-start (recommended):
- When `hooks.enabled=true` and `hooks.gmail.account` is set, the Gateway starts
`gog gmail watch serve` on boot and auto-renews the watch.
- Set `OPENCLAW_SKIP_GMAIL_WATCHER=1` to opt out (useful if you run the daemon yourself).
- Do not run the manual daemon at the same time, or you will hit
`listen tcp 127.0.0.1:8788: bind: address already in use`.
Manual daemon (starts `gog gmail watch serve` + auto-renew):
```bash
openclaw webhooks gmail run
```
## One-time setup
1. Select the GCP project **that owns the OAuth client** used by `gog`.
```bash
gcloud auth login
gcloud config set project <project-id>
```
Note: Gmail watch requires the Pub/Sub topic to live in the same project as the OAuth client.
2. Enable APIs:
```bash
gcloud services enable gmail.googleapis.com pubsub.googleapis.com
```
3. Create a topic:
```bash
gcloud pubsub topics create gog-gmail-watch
```
4. Allow Gmail push to publish:
```bash
gcloud pubsub topics add-iam-policy-binding gog-gmail-watch \
--member=serviceAccount:gmail-api-push@system.gserviceaccount.com \
--role=roles/pubsub.publisher
```
## Start the watch
```bash
gog gmail watch start \
--account openclaw@gmail.com \
--label INBOX \
--topic projects/<project-id>/topics/gog-gmail-watch
```
Save the `history_id` from the output (for debugging).
## Run the push handler
Local example (shared token auth):
```bash
gog gmail watch serve \
--account openclaw@gmail.com \
--bind 127.0.0.1 \
--port 8788 \
--path /gmail-pubsub \
--token <shared> \
--hook-url http://127.0.0.1:18789/hooks/gmail \
--hook-token OPENCLAW_HOOK_TOKEN \
--include-body \
--max-bytes 20000
```
Notes:
- `--token` protects the push endpoint (`x-gog-token` or `?token=`).
- `--hook-url` points to OpenClaw `/hooks/gmail` (mapped; isolated run + summary to main).
- `--include-body` and `--max-bytes` control the body snippet sent to OpenClaw.
Recommended: `openclaw webhooks gmail run` wraps the same flow and auto-renews the watch.
## Expose the handler (advanced, unsupported)
If you need a non-Tailscale tunnel, wire it manually and use the public URL in the push
subscription (unsupported, no guardrails):
```bash
cloudflared tunnel --url http://127.0.0.1:8788 --no-autoupdate
```
Use the generated URL as the push endpoint:
```bash
gcloud pubsub subscriptions create gog-gmail-watch-push \
--topic gog-gmail-watch \
--push-endpoint "https://<public-url>/gmail-pubsub?token=<shared>"
```
Production: use a stable HTTPS endpoint and configure Pub/Sub OIDC JWT, then run:
```bash
gog gmail watch serve --verify-oidc --oidc-email <svc@...>
```
## Test
Send a message to the watched inbox:
```bash
gog gmail send \
--account openclaw@gmail.com \
--to openclaw@gmail.com \
--subject "watch test" \
--body "ping"
```
Check watch state and history:
```bash
gog gmail watch status --account openclaw@gmail.com
gog gmail history --account openclaw@gmail.com --since <historyId>
```
## Troubleshooting
- `Invalid topicName`: project mismatch (topic not in the OAuth client project).
- `User not authorized`: missing `roles/pubsub.publisher` on the topic.
- Empty messages: Gmail push only provides `historyId`; fetch via `gog gmail history`.
## Cleanup
```bash
gog gmail watch stop --account openclaw@gmail.com
gcloud pubsub subscriptions delete gog-gmail-watch-push
gcloud pubsub topics delete gog-gmail-watch
```
This page moved to [Scheduled Tasks](/automation/cron-jobs#gmail-pubsub-integration). See [Scheduled Tasks](/automation/cron-jobs#gmail-pubsub-integration) for Gmail PubSub documentation.

File diff suppressed because it is too large Load Diff

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