Compare commits

..

365 Commits

Author SHA1 Message Date
Peter Steinberger
4c6c9333d1 refactor: consolidate cron delivery boundary parsing 2026-04-01 16:22:06 +09:00
Peter Steinberger
6c8fa821c1 fix: preserve telegram topic delivery routing (#58489) (thanks @cwmine) 2026-04-01 16:02:22 +09:00
yi-bot
d9827c920a fix: preserve telegram topic routing in announce and delivery context 2026-03-31 16:57:05 +00: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
1344 changed files with 68939 additions and 15433 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

@@ -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();

5
.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:

View File

@@ -302,11 +302,17 @@ jobs:
- name: Run ${{ matrix.task }} (${{ matrix.runtime }})
env:
TASK: ${{ matrix.task }}
SHARD_COUNT: ${{ matrix.shard_count || '' }}
SHARD_INDEX: ${{ matrix.shard_index || '' }}
shell: bash
run: |
set -euo pipefail
case "$TASK" in
extensions)
if [ -n "$SHARD_COUNT" ] && [ -n "$SHARD_INDEX" ]; then
export OPENCLAW_TEST_SHARDS="$SHARD_COUNT"
export OPENCLAW_TEST_SHARD_INDEX="$SHARD_INDEX"
fi
pnpm test:extensions
;;
contracts|contracts-protocol)
@@ -459,6 +465,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

View File

@@ -71,6 +71,8 @@ jobs:
echo "Publishing openclaw@${PACKAGE_VERSION}"
- name: Check
env:
OPENCLAW_LOCAL_CHECK: "0"
run: pnpm check
- name: Build

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,

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,10 @@
- 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: 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.
- 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,27 +4,39 @@ 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
- 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.
- 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.
- Plugins/install: add `--dangerously-force-unsafe-install` as a break-glass override for built-in dangerous-code install false positives while still keeping plugin `before_install` policy blocks and scan-failure blocking intact.
- Skills/install: block gateway-backed skill dependency installs on built-in dangerous-code `critical` findings unless the caller explicitly sets the matching dangerous override, while keeping suspicious findings warn-only and preserving `before_install` hook blocks.
- 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.
- Flows/tasks: add a minimal SQLite-backed 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.
- Flows/tasks: route one-task ACP and subagent updates through a parent flow owner context, so detached work can emerge back through the intended parent thread/session instead of speaking only as a raw child task.
- Flows/tasks: persist blocked state on one-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.
- ClawFlow: add the first linear flow control surface with `openclaw flows 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.
- 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.
- 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.
- 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.
- TTS: Add structured provider diagnostics and fallback attempt analytics. (#57954) Thanks @joshavant.
- 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.
- 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.
### Fixes
- 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.
- 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.
- 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.
- 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.
- 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.
@@ -37,8 +49,12 @@ Docs: https://docs.openclaw.ai
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
@@ -46,6 +62,7 @@ Docs: https://docs.openclaw.ai
- 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.
- 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.
- 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.
@@ -59,9 +76,13 @@ Docs: https://docs.openclaw.ai
- 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.
- 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: 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.
- 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.
- 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.
- 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.
- 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.
@@ -70,7 +91,9 @@ Docs: https://docs.openclaw.ai
- 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.
- 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.
- 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/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/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.
@@ -101,13 +124,52 @@ Docs: https://docs.openclaw.ai
- 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.
## 2026.3.28
- 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.
- 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.
- 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.
- 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/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/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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- TTS: Restore 3.28 schema compatibility and fallback observability. (#57953) Thanks @joshavant.
- 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.
- 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.
- 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.
- 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/Telegram: migrate removed `channels.telegram.groupMentionsOnly` into `channels.telegram.groups["*"].requireMention` on load so legacy configs no longer crash at startup. (#55336) thanks @jameslcowan.
- 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/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.
- 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.
- 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.
- 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.
- 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.
- 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.
- ClawFlow: add a small 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.
- Sessions/Feishu: preserve conversation ids that legitimately embed `:topic:` in shared session helper parsing, while keeping Telegram topic session parsing intact. (#58100) Thanks @gumadeiras.
### 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`.
- 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.
## 2026.3.28
### Changes
@@ -145,6 +207,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)
- 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.
- 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.
- Mistral: normalize OpenAI-compatible request flags so official Mistral API runs no longer fail with remaining `422 status code (no body)` chat errors.
@@ -185,9 +248,13 @@ 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
@@ -200,6 +267,7 @@ Docs: https://docs.openclaw.ai
- 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.
- Agents/failover: classify Codex accountId token extraction failures as auth errors so model fallback continues to the next configured candidate. (#55206) Thanks @cosmicnet.
- 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.
- Plugins/runtime: reuse only compatible active plugin registries across tools, providers, web search, and channel bootstrap, align `/tools/invoke` 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.
- 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.
- 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.
@@ -250,6 +318,11 @@ Docs: https://docs.openclaw.ai
- 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.
### 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
### Breaking
@@ -317,6 +390,7 @@ Docs: https://docs.openclaw.ai
- 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
- 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.
## 2026.3.24-beta.1
@@ -390,7 +464,12 @@ 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.
- Channels/QQ Bot: add QQ Bot as a bundled first-party channel plugin for the official QQ Bot API, including multi-account setup, SecretRef-aware credentials, QQ-specific slash commands, media send/receive support, and bundled-channel integration fixes for config schema, version/help surfaces, and local media delivery.
- 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.
- 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.
- Config/web fetch: allow the documented `tools.web.fetch.maxResponseBytes` setting in runtime schema validation so valid configs no longer fail with unrecognized-key errors. (#53401) Thanks @erhhung.
- 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.
- 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.
## 2026.3.23
@@ -698,6 +777,7 @@ Docs: https://docs.openclaw.ai
- Z.AI/onboarding: add `glm-5-turbo` to the default Z.AI provider catalog so onboarding-generated configs expose the new model alongside the existing GLM defaults. (#46670) Thanks @tomsun28.
- Zalo Personal/group gating: stop reapplying `dmPolicy.allowFrom` as a sender gate for already-allowlisted groups when `groupAllowFrom` is unset, so any member of an allowed group can trigger replies while DMs stay restricted. (#46663) Fixes #40146. Thanks @Takhoffman.
- Zalo/plugin runtime: export `resolveClientIp` from `openclaw/plugin-sdk/zalo` so installed builds no longer crash on startup when the webhook monitor loads from the packaged extension instead of the monorepo source tree. (#46549) Thanks @No898.
- Onboarding/custom providers: usage of pi-ai's specialized "azure-openai-responses" implementation for onboarded Azure OpenAI endpoints. (#50851) Thanks @kunalk16.
- Docker/live tests: mount external CLI auth homes into writable container copies, derive Codex OAuth expiry from JWT `exp`, refresh synced CLI creds instead of trusting stale cached expiry, and make gateway live probes wait on transcript output so `pnpm test:docker:all` stays green in Linux.
- Gateway/watch mode: restart on bundled-plugin package and manifest metadata changes, rebuild `dist` for extension source and `tsdown.config.ts` changes, and still ignore extension docs. (#47571) Thanks @gumadeiras.
- Gateway/watch mode: recreate bundled plugin runtime metadata after clean or stale `dist` states, so `pnpm gateway:watch` no longer fails on missing bundled-plugin manifests after a rebuild. Thanks @gumadeiras.

View File

@@ -57,7 +57,10 @@ These are frequently reported but are typically closed with no code change:
- 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 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 +96,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 +139,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.

View File

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

View File

@@ -91,6 +91,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
@@ -261,6 +262,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()
}

View File

@@ -45,6 +45,12 @@ class NodeRuntime(
context: Context,
val prefs: SecurePrefs = SecurePrefs(context.applicationContext),
) {
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)
@@ -775,28 +781,51 @@ 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) {
@@ -818,25 +847,27 @@ 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 = resolveGatewayConnectAuth())
}
fun connect(
endpoint: GatewayEndpoint,
auth: GatewayConnectAuth,
) {
connectedEndpoint = endpoint
operatorStatusText = "Connecting…"
nodeStatusText = "Connecting…"
updateStatus()
connectWithAuth(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() {
@@ -868,6 +899,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
@@ -1197,6 +1233,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

@@ -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,
)
@@ -269,7 +275,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),

View File

@@ -37,9 +37,13 @@ private val gatewaySetupJson = Json { ignoreUnknownKeys = true }
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? {
@@ -69,13 +73,23 @@ internal fun resolveGatewayConnectConfig(
)
}
val manualUrl = composeGatewayManualUrl(manualHost, manualPort, manualTls) ?: return null
val manualUrl = composeGatewayManualUrl(manualHostInput, manualPortInput, manualTlsInput) ?: return null
val parsed = parseGatewayEndpoint(manualUrl) ?: return null
val savedManualEndpoint =
composeGatewayManualUrl(savedManualHost, savedManualPort, savedManualTls)
?.let(::parseGatewayEndpoint)
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(),
)

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 =
@@ -732,7 +735,7 @@ fun OnboardingFlow(viewModel: MainViewModel, modifier: Modifier = Modifier) {
FinalStep(
parsedGateway = parseGatewayEndpoint(gatewayUrl),
statusText = statusText,
isConnected = isConnected,
isConnected = canFinishOnboarding,
serverName = serverName,
remoteAddress = remoteAddress,
attemptedConnect = attemptedConnect,
@@ -848,7 +851,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),
@@ -882,7 +885,17 @@ fun OnboardingFlow(viewModel: MainViewModel, modifier: Modifier = Modifier) {
viewModel.setGatewayToken("")
}
viewModel.setGatewayPassword(password)
viewModel.connectManual()
viewModel.connect(
GatewayEndpoint.manual(host = parsed.host, port = parsed.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 +911,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(
@@ -1677,21 +1694,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

@@ -0,0 +1,58 @@
package ai.openclaw.app
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.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)
}
}

View File

@@ -155,9 +155,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 +183,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 +202,74 @@ class GatewayConfigResolverTest {
assertNull(resolved?.password?.takeIf { it.isNotEmpty() })
}
@Test
fun resolveGatewayConnectConfigManualPreservesBootstrapTokenWhenNoReplacementAuthExists() {
val resolved =
resolveGatewayConnectConfig(
useSetupCode = false,
setupCode = "",
savedManualHost = "192.168.31.100",
savedManualPort = "18789",
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)
assertEquals("bootstrap-1", resolved?.bootstrapToken)
assertEquals("", resolved?.token)
assertEquals("", resolved?.password)
}
@Test
fun resolveGatewayConnectConfigManualDropsBootstrapTokenWhenReplacementPasswordExists() {
val resolved =
resolveGatewayConnectConfig(
useSetupCode = false,
setupCode = "",
savedManualHost = "192.168.31.100",
savedManualPort = "18789",
savedManualTls = false,
manualHostInput = "192.168.31.100",
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 = "192.168.31.100",
savedManualPort = "18789",
savedManualTls = false,
manualHostInput = "192.168.31.101",
manualPortInput = "18789",
manualTlsInput = false,
fallbackBootstrapToken = "bootstrap-1",
fallbackToken = "",
fallbackPassword = "",
)
assertEquals("", resolved?.bootstrapToken)
assertEquals("192.168.31.101", resolved?.host)
}
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

@@ -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.3.31
OPENCLAW_MARKETING_VERSION = 2026.3.31
OPENCLAW_BUILD_VERSION = 2026033100
#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.3.31-beta.1` becomes:
- `CFBundleShortVersionString = 2026.3.31`
- `CFBundleVersion = next TestFlight build number for 2026.3.31`
Required env for beta builds:

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)
}

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,52 @@ enum HostEnvSecurityPolicy {
"PHP_INI_SCAN_DIR",
"DENO_DIR",
"BUN_CONFIG_REGISTRY",
"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_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",

View File

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

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

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

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
{"generatedBy":"scripts/generate-config-doc-baseline.ts","recordType":"meta","totalPaths":5593}
{"generatedBy":"scripts/generate-config-doc-baseline.ts","recordType":"meta","totalPaths":5701}
{"recordType":"path","path":"acp","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"ACP","help":"ACP runtime controls for enabling dispatch, selecting backends, constraining allowed agent targets, and tuning streamed turn projection behavior.","hasChildren":true}
{"recordType":"path","path":"acp.allowedAgents","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"ACP Allowed Agents","help":"Allowlist of ACP target agent ids permitted for ACP runtime sessions. Empty means no additional allowlist restriction.","hasChildren":true}
{"recordType":"path","path":"acp.allowedAgents.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
@@ -144,7 +144,7 @@
{"recordType":"path","path":"agents.defaults.heartbeat.prompt","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"agents.defaults.heartbeat.session","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"agents.defaults.heartbeat.suppressToolErrorWarnings","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["automation"],"label":"Heartbeat Suppress Tool Error Warnings","help":"Suppress tool error warning payloads during heartbeat runs.","hasChildren":false}
{"recordType":"path","path":"agents.defaults.heartbeat.target","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["automation"],"help":"Delivery target (\"last\", \"none\", or a channel id). Known channels: telegram, whatsapp, discord, irc, googlechat, slack, signal, imessage, line, bluebubbles, feishu, matrix, mattermost, msteams, nextcloud-talk, nostr, synology-chat, tlon, twitch, zalo, zalouser.","hasChildren":false}
{"recordType":"path","path":"agents.defaults.heartbeat.target","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["automation"],"help":"Delivery target (\"last\", \"none\", or a channel id). Known channels: telegram, whatsapp, discord, irc, googlechat, slack, signal, imessage, line, bluebubbles, feishu, matrix, mattermost, msteams, nextcloud-talk, nostr, qqbot, synology-chat, tlon, twitch, zalo, zalouser.","hasChildren":false}
{"recordType":"path","path":"agents.defaults.heartbeat.to","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"agents.defaults.humanDelay","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
{"recordType":"path","path":"agents.defaults.humanDelay.maxMs","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["performance"],"label":"Human Delay Max (ms)","help":"Maximum delay in ms for custom humanDelay (default: 2500).","hasChildren":false}
@@ -159,6 +159,8 @@
{"recordType":"path","path":"agents.defaults.imageModel.fallbacks","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["media","models","reliability"],"label":"Image Model Fallbacks","help":"Ordered fallback image models (provider/model).","hasChildren":true}
{"recordType":"path","path":"agents.defaults.imageModel.fallbacks.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"agents.defaults.imageModel.primary","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["media","models"],"label":"Image Model","help":"Optional image model (provider/model) used when the primary model lacks image input.","hasChildren":false}
{"recordType":"path","path":"agents.defaults.llm","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
{"recordType":"path","path":"agents.defaults.llm.idleTimeoutSeconds","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"agents.defaults.maxConcurrent","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"agents.defaults.mediaMaxMb","kind":"core","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"agents.defaults.memorySearch","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Memory Search","help":"Vector search over MEMORY.md and memory/*.md (per-agent overrides supported).","hasChildren":true}
@@ -204,7 +206,7 @@
{"recordType":"path","path":"agents.defaults.memorySearch.remote.apiKey.id","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"agents.defaults.memorySearch.remote.apiKey.provider","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"agents.defaults.memorySearch.remote.apiKey.source","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"agents.defaults.memorySearch.remote.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Remote Embedding Base URL","help":"Overrides the embedding API endpoint, such as an OpenAI-compatible proxy or custom Gemini base URL. Use this only when routing through your own gateway or vendor endpoint; keep provider defaults otherwise.","hasChildren":false}
{"recordType":"path","path":"agents.defaults.memorySearch.remote.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced","url-secret"],"label":"Remote Embedding Base URL","help":"Overrides the embedding API endpoint, such as an OpenAI-compatible proxy or custom Gemini base URL. Use this only when routing through your own gateway or vendor endpoint; keep provider defaults otherwise.","hasChildren":false}
{"recordType":"path","path":"agents.defaults.memorySearch.remote.batch","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
{"recordType":"path","path":"agents.defaults.memorySearch.remote.batch.concurrency","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["performance"],"label":"Remote Batch Concurrency","help":"Limits how many embedding batch jobs run at the same time during indexing (default: 2). Increase carefully for faster bulk indexing, but watch provider rate limits and queue errors.","hasChildren":false}
{"recordType":"path","path":"agents.defaults.memorySearch.remote.batch.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Remote Batch Embedding Enabled","help":"Enables provider batch APIs for embedding jobs when supported (OpenAI/Gemini), improving throughput on larger index runs. Keep this enabled unless debugging provider batch failures or running very small workloads.","hasChildren":false}
@@ -342,6 +344,7 @@
{"recordType":"path","path":"agents.defaults.subagents.model.fallbacks","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
{"recordType":"path","path":"agents.defaults.subagents.model.fallbacks.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"agents.defaults.subagents.model.primary","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"agents.defaults.subagents.requireAgentId","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"agents.defaults.subagents.runTimeoutSeconds","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"agents.defaults.subagents.thinking","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"agents.defaults.thinkingDefault","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
@@ -377,7 +380,7 @@
{"recordType":"path","path":"agents.list.*.heartbeat.prompt","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"agents.list.*.heartbeat.session","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"agents.list.*.heartbeat.suppressToolErrorWarnings","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["automation"],"label":"Agent Heartbeat Suppress Tool Error Warnings","help":"Suppress tool error warning payloads during heartbeat runs.","hasChildren":false}
{"recordType":"path","path":"agents.list.*.heartbeat.target","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["automation"],"help":"Delivery target (\"last\", \"none\", or a channel id). Known channels: telegram, whatsapp, discord, irc, googlechat, slack, signal, imessage, line, bluebubbles, feishu, matrix, mattermost, msteams, nextcloud-talk, nostr, synology-chat, tlon, twitch, zalo, zalouser.","hasChildren":false}
{"recordType":"path","path":"agents.list.*.heartbeat.target","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["automation"],"help":"Delivery target (\"last\", \"none\", or a channel id). Known channels: telegram, whatsapp, discord, irc, googlechat, slack, signal, imessage, line, bluebubbles, feishu, matrix, mattermost, msteams, nextcloud-talk, nostr, qqbot, synology-chat, tlon, twitch, zalo, zalouser.","hasChildren":false}
{"recordType":"path","path":"agents.list.*.heartbeat.to","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"agents.list.*.humanDelay","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
{"recordType":"path","path":"agents.list.*.humanDelay.maxMs","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
@@ -432,7 +435,7 @@
{"recordType":"path","path":"agents.list.*.memorySearch.remote.apiKey.id","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"agents.list.*.memorySearch.remote.apiKey.provider","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"agents.list.*.memorySearch.remote.apiKey.source","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"agents.list.*.memorySearch.remote.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"agents.list.*.memorySearch.remote.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced","url-secret"],"hasChildren":false}
{"recordType":"path","path":"agents.list.*.memorySearch.remote.batch","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
{"recordType":"path","path":"agents.list.*.memorySearch.remote.batch.concurrency","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"agents.list.*.memorySearch.remote.batch.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
@@ -566,6 +569,7 @@
{"recordType":"path","path":"agents.list.*.subagents.model.fallbacks","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
{"recordType":"path","path":"agents.list.*.subagents.model.fallbacks.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"agents.list.*.subagents.model.primary","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"agents.list.*.subagents.requireAgentId","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"agents.list.*.subagents.thinking","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"agents.list.*.thinkingDefault","kind":"core","type":"string","required":false,"enumValues":["off","minimal","low","medium","high","xhigh","adaptive"],"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Agent Thinking Default","help":"Optional per-agent default thinking level. Overrides agents.defaults.thinkingDefault for this agent when no per-message or session override is set.","hasChildren":false}
{"recordType":"path","path":"agents.list.*.tools","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
@@ -599,7 +603,7 @@
{"recordType":"path","path":"agents.list.*.tools.exec.ask","kind":"core","type":"string","required":false,"enumValues":["off","on-miss","always"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"agents.list.*.tools.exec.backgroundMs","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"agents.list.*.tools.exec.cleanupMs","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"agents.list.*.tools.exec.host","kind":"core","type":"string","required":false,"enumValues":["sandbox","gateway","node"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"agents.list.*.tools.exec.host","kind":"core","type":"string","required":false,"enumValues":["auto","sandbox","gateway","node"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"agents.list.*.tools.exec.node","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"agents.list.*.tools.exec.notifyOnExit","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"agents.list.*.tools.exec.notifyOnExitEmptySuccess","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
@@ -1993,6 +1997,7 @@
{"recordType":"path","path":"channels.matrix.groups.*.tools.deny.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.matrix.groups.*.users","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
{"recordType":"path","path":"channels.matrix.groups.*.users.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.matrix.historyLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.matrix.homeserver","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.matrix.initialSyncLimit","kind":"channel","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.matrix.markdown","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
@@ -2374,6 +2379,56 @@
{"recordType":"path","path":"channels.nostr.profile.website","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.nostr.relays","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
{"recordType":"path","path":"channels.nostr.relays.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.qqbot","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"QQ Bot","help":"connect to QQ via official QQ Bot API with group chat and direct message support.","hasChildren":true}
{"recordType":"path","path":"channels.qqbot.accounts","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
{"recordType":"path","path":"channels.qqbot.accounts.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
{"recordType":"path","path":"channels.qqbot.accounts.*.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
{"recordType":"path","path":"channels.qqbot.accounts.*.allowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.qqbot.accounts.*.appId","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.qqbot.accounts.*.audioFormatPolicy","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
{"recordType":"path","path":"channels.qqbot.accounts.*.audioFormatPolicy.sttDirectFormats","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
{"recordType":"path","path":"channels.qqbot.accounts.*.audioFormatPolicy.sttDirectFormats.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.qqbot.accounts.*.audioFormatPolicy.transcodeEnabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.qqbot.accounts.*.audioFormatPolicy.uploadDirectFormats","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
{"recordType":"path","path":"channels.qqbot.accounts.*.audioFormatPolicy.uploadDirectFormats.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.qqbot.accounts.*.clientSecret","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","channels","network","security"],"hasChildren":true}
{"recordType":"path","path":"channels.qqbot.accounts.*.clientSecret.id","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.qqbot.accounts.*.clientSecret.provider","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.qqbot.accounts.*.clientSecret.source","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.qqbot.accounts.*.clientSecretFile","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":true,"tags":["auth","channels","network","security","storage"],"hasChildren":false}
{"recordType":"path","path":"channels.qqbot.accounts.*.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.qqbot.accounts.*.markdownSupport","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.qqbot.accounts.*.name","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.qqbot.accounts.*.systemPrompt","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.qqbot.accounts.*.upgradeMode","kind":"channel","type":"string","required":false,"enumValues":["doc","hot-reload"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.qqbot.accounts.*.upgradeUrl","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.qqbot.accounts.*.urlDirectUpload","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.qqbot.accounts.*.voiceDirectUploadFormats","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
{"recordType":"path","path":"channels.qqbot.accounts.*.voiceDirectUploadFormats.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.qqbot.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
{"recordType":"path","path":"channels.qqbot.allowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.qqbot.appId","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.qqbot.audioFormatPolicy","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
{"recordType":"path","path":"channels.qqbot.audioFormatPolicy.sttDirectFormats","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
{"recordType":"path","path":"channels.qqbot.audioFormatPolicy.sttDirectFormats.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.qqbot.audioFormatPolicy.transcodeEnabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.qqbot.audioFormatPolicy.uploadDirectFormats","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
{"recordType":"path","path":"channels.qqbot.audioFormatPolicy.uploadDirectFormats.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.qqbot.clientSecret","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","channels","network","security"],"hasChildren":true}
{"recordType":"path","path":"channels.qqbot.clientSecret.id","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.qqbot.clientSecret.provider","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.qqbot.clientSecret.source","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.qqbot.clientSecretFile","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":true,"tags":["auth","channels","network","security","storage"],"hasChildren":false}
{"recordType":"path","path":"channels.qqbot.defaultAccount","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.qqbot.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.qqbot.markdownSupport","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.qqbot.name","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.qqbot.systemPrompt","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.qqbot.upgradeMode","kind":"channel","type":"string","required":false,"enumValues":["doc","hot-reload"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.qqbot.upgradeUrl","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.qqbot.urlDirectUpload","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.qqbot.voiceDirectUploadFormats","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
{"recordType":"path","path":"channels.qqbot.voiceDirectUploadFormats.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.signal","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Signal","help":"signal-cli linked device; more setup (David Reagans: \"Hop on Discord.\").","hasChildren":true}
{"recordType":"path","path":"channels.signal.account","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Signal Account","help":"Signal account identifier (phone/number handle) used to bind this channel config to a specific Signal identity. Keep this aligned with your linked device/session state.","hasChildren":false}
{"recordType":"path","path":"channels.signal.accounts","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
@@ -3053,7 +3108,7 @@
{"recordType":"path","path":"channels.telegram.execApprovals","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Telegram Exec Approvals","help":"Telegram-native exec approval routing and approver authorization. Enable this only when Telegram should act as an explicit exec-approval client for the selected bot account.","hasChildren":true}
{"recordType":"path","path":"channels.telegram.execApprovals.agentFilter","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Telegram Exec Approval Agent Filter","help":"Optional allowlist of agent IDs eligible for Telegram exec approvals, for example `[\"main\", \"ops-agent\"]`. Use this to keep approval prompts scoped to the agents you actually operate from Telegram.","hasChildren":true}
{"recordType":"path","path":"channels.telegram.execApprovals.agentFilter.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.telegram.execApprovals.approvers","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Telegram Exec Approval Approvers","help":"Telegram user IDs allowed to approve exec requests for this bot account. Use numeric Telegram user IDs; prompts are only delivered to these approvers when target includes dm.","hasChildren":true}
{"recordType":"path","path":"channels.telegram.execApprovals.approvers","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Telegram Exec Approval Approvers","help":"Telegram user IDs allowed to approve exec requests for this bot account. Use numeric Telegram user IDs. If you leave this unset, OpenClaw falls back to numeric owner IDs inferred from channels.telegram.allowFrom and direct-message defaultTo when possible.","hasChildren":true}
{"recordType":"path","path":"channels.telegram.execApprovals.approvers.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.telegram.execApprovals.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Telegram Exec Approvals Enabled","help":"Enable Telegram exec approvals for this account. When false or unset, Telegram messages/buttons cannot approve exec requests.","hasChildren":false}
{"recordType":"path","path":"channels.telegram.execApprovals.sessionFilter","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network","storage"],"label":"Telegram Exec Approval Session Filter","help":"Optional session-key filters matched as substring or regex-style patterns before Telegram approval routing is used. Use narrow patterns so Telegram approvals only appear for intended sessions.","hasChildren":true}
@@ -3647,7 +3702,7 @@
{"recordType":"path","path":"gateway.push","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["network"],"label":"Gateway Push Delivery","help":"Push-delivery settings used by the gateway when it needs to wake or notify paired devices. Configure relay-backed APNs here for official iOS builds; direct APNs auth remains env-based for local/manual builds.","hasChildren":true}
{"recordType":"path","path":"gateway.push.apns","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["network"],"label":"Gateway APNs Delivery","help":"APNs delivery settings for iOS devices paired to this gateway. Use relay settings for official/TestFlight builds that register through the external push relay.","hasChildren":true}
{"recordType":"path","path":"gateway.push.apns.relay","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["network"],"label":"Gateway APNs Relay","help":"External relay settings for relay-backed APNs sends. The gateway uses this relay for push.test, wake nudges, and reconnect wakes after a paired official iOS build publishes a relay-backed registration.","hasChildren":true}
{"recordType":"path","path":"gateway.push.apns.relay.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced","network"],"label":"Gateway APNs Relay Base URL","help":"Base HTTPS URL for the external APNs relay service used by official/TestFlight iOS builds. Keep this aligned with the relay URL baked into the iOS build so registration and send traffic hit the same deployment.","hasChildren":false}
{"recordType":"path","path":"gateway.push.apns.relay.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced","network","url-secret"],"label":"Gateway APNs Relay Base URL","help":"Base HTTPS URL for the external APNs relay service used by official/TestFlight iOS builds. Keep this aligned with the relay URL baked into the iOS build so registration and send traffic hit the same deployment.","hasChildren":false}
{"recordType":"path","path":"gateway.push.apns.relay.timeoutMs","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["network","performance"],"label":"Gateway APNs Relay Timeout (ms)","help":"Timeout in milliseconds for relay send requests from the gateway to the APNs relay (default: 10000). Increase for slower relays or networks, or lower to fail wake attempts faster.","hasChildren":false}
{"recordType":"path","path":"gateway.reload","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["network","reliability"],"label":"Config Reload","help":"Live config-reload policy for how edits are applied and when full restarts are triggered. Keep hybrid behavior for safest operational updates unless debugging reload internals.","hasChildren":true}
{"recordType":"path","path":"gateway.reload.debounceMs","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["network","performance","reliability"],"label":"Config Reload Debounce (ms)","help":"Debounce window (ms) before applying config changes.","hasChildren":false}
@@ -3796,7 +3851,9 @@
{"recordType":"path","path":"mcp.servers.*.cwd","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"mcp.servers.*.env","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
{"recordType":"path","path":"mcp.servers.*.env.*","kind":"core","type":["boolean","number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"mcp.servers.*.url","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"mcp.servers.*.headers","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
{"recordType":"path","path":"mcp.servers.*.headers.*","kind":"core","type":["boolean","number","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["security"],"hasChildren":false}
{"recordType":"path","path":"mcp.servers.*.url","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced","url-secret"],"hasChildren":false}
{"recordType":"path","path":"mcp.servers.*.workingDirectory","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"media","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Media","help":"Top-level media behavior shared across providers and tools that handle inbound files. Keep defaults unless you need stable filenames for external processing pipelines or longer-lived inbound media retention.","hasChildren":true}
{"recordType":"path","path":"media.preserveFilenames","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Preserve Media Filenames","help":"When enabled, uploaded media keeps its original filename instead of a generated temp-safe name. Turn this on when downstream automations depend on stable names, and leave off to reduce accidental filename leakage.","hasChildren":false}
@@ -3832,6 +3889,7 @@
{"recordType":"path","path":"memory.qmd.scope.rules.*.match.keyPrefix","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"memory.qmd.scope.rules.*.match.rawKeyPrefix","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"memory.qmd.searchMode","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"QMD Search Mode","help":"Selects the QMD retrieval path: \"query\" uses standard query flow, \"search\" uses search-oriented retrieval, and \"vsearch\" emphasizes vector retrieval. Keep default unless tuning relevance quality.","hasChildren":false}
{"recordType":"path","path":"memory.qmd.searchTool","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"QMD Search Tool Override","help":"Overrides the exact mcporter tool name used for QMD searches while preserving `searchMode` as the semantic retrieval mode. Use this only when your QMD MCP server exposes a custom tool such as `hybrid_search` and keep it unset for the normal built-in tool mapping.","hasChildren":false}
{"recordType":"path","path":"memory.qmd.sessions","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
{"recordType":"path","path":"memory.qmd.sessions.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"QMD Session Indexing","help":"Indexes session transcripts into QMD so recall can include prior conversation content (experimental, default: false). Enable only when transcript memory is required and you accept larger index churn.","hasChildren":false}
{"recordType":"path","path":"memory.qmd.sessions.exportDir","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"QMD Session Export Directory","help":"Overrides where sanitized session exports are written before QMD indexing. Use this when default state storage is constrained or when exports must land on a managed volume.","hasChildren":false}
@@ -3937,14 +3995,14 @@
{"recordType":"path","path":"models.mode","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["models"],"label":"Model Catalog Mode","help":"Controls provider catalog behavior: \"merge\" keeps built-ins and overlays your custom providers, while \"replace\" uses only your configured providers. In \"merge\", matching provider IDs preserve non-empty agent models.json baseUrl values, while apiKey values are preserved only when the provider is not SecretRef-managed in current config/auth-profile context; SecretRef-managed providers refresh apiKey from current source markers, and matching model contextWindow/maxTokens use the higher value between explicit and implicit entries.","hasChildren":false}
{"recordType":"path","path":"models.providers","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["models"],"label":"Model Providers","help":"Provider map keyed by provider ID containing connection/auth settings and concrete model definitions. Use stable provider keys so references from agents and tooling remain portable across environments.","hasChildren":true}
{"recordType":"path","path":"models.providers.*","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
{"recordType":"path","path":"models.providers.*.api","kind":"core","type":"string","required":false,"enumValues":["openai-completions","openai-responses","openai-codex-responses","anthropic-messages","google-generative-ai","github-copilot","bedrock-converse-stream","ollama"],"deprecated":false,"sensitive":false,"tags":["models"],"label":"Model Provider API Adapter","help":"Provider API adapter selection controlling request/response compatibility handling for model calls. Use the adapter that matches your upstream provider protocol to avoid feature mismatch.","hasChildren":false}
{"recordType":"path","path":"models.providers.*.api","kind":"core","type":"string","required":false,"enumValues":["openai-completions","openai-responses","openai-codex-responses","anthropic-messages","google-generative-ai","github-copilot","bedrock-converse-stream","ollama","azure-openai-responses"],"deprecated":false,"sensitive":false,"tags":["models"],"label":"Model Provider API Adapter","help":"Provider API adapter selection controlling request/response compatibility handling for model calls. Use the adapter that matches your upstream provider protocol to avoid feature mismatch.","hasChildren":false}
{"recordType":"path","path":"models.providers.*.apiKey","kind":"core","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","models","security"],"label":"Model Provider API Key","help":"Provider credential used for API-key based authentication when the provider requires direct key auth. Use secret/env substitution and avoid storing real keys in committed config files.","hasChildren":true}
{"recordType":"path","path":"models.providers.*.apiKey.id","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"models.providers.*.apiKey.provider","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"models.providers.*.apiKey.source","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"models.providers.*.auth","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["models"],"label":"Model Provider Auth Mode","help":"Selects provider auth style: \"api-key\" for API key auth, \"token\" for bearer token auth, \"oauth\" for OAuth credentials, and \"aws-sdk\" for AWS credential resolution. Match this to your provider requirements.","hasChildren":false}
{"recordType":"path","path":"models.providers.*.authHeader","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["models"],"label":"Model Provider Authorization Header","help":"When true, credentials are sent via the HTTP Authorization header even if alternate auth is possible. Use this only when your provider or proxy explicitly requires Authorization forwarding.","hasChildren":false}
{"recordType":"path","path":"models.providers.*.baseUrl","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":["models"],"label":"Model Provider Base URL","help":"Base URL for the provider endpoint used to serve model requests for that provider entry. Use HTTPS endpoints and keep URLs environment-specific through config templating where needed.","hasChildren":false}
{"recordType":"path","path":"models.providers.*.baseUrl","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":["models","url-secret"],"label":"Model Provider Base URL","help":"Base URL for the provider endpoint used to serve model requests for that provider entry. Use HTTPS endpoints and keep URLs environment-specific through config templating where needed.","hasChildren":false}
{"recordType":"path","path":"models.providers.*.headers","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["models"],"label":"Model Provider Headers","help":"Static HTTP headers merged into provider requests for tenant routing, proxy auth, or custom gateway requirements. Use this sparingly and keep sensitive header values in secrets.","hasChildren":true}
{"recordType":"path","path":"models.providers.*.headers.*","kind":"core","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["models","security"],"hasChildren":true}
{"recordType":"path","path":"models.providers.*.headers.*.id","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
@@ -3953,7 +4011,7 @@
{"recordType":"path","path":"models.providers.*.injectNumCtxForOpenAICompat","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["models"],"label":"Model Provider Inject num_ctx (OpenAI Compat)","help":"Controls whether OpenClaw injects `options.num_ctx` for Ollama providers configured with the OpenAI-compatible adapter (`openai-completions`). Default is true. Set false only if your proxy/upstream rejects unknown `options` payload fields.","hasChildren":false}
{"recordType":"path","path":"models.providers.*.models","kind":"core","type":"array","required":true,"deprecated":false,"sensitive":false,"tags":["models"],"label":"Model Provider Model List","help":"Declared model list for a provider including identifiers, metadata, and optional compatibility/cost hints. Keep IDs exact to provider catalog values so selection and fallback resolve correctly.","hasChildren":true}
{"recordType":"path","path":"models.providers.*.models.*","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
{"recordType":"path","path":"models.providers.*.models.*.api","kind":"core","type":"string","required":false,"enumValues":["openai-completions","openai-responses","openai-codex-responses","anthropic-messages","google-generative-ai","github-copilot","bedrock-converse-stream","ollama"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"models.providers.*.models.*.api","kind":"core","type":"string","required":false,"enumValues":["openai-completions","openai-responses","openai-codex-responses","anthropic-messages","google-generative-ai","github-copilot","bedrock-converse-stream","ollama","azure-openai-responses"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"models.providers.*.models.*.compat","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
{"recordType":"path","path":"models.providers.*.models.*.compat.maxTokensField","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"models.providers.*.models.*.compat.nativeWebSearchTool","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
@@ -4023,6 +4081,7 @@
{"recordType":"path","path":"plugins.entries.acpx.config.mcpServers.*.env.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.acpx.config.nonInteractivePermissions","kind":"plugin","type":"string","required":false,"enumValues":["deny","fail"],"deprecated":false,"sensitive":false,"tags":["access"],"label":"Non-Interactive Permission Policy","help":"acpx policy when interactive permission prompts are unavailable.","hasChildren":false}
{"recordType":"path","path":"plugins.entries.acpx.config.permissionMode","kind":"plugin","type":"string","required":false,"enumValues":["approve-all","approve-reads","deny-all"],"deprecated":false,"sensitive":false,"tags":["access"],"label":"Permission Mode","help":"Default acpx permission policy for runtime prompts.","hasChildren":false}
{"recordType":"path","path":"plugins.entries.acpx.config.pluginToolsMcpBridge","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Plugin Tools MCP Bridge","help":"Default off. When enabled, inject the built-in OpenClaw plugin-tools MCP server into ACPX sessions so ACP agents can call plugin-registered tools.","hasChildren":false}
{"recordType":"path","path":"plugins.entries.acpx.config.queueOwnerTtlSeconds","kind":"plugin","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":["access","advanced"],"label":"Queue Owner TTL Seconds","help":"Idle queue-owner TTL for acpx prompt turns. Keep this short in OpenClaw to avoid delayed completion after each turn.","hasChildren":false}
{"recordType":"path","path":"plugins.entries.acpx.config.strictWindowsCmdWrapper","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Strict Windows cmd Wrapper","help":"Enabled by default. On Windows, reject unresolved .cmd/.bat wrappers instead of shell fallback. Disable only for compatibility with non-standard wrappers.","hasChildren":false}
{"recordType":"path","path":"plugins.entries.acpx.config.timeoutSeconds","kind":"plugin","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":["advanced","performance"],"label":"Prompt Timeout Seconds","help":"Optional acpx timeout for each runtime turn.","hasChildren":false}
@@ -4043,6 +4102,15 @@
{"recordType":"path","path":"plugins.entries.amazon-bedrock.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.amazon-bedrock.subagent.allowModelOverride","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Allow Plugin Subagent Model Override","help":"Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.","hasChildren":false}
{"recordType":"path","path":"plugins.entries.anthropic","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/anthropic-provider","help":"OpenClaw Anthropic provider plugin (plugin: anthropic)","hasChildren":true}
{"recordType":"path","path":"plugins.entries.anthropic-vertex","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/anthropic-vertex-provider","help":"OpenClaw Anthropic Vertex provider plugin (plugin: anthropic-vertex)","hasChildren":true}
{"recordType":"path","path":"plugins.entries.anthropic-vertex.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/anthropic-vertex-provider Config","help":"Plugin-defined config payload for anthropic-vertex.","hasChildren":false}
{"recordType":"path","path":"plugins.entries.anthropic-vertex.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/anthropic-vertex-provider","hasChildren":false}
{"recordType":"path","path":"plugins.entries.anthropic-vertex.hooks","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Plugin Hook Policy","help":"Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.","hasChildren":true}
{"recordType":"path","path":"plugins.entries.anthropic-vertex.hooks.allowPromptInjection","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Allow Prompt Injection Hooks","help":"Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.","hasChildren":false}
{"recordType":"path","path":"plugins.entries.anthropic-vertex.subagent","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Plugin Subagent Policy","help":"Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.","hasChildren":true}
{"recordType":"path","path":"plugins.entries.anthropic-vertex.subagent.allowedModels","kind":"plugin","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Plugin Subagent Allowed Models","help":"Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.","hasChildren":true}
{"recordType":"path","path":"plugins.entries.anthropic-vertex.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.anthropic-vertex.subagent.allowModelOverride","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Allow Plugin Subagent Model Override","help":"Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.","hasChildren":false}
{"recordType":"path","path":"plugins.entries.anthropic.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/anthropic-provider Config","help":"Plugin-defined config payload for anthropic.","hasChildren":false}
{"recordType":"path","path":"plugins.entries.anthropic.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/anthropic-provider","hasChildren":false}
{"recordType":"path","path":"plugins.entries.anthropic.hooks","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Plugin Hook Policy","help":"Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.","hasChildren":true}
@@ -4160,9 +4228,9 @@
{"recordType":"path","path":"plugins.entries.diffs.config.defaults.background","kind":"plugin","type":"boolean","required":false,"defaultValue":true,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Default Background Highlights","help":"Show added/removed background highlights by default.","hasChildren":false}
{"recordType":"path","path":"plugins.entries.diffs.config.defaults.diffIndicators","kind":"plugin","type":"string","required":false,"enumValues":["bars","classic","none"],"defaultValue":"bars","deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Diff Indicator Style","help":"Choose added/removed indicators style.","hasChildren":false}
{"recordType":"path","path":"plugins.entries.diffs.config.defaults.fileFormat","kind":"plugin","type":"string","required":false,"enumValues":["png","pdf"],"defaultValue":"png","deprecated":false,"sensitive":false,"tags":["storage"],"label":"Default File Format","help":"Rendered file format for file mode (PNG or PDF).","hasChildren":false}
{"recordType":"path","path":"plugins.entries.diffs.config.defaults.fileMaxWidth","kind":"plugin","type":"number","required":false,"defaultValue":960,"deprecated":false,"sensitive":false,"tags":["performance","storage"],"label":"Default File Max Width","help":"Maximum file render width in CSS pixels.","hasChildren":false}
{"recordType":"path","path":"plugins.entries.diffs.config.defaults.fileMaxWidth","kind":"plugin","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":["performance","storage"],"label":"Default File Max Width","help":"Maximum file render width in CSS pixels.","hasChildren":false}
{"recordType":"path","path":"plugins.entries.diffs.config.defaults.fileQuality","kind":"plugin","type":"string","required":false,"enumValues":["standard","hq","print"],"defaultValue":"standard","deprecated":false,"sensitive":false,"tags":["storage"],"label":"Default File Quality","help":"Quality preset for PNG/PDF rendering.","hasChildren":false}
{"recordType":"path","path":"plugins.entries.diffs.config.defaults.fileScale","kind":"plugin","type":"number","required":false,"defaultValue":2,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Default File Scale","help":"Device scale factor used while rendering file artifacts.","hasChildren":false}
{"recordType":"path","path":"plugins.entries.diffs.config.defaults.fileScale","kind":"plugin","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Default File Scale","help":"Device scale factor used while rendering file artifacts.","hasChildren":false}
{"recordType":"path","path":"plugins.entries.diffs.config.defaults.fontFamily","kind":"plugin","type":"string","required":false,"defaultValue":"Fira Code","deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Default Font","help":"Preferred font family name for diff content and headers.","hasChildren":false}
{"recordType":"path","path":"plugins.entries.diffs.config.defaults.fontSize","kind":"plugin","type":"number","required":false,"defaultValue":15,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Default Font Size","help":"Base diff font size in pixels.","hasChildren":false}
{"recordType":"path","path":"plugins.entries.diffs.config.defaults.format","kind":"plugin","type":"string","required":false,"enumValues":["png","pdf"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
@@ -4248,7 +4316,7 @@
{"recordType":"path","path":"plugins.entries.firecrawl.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/firecrawl-plugin Config","help":"Plugin-defined config payload for firecrawl.","hasChildren":true}
{"recordType":"path","path":"plugins.entries.firecrawl.config.webSearch","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
{"recordType":"path","path":"plugins.entries.firecrawl.config.webSearch.apiKey","kind":"plugin","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","security"],"label":"Firecrawl Search API Key","help":"Firecrawl API key for web search (fallback: FIRECRAWL_API_KEY env var).","hasChildren":false}
{"recordType":"path","path":"plugins.entries.firecrawl.config.webSearch.baseUrl","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Firecrawl Search Base URL","help":"Firecrawl Search base URL override.","hasChildren":false}
{"recordType":"path","path":"plugins.entries.firecrawl.config.webSearch.baseUrl","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced","url-secret"],"label":"Firecrawl Search Base URL","help":"Firecrawl Search base URL override.","hasChildren":false}
{"recordType":"path","path":"plugins.entries.firecrawl.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/firecrawl-plugin","hasChildren":false}
{"recordType":"path","path":"plugins.entries.firecrawl.hooks","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Plugin Hook Policy","help":"Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.","hasChildren":true}
{"recordType":"path","path":"plugins.entries.firecrawl.hooks.allowPromptInjection","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Allow Prompt Injection Hooks","help":"Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.","hasChildren":false}
@@ -4418,7 +4486,7 @@
{"recordType":"path","path":"plugins.entries.memory-lancedb.config.dbPath","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced","storage"],"label":"Database Path","hasChildren":false}
{"recordType":"path","path":"plugins.entries.memory-lancedb.config.embedding","kind":"plugin","type":"object","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
{"recordType":"path","path":"plugins.entries.memory-lancedb.config.embedding.apiKey","kind":"plugin","type":"string","required":true,"deprecated":false,"sensitive":true,"tags":["auth","security","storage"],"label":"OpenAI API Key","help":"API key for OpenAI embeddings (or use ${OPENAI_API_KEY})","hasChildren":false}
{"recordType":"path","path":"plugins.entries.memory-lancedb.config.embedding.baseUrl","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced","storage"],"label":"Base URL","help":"Base URL for compatible providers (e.g. http://localhost:11434/v1)","hasChildren":false}
{"recordType":"path","path":"plugins.entries.memory-lancedb.config.embedding.baseUrl","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced","storage","url-secret"],"label":"Base URL","help":"Base URL for compatible providers (e.g. http://localhost:11434/v1)","hasChildren":false}
{"recordType":"path","path":"plugins.entries.memory-lancedb.config.embedding.dimensions","kind":"plugin","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":["advanced","storage"],"label":"Dimensions","help":"Vector dimensions for custom models (required for non-standard models)","hasChildren":false}
{"recordType":"path","path":"plugins.entries.memory-lancedb.config.embedding.model","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["models","storage"],"label":"Embedding Model","help":"OpenAI embedding model to use","hasChildren":false}
{"recordType":"path","path":"plugins.entries.memory-lancedb.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Enable @openclaw/memory-lancedb","hasChildren":false}
@@ -4477,7 +4545,7 @@
{"recordType":"path","path":"plugins.entries.moonshot.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/moonshot-provider Config","help":"Plugin-defined config payload for moonshot.","hasChildren":true}
{"recordType":"path","path":"plugins.entries.moonshot.config.webSearch","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
{"recordType":"path","path":"plugins.entries.moonshot.config.webSearch.apiKey","kind":"plugin","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","security"],"label":"Kimi Search API Key","help":"Moonshot/Kimi API key (fallback: KIMI_API_KEY or MOONSHOT_API_KEY env var).","hasChildren":false}
{"recordType":"path","path":"plugins.entries.moonshot.config.webSearch.baseUrl","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Kimi Search Base URL","help":"Kimi base URL override.","hasChildren":false}
{"recordType":"path","path":"plugins.entries.moonshot.config.webSearch.baseUrl","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced","url-secret"],"label":"Kimi Search Base URL","help":"Kimi base URL override.","hasChildren":false}
{"recordType":"path","path":"plugins.entries.moonshot.config.webSearch.model","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["models"],"label":"Kimi Search Model","help":"Kimi model override.","hasChildren":false}
{"recordType":"path","path":"plugins.entries.moonshot.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/moonshot-provider","hasChildren":false}
{"recordType":"path","path":"plugins.entries.moonshot.hooks","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Plugin Hook Policy","help":"Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.","hasChildren":true}
@@ -4602,7 +4670,7 @@
{"recordType":"path","path":"plugins.entries.perplexity.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/perplexity-plugin Config","help":"Plugin-defined config payload for perplexity.","hasChildren":true}
{"recordType":"path","path":"plugins.entries.perplexity.config.webSearch","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
{"recordType":"path","path":"plugins.entries.perplexity.config.webSearch.apiKey","kind":"plugin","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","security"],"label":"Perplexity API Key","help":"Perplexity or OpenRouter API key for web search.","hasChildren":false}
{"recordType":"path","path":"plugins.entries.perplexity.config.webSearch.baseUrl","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Perplexity Base URL","help":"Optional Perplexity/OpenRouter chat-completions base URL override.","hasChildren":false}
{"recordType":"path","path":"plugins.entries.perplexity.config.webSearch.baseUrl","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced","url-secret"],"label":"Perplexity Base URL","help":"Optional Perplexity/OpenRouter chat-completions base URL override.","hasChildren":false}
{"recordType":"path","path":"plugins.entries.perplexity.config.webSearch.model","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["models"],"label":"Perplexity Model","help":"Optional Sonar/OpenRouter model override.","hasChildren":false}
{"recordType":"path","path":"plugins.entries.perplexity.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/perplexity-plugin","hasChildren":false}
{"recordType":"path","path":"plugins.entries.perplexity.hooks","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Plugin Hook Policy","help":"Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.","hasChildren":true}
@@ -4629,6 +4697,32 @@
{"recordType":"path","path":"plugins.entries.qianfan.subagent.allowedModels","kind":"plugin","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Plugin Subagent Allowed Models","help":"Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.","hasChildren":true}
{"recordType":"path","path":"plugins.entries.qianfan.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.qianfan.subagent.allowModelOverride","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Allow Plugin Subagent Model Override","help":"Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.","hasChildren":false}
{"recordType":"path","path":"plugins.entries.qqbot","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/qqbot","help":"OpenClaw QQ Bot channel plugin (plugin: qqbot)","hasChildren":true}
{"recordType":"path","path":"plugins.entries.qqbot.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/qqbot Config","help":"Plugin-defined config payload for qqbot.","hasChildren":true}
{"recordType":"path","path":"plugins.entries.qqbot.config.accounts","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
{"recordType":"path","path":"plugins.entries.qqbot.config.accounts.*","kind":"plugin","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.qqbot.config.allowFrom","kind":"plugin","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
{"recordType":"path","path":"plugins.entries.qqbot.config.allowFrom.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.qqbot.config.appId","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.qqbot.config.audioFormatPolicy","kind":"plugin","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.qqbot.config.clientSecret","kind":"plugin","required":false,"deprecated":false,"sensitive":true,"tags":["auth","security"],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.qqbot.config.clientSecretFile","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":true,"tags":["auth","security","storage"],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.qqbot.config.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.qqbot.config.markdownSupport","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.qqbot.config.name","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.qqbot.config.systemPrompt","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.qqbot.config.upgradeMode","kind":"plugin","type":"string","required":false,"enumValues":["doc","hot-reload"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.qqbot.config.upgradeUrl","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.qqbot.config.urlDirectUpload","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.qqbot.config.voiceDirectUploadFormats","kind":"plugin","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
{"recordType":"path","path":"plugins.entries.qqbot.config.voiceDirectUploadFormats.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.qqbot.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/qqbot","hasChildren":false}
{"recordType":"path","path":"plugins.entries.qqbot.hooks","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Plugin Hook Policy","help":"Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.","hasChildren":true}
{"recordType":"path","path":"plugins.entries.qqbot.hooks.allowPromptInjection","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Allow Prompt Injection Hooks","help":"Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.","hasChildren":false}
{"recordType":"path","path":"plugins.entries.qqbot.subagent","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Plugin Subagent Policy","help":"Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.","hasChildren":true}
{"recordType":"path","path":"plugins.entries.qqbot.subagent.allowedModels","kind":"plugin","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Plugin Subagent Allowed Models","help":"Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.","hasChildren":true}
{"recordType":"path","path":"plugins.entries.qqbot.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.qqbot.subagent.allowModelOverride","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Allow Plugin Subagent Model Override","help":"Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.","hasChildren":false}
{"recordType":"path","path":"plugins.entries.sglang","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/sglang-provider","help":"OpenClaw SGLang provider plugin (plugin: sglang)","hasChildren":true}
{"recordType":"path","path":"plugins.entries.sglang.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/sglang-provider Config","help":"Plugin-defined config payload for sglang.","hasChildren":false}
{"recordType":"path","path":"plugins.entries.sglang.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/sglang-provider","hasChildren":false}
@@ -4687,7 +4781,7 @@
{"recordType":"path","path":"plugins.entries.tavily.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/tavily-plugin Config","help":"Plugin-defined config payload for tavily.","hasChildren":true}
{"recordType":"path","path":"plugins.entries.tavily.config.webSearch","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
{"recordType":"path","path":"plugins.entries.tavily.config.webSearch.apiKey","kind":"plugin","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","security"],"label":"Tavily API Key","help":"Tavily API key for web search and extraction (fallback: TAVILY_API_KEY env var).","hasChildren":false}
{"recordType":"path","path":"plugins.entries.tavily.config.webSearch.baseUrl","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Tavily Base URL","help":"Tavily API base URL override.","hasChildren":false}
{"recordType":"path","path":"plugins.entries.tavily.config.webSearch.baseUrl","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced","url-secret"],"label":"Tavily Base URL","help":"Tavily API base URL override.","hasChildren":false}
{"recordType":"path","path":"plugins.entries.tavily.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/tavily-plugin","hasChildren":false}
{"recordType":"path","path":"plugins.entries.tavily.hooks","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Plugin Hook Policy","help":"Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.","hasChildren":true}
{"recordType":"path","path":"plugins.entries.tavily.hooks.allowPromptInjection","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Allow Prompt Injection Hooks","help":"Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.","hasChildren":false}
@@ -4826,31 +4920,6 @@
{"recordType":"path","path":"plugins.entries.voice-call.config.transcriptTimeoutMs","kind":"plugin","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.auto","kind":"plugin","type":"string","required":false,"enumValues":["off","always","inbound","tagged"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.edge","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.edge.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.edge.lang","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.edge.outputFormat","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.edge.pitch","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.edge.proxy","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.edge.rate","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.edge.saveSubtitles","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.edge.timeoutMs","kind":"plugin","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.edge.voice","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.edge.volume","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.elevenlabs","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.elevenlabs.apiKey","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":true,"tags":["advanced","auth","media","security"],"label":"ElevenLabs API Key","hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.elevenlabs.applyTextNormalization","kind":"plugin","type":"string","required":false,"enumValues":["auto","on","off"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.elevenlabs.baseUrl","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced","media"],"label":"ElevenLabs Base URL","hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.elevenlabs.languageCode","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.elevenlabs.modelId","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced","media","models"],"label":"ElevenLabs Model ID","hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.elevenlabs.seed","kind":"plugin","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.elevenlabs.voiceId","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced","media"],"label":"ElevenLabs Voice ID","hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.elevenlabs.voiceSettings","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.elevenlabs.voiceSettings.similarityBoost","kind":"plugin","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.elevenlabs.voiceSettings.speed","kind":"plugin","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.elevenlabs.voiceSettings.stability","kind":"plugin","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.elevenlabs.voiceSettings.style","kind":"plugin","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.elevenlabs.voiceSettings.useSpeakerBoost","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.maxTextLength","kind":"plugin","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.mode","kind":"plugin","type":"string","required":false,"enumValues":["final","all"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
@@ -4863,15 +4932,54 @@
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.modelOverrides.allowVoice","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.modelOverrides.allowVoiceSettings","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.modelOverrides.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.openai","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.openai.apiKey","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":true,"tags":["advanced","auth","media","security"],"label":"OpenAI API Key","hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.openai.baseUrl","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.openai.instructions","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.openai.model","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced","media","models"],"label":"OpenAI TTS Model","hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.openai.speed","kind":"plugin","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.openai.voice","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced","media"],"label":"OpenAI TTS Voice","hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.prefsPath","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.provider","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced","media"],"label":"TTS Provider Override","help":"Deep-merges with messages.tts (Microsoft is ignored for calls).","hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.*","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.*.apiKey","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":true,"tags":["auth","media","security"],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.edge","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.edge.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.edge.lang","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.edge.outputFormat","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.edge.pitch","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.edge.proxy","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.edge.rate","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.edge.saveSubtitles","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.edge.timeoutMs","kind":"plugin","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.edge.voice","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.edge.volume","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.elevenlabs","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.elevenlabs.apiKey","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":true,"tags":["advanced","auth","media","security"],"label":"ElevenLabs API Key","hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.elevenlabs.applyTextNormalization","kind":"plugin","type":"string","required":false,"enumValues":["auto","on","off"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.elevenlabs.baseUrl","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced","media","url-secret"],"label":"ElevenLabs Base URL","hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.elevenlabs.languageCode","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.elevenlabs.modelId","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced","media","models"],"label":"ElevenLabs Model ID","hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.elevenlabs.seed","kind":"plugin","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.elevenlabs.voiceId","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced","media"],"label":"ElevenLabs Voice ID","hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.elevenlabs.voiceSettings","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.elevenlabs.voiceSettings.similarityBoost","kind":"plugin","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.elevenlabs.voiceSettings.speed","kind":"plugin","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.elevenlabs.voiceSettings.stability","kind":"plugin","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.elevenlabs.voiceSettings.style","kind":"plugin","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.elevenlabs.voiceSettings.useSpeakerBoost","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.microsoft","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.microsoft.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.microsoft.lang","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.microsoft.outputFormat","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.microsoft.pitch","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.microsoft.proxy","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.microsoft.rate","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.microsoft.saveSubtitles","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.microsoft.timeoutMs","kind":"plugin","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.microsoft.voice","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.microsoft.volume","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.openai","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.openai.apiKey","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":true,"tags":["advanced","auth","media","security"],"label":"OpenAI API Key","hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.openai.baseUrl","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["media","url-secret"],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.openai.instructions","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.openai.model","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced","media","models"],"label":"OpenAI TTS Model","hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.openai.speed","kind":"plugin","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.openai.voice","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced","media"],"label":"OpenAI TTS Voice","hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.summaryModel","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.timeoutMs","kind":"plugin","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.voice-call.config.tunnel","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
@@ -4974,7 +5082,7 @@
{"recordType":"path","path":"plugins.installs.*.clawhubPackage","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.installs.*.clawhubUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.installs.*.installedAt","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Plugin Install Time","help":"ISO timestamp of last install/update.","hasChildren":false}
{"recordType":"path","path":"plugins.installs.*.installPath","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Plugin Install Path","help":"Resolved install directory (usually ~/.openclaw/extensions/<id>).","hasChildren":false}
{"recordType":"path","path":"plugins.installs.*.installPath","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Plugin Install Path","help":"Resolved install directory for the installed plugin bundle.","hasChildren":false}
{"recordType":"path","path":"plugins.installs.*.integrity","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Plugin Resolved Integrity","help":"Resolved npm dist integrity hash for the fetched artifact (if reported by npm).","hasChildren":false}
{"recordType":"path","path":"plugins.installs.*.marketplaceName","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Plugin Marketplace Name","help":"Marketplace display name recorded for marketplace-backed plugin installs (if available).","hasChildren":false}
{"recordType":"path","path":"plugins.installs.*.marketplacePlugin","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Plugin Marketplace Plugin","help":"Plugin entry name inside the source marketplace, used for later updates.","hasChildren":false}
@@ -5178,7 +5286,7 @@
{"recordType":"path","path":"tools.exec.ask","kind":"core","type":"string","required":false,"enumValues":["off","on-miss","always"],"deprecated":false,"sensitive":false,"tags":["tools"],"label":"Exec Ask","help":"Approval strategy for when exec commands require human confirmation before running. Use stricter ask behavior in shared channels and lower-friction settings in private operator contexts.","hasChildren":false}
{"recordType":"path","path":"tools.exec.backgroundMs","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"tools.exec.cleanupMs","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"tools.exec.host","kind":"core","type":"string","required":false,"enumValues":["sandbox","gateway","node"],"deprecated":false,"sensitive":false,"tags":["tools"],"label":"Exec Host","help":"Selects execution host strategy for shell commands, typically controlling local vs delegated execution environment. Use the safest host mode that still satisfies your automation requirements.","hasChildren":false}
{"recordType":"path","path":"tools.exec.host","kind":"core","type":"string","required":false,"enumValues":["auto","sandbox","gateway","node"],"deprecated":false,"sensitive":false,"tags":["tools"],"label":"Exec Target","help":"Selects execution target strategy for shell commands. Use \"auto\" for runtime-aware behavior (sandbox when available, otherwise gateway), or pin sandbox/gateway/node explicitly when you need a fixed surface.","hasChildren":false}
{"recordType":"path","path":"tools.exec.node","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["tools"],"label":"Exec Node Binding","help":"Node binding configuration for exec tooling when command execution is delegated through connected nodes. Use explicit node binding only when multi-node routing is required.","hasChildren":false}
{"recordType":"path","path":"tools.exec.notifyOnExit","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["tools"],"label":"Exec Notify On Exit","help":"When true (default), backgrounded exec sessions on exit and node exec lifecycle events enqueue a system event and request a heartbeat.","hasChildren":false}
{"recordType":"path","path":"tools.exec.notifyOnExitEmptySuccess","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["tools"],"label":"Exec Notify On Empty Success","help":"When true, successful backgrounded exec exits with empty output still enqueue a completion system event (default: false).","hasChildren":false}
@@ -5238,7 +5346,7 @@
{"recordType":"path","path":"tools.media.audio.attachments.maxAttachments","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"tools.media.audio.attachments.mode","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"tools.media.audio.attachments.prefer","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"tools.media.audio.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"tools.media.audio.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["media","tools","url-secret"],"hasChildren":false}
{"recordType":"path","path":"tools.media.audio.deepgram","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
{"recordType":"path","path":"tools.media.audio.deepgram.detectLanguage","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"tools.media.audio.deepgram.punctuate","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
@@ -5255,7 +5363,7 @@
{"recordType":"path","path":"tools.media.audio.models.*","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
{"recordType":"path","path":"tools.media.audio.models.*.args","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
{"recordType":"path","path":"tools.media.audio.models.*.args.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"tools.media.audio.models.*.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"tools.media.audio.models.*.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["media","tools","url-secret"],"hasChildren":false}
{"recordType":"path","path":"tools.media.audio.models.*.capabilities","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
{"recordType":"path","path":"tools.media.audio.models.*.capabilities.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"tools.media.audio.models.*.command","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
@@ -5299,7 +5407,7 @@
{"recordType":"path","path":"tools.media.image.attachments.maxAttachments","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"tools.media.image.attachments.mode","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"tools.media.image.attachments.prefer","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"tools.media.image.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"tools.media.image.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["media","tools","url-secret"],"hasChildren":false}
{"recordType":"path","path":"tools.media.image.deepgram","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
{"recordType":"path","path":"tools.media.image.deepgram.detectLanguage","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"tools.media.image.deepgram.punctuate","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
@@ -5316,7 +5424,7 @@
{"recordType":"path","path":"tools.media.image.models.*","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
{"recordType":"path","path":"tools.media.image.models.*.args","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
{"recordType":"path","path":"tools.media.image.models.*.args.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"tools.media.image.models.*.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"tools.media.image.models.*.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["media","tools","url-secret"],"hasChildren":false}
{"recordType":"path","path":"tools.media.image.models.*.capabilities","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
{"recordType":"path","path":"tools.media.image.models.*.capabilities.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"tools.media.image.models.*.command","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
@@ -5358,7 +5466,7 @@
{"recordType":"path","path":"tools.media.models.*","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
{"recordType":"path","path":"tools.media.models.*.args","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
{"recordType":"path","path":"tools.media.models.*.args.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"tools.media.models.*.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"tools.media.models.*.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["media","tools","url-secret"],"hasChildren":false}
{"recordType":"path","path":"tools.media.models.*.capabilities","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
{"recordType":"path","path":"tools.media.models.*.capabilities.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"tools.media.models.*.command","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
@@ -5386,7 +5494,7 @@
{"recordType":"path","path":"tools.media.video.attachments.maxAttachments","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"tools.media.video.attachments.mode","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"tools.media.video.attachments.prefer","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"tools.media.video.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"tools.media.video.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["media","tools","url-secret"],"hasChildren":false}
{"recordType":"path","path":"tools.media.video.deepgram","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
{"recordType":"path","path":"tools.media.video.deepgram.detectLanguage","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"tools.media.video.deepgram.punctuate","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
@@ -5403,7 +5511,7 @@
{"recordType":"path","path":"tools.media.video.models.*","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
{"recordType":"path","path":"tools.media.video.models.*.args","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
{"recordType":"path","path":"tools.media.video.models.*.args.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"tools.media.video.models.*.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"tools.media.video.models.*.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["media","tools","url-secret"],"hasChildren":false}
{"recordType":"path","path":"tools.media.video.models.*.capabilities","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
{"recordType":"path","path":"tools.media.video.models.*.capabilities.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"tools.media.video.models.*.command","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
@@ -5487,7 +5595,7 @@
{"recordType":"path","path":"tools.web.fetch.firecrawl.apiKey.id","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"tools.web.fetch.firecrawl.apiKey.provider","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"tools.web.fetch.firecrawl.apiKey.source","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"tools.web.fetch.firecrawl.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["tools"],"label":"Firecrawl Base URL","help":"Firecrawl base URL (e.g. https://api.firecrawl.dev or custom endpoint).","hasChildren":false}
{"recordType":"path","path":"tools.web.fetch.firecrawl.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["tools","url-secret"],"label":"Firecrawl Base URL","help":"Firecrawl base URL (e.g. https://api.firecrawl.dev or custom endpoint).","hasChildren":false}
{"recordType":"path","path":"tools.web.fetch.firecrawl.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["tools"],"label":"Enable Firecrawl Fallback","help":"Enable Firecrawl fallback for web_fetch (if configured).","hasChildren":false}
{"recordType":"path","path":"tools.web.fetch.firecrawl.maxAgeMs","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["performance","tools"],"label":"Firecrawl Cache Max Age (ms)","help":"Firecrawl maxAge (ms) for cached results when supported by the API.","hasChildren":false}
{"recordType":"path","path":"tools.web.fetch.firecrawl.onlyMainContent","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["tools"],"label":"Firecrawl Main Content Only","help":"When true, Firecrawl returns only the main content (default: true).","hasChildren":false}
@@ -5509,7 +5617,7 @@
{"recordType":"path","path":"tools.web.search.brave.apiKey.id","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"tools.web.search.brave.apiKey.provider","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"tools.web.search.brave.apiKey.source","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"tools.web.search.brave.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"tools.web.search.brave.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["tools","url-secret"],"hasChildren":false}
{"recordType":"path","path":"tools.web.search.brave.mode","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"tools.web.search.brave.model","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"tools.web.search.cacheTtlMinutes","kind":"core","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":["performance","storage","tools"],"label":"Web Search Cache TTL (min)","help":"Cache TTL in minutes for web_search results.","hasChildren":false}
@@ -5519,21 +5627,21 @@
{"recordType":"path","path":"tools.web.search.firecrawl.apiKey.id","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"tools.web.search.firecrawl.apiKey.provider","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"tools.web.search.firecrawl.apiKey.source","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"tools.web.search.firecrawl.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"tools.web.search.firecrawl.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["tools","url-secret"],"hasChildren":false}
{"recordType":"path","path":"tools.web.search.firecrawl.model","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"tools.web.search.gemini","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
{"recordType":"path","path":"tools.web.search.gemini.apiKey","kind":"core","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","security","tools"],"hasChildren":true}
{"recordType":"path","path":"tools.web.search.gemini.apiKey.id","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"tools.web.search.gemini.apiKey.provider","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"tools.web.search.gemini.apiKey.source","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"tools.web.search.gemini.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"tools.web.search.gemini.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["tools","url-secret"],"hasChildren":false}
{"recordType":"path","path":"tools.web.search.gemini.model","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"tools.web.search.grok","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
{"recordType":"path","path":"tools.web.search.grok.apiKey","kind":"core","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","security","tools"],"hasChildren":true}
{"recordType":"path","path":"tools.web.search.grok.apiKey.id","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"tools.web.search.grok.apiKey.provider","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"tools.web.search.grok.apiKey.source","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"tools.web.search.grok.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"tools.web.search.grok.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["tools","url-secret"],"hasChildren":false}
{"recordType":"path","path":"tools.web.search.grok.inlineCitations","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"tools.web.search.grok.model","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"tools.web.search.kimi","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
@@ -5541,7 +5649,7 @@
{"recordType":"path","path":"tools.web.search.kimi.apiKey.id","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"tools.web.search.kimi.apiKey.provider","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"tools.web.search.kimi.apiKey.source","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"tools.web.search.kimi.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"tools.web.search.kimi.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["tools","url-secret"],"hasChildren":false}
{"recordType":"path","path":"tools.web.search.kimi.model","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"tools.web.search.maxResults","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["performance","tools"],"label":"Web Search Max Results","help":"Number of results to return (1-10).","hasChildren":false}
{"recordType":"path","path":"tools.web.search.perplexity","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
@@ -5549,7 +5657,7 @@
{"recordType":"path","path":"tools.web.search.perplexity.apiKey.id","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"tools.web.search.perplexity.apiKey.provider","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"tools.web.search.perplexity.apiKey.source","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"tools.web.search.perplexity.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"tools.web.search.perplexity.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["tools","url-secret"],"hasChildren":false}
{"recordType":"path","path":"tools.web.search.perplexity.model","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"tools.web.search.provider","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["tools"],"label":"Web Search Provider","help":"Search provider id. Auto-detected from available API keys if omitted.","hasChildren":false}
{"recordType":"path","path":"tools.web.search.timeoutSeconds","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["performance","tools"],"label":"Web Search Timeout (sec)","help":"Timeout in seconds for web_search requests.","hasChildren":false}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

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:

109
docs/automation/clawflow.md Normal file
View File

@@ -0,0 +1,109 @@
---
summary: "ClawFlow workflow orchestration for background tasks and detached runs"
read_when:
- You want a flow to own one or more detached tasks
- You want to inspect or cancel a background job as a unit
- You want to understand how flows relate to tasks and background work
title: "ClawFlow"
---
# ClawFlow
ClawFlow is the flow layer above [Background Tasks](/automation/tasks). Tasks still track detached work. ClawFlow groups those task runs into a single job, keeps the parent owner context, and gives you a flow-level control surface.
Use ClawFlow when the work is more than a single detached run. A flow can still be one task, but it can also coordinate multiple tasks in a simple linear sequence.
## TL;DR
- Tasks are the execution records.
- ClawFlow is the job-level wrapper above tasks.
- A flow keeps one owner/session context for the whole job.
- Use `openclaw flows list`, `openclaw flows show`, and `openclaw flows cancel` to inspect or manage flows.
## Quick start
```bash
openclaw flows list
openclaw flows show <flow-id-or-owner-session>
openclaw flows cancel <flow-id-or-owner-session>
```
## How it relates to tasks
Background tasks still do the low-level work:
- ACP runs
- subagent runs
- cron executions
- CLI-initiated runs
ClawFlow sits above that ledger:
- it keeps related task runs under one flow id
- it tracks the flow state separately from the individual task state
- it makes blocked or multi-step work easier to inspect from one place
For a single detached run, the flow can be a one-task flow. For more structured work, ClawFlow can keep multiple task runs under the same job.
## Runtime substrate
ClawFlow is the runtime substrate, not a workflow language.
It owns:
- the flow id
- the owner session and return context
- waiting state
- small persisted outputs
- finish, fail, cancel, and blocked state
It does **not** own branching or business logic. Put that in the authoring layer that sits above it:
- Lobster
- acpx
- plain TypeScript helpers
- bundled skills
In practice, authoring layers target a small runtime surface:
- `createFlow(...)`
- `runTaskInFlow(...)`
- `setFlowWaiting(...)`
- `setFlowOutput(...)`
- `appendFlowOutput(...)`
- `emitFlowUpdate(...)`
- `resumeFlow(...)`
- `finishFlow(...)`
- `failFlow(...)`
That keeps flow ownership and return-to-thread behavior in core without forcing a single DSL on top of it.
## Authoring pattern
The intended shape is linear:
1. Create one flow for the job.
2. Run one detached task under that flow.
3. Wait for the child task or outside event.
4. Resume the flow in the caller.
5. Spawn the next child task or finish.
ClawFlow persists the minimal state needed to resume that job: the current step, the task it is waiting on, and a small output bag for handoff between steps.
## CLI surface
The flow CLI is intentionally small:
- `openclaw flows list` shows active and recent flows
- `openclaw flows show <lookup>` shows one flow and its linked tasks
- `openclaw flows cancel <lookup>` cancels the flow and any active child tasks
`flows show` also surfaces the current wait target and any stored output keys, which is often enough to answer "what is this job waiting on?" without digging into every child task.
The lookup token accepts either a flow id or the owner session key.
## Related
- [Background Tasks](/automation/tasks) — detached work ledger
- [CLI: flows](/cli/flows) — flow inspection and control commands
- [Cron Jobs](/automation/cron-jobs) — scheduled jobs that may create tasks

View File

@@ -204,6 +204,7 @@ 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.threadId`: optional explicit thread or topic id when the target channel supports threaded delivery.
- `delivery.bestEffort`: avoid failing the job if announce delivery fails.
Announce delivery suppresses messaging tool sends for the run; use `delivery.channel`/`delivery.to`
@@ -270,8 +271,9 @@ Isolated jobs (`agentTurn`) can set `lightContext: true` to run with lightweight
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.channel`: `last` or any deliverable channel id, for example `discord`, `matrix`, `telegram`, or `whatsapp`.
- `delivery.to`: channel-specific recipient target.
- `delivery.threadId`: optional thread/topic override for channels like Telegram, Slack, Discord, or Matrix when you want a specific thread without encoding it into `delivery.to`.
`announce` delivery is only valid for isolated jobs (`sessionTarget: "isolated"`).
`webhook` delivery is valid for both main and isolated jobs.

View File

@@ -525,6 +525,11 @@ If the gateway is unavailable or does not support plugin approvals, the tool cal
Runs after the built-in install security scan and before installation continues. OpenClaw fires this hook for interactive skill installs as well as plugin bundle, package, and single-file installs.
Default behavior differs by target type:
- Plugin installs fail closed on built-in scan `critical` findings and scan errors unless the operator explicitly uses `openclaw plugins install --dangerously-force-unsafe-install`.
- Skill installs still surface built-in scan findings and scan errors as warnings and continue by default.
Return fields:
- **`findings`**: Additional scan findings to surface as warnings

View File

@@ -13,46 +13,34 @@ OpenClaw provides several automation mechanisms, each suited to different use ca
## Quick decision guide
```
Do you need something to run on a schedule?
YES → Is exact timing critical?
YES → Cron (isolated)
NO → Can it batch with other checks?
YES → Heartbeat
NO → Cron
NO → Continue...
Do you need to react to an event (message, tool call, session change)?
YES → Hooks (or plugin hooks)
Do you need to receive external HTTP events?
YES → Webhooks
Do you want persistent instructions the agent always follows?
YES → Standing Orders
Do you want to track what background work happened?
→ Background Tasks (automatic for cron, ACP, subagents)
```mermaid
flowchart TD
A{Run on a schedule?} -->|Yes| B{Exact timing needed?}
A -->|No| C{React to events?}
B -->|Yes| D[Cron]
B -->|No| E[Heartbeat]
C -->|Yes| F[Hooks]
C -->|No| G[Standing Orders]
```
## Mechanisms at a glance
| Mechanism | What it does | Runs in | Creates task record |
|---|---|---|---|
| [Heartbeat](/gateway/heartbeat) | Periodic main-session turn — batches multiple checks | Main session | No |
| [Cron](/automation/cron-jobs) | Scheduled jobs with precise timing | Main or isolated session | Yes (all types) |
| [Background Tasks](/automation/tasks) | Tracks detached work (cron, ACP, subagents, CLI) | N/A (ledger) | N/A |
| [Hooks](/automation/hooks) | Event-driven scripts triggered by agent lifecycle events | Hook runner | No |
| [Standing Orders](/automation/standing-orders) | Persistent instructions injected into the system prompt | Main session | No |
| [Webhooks](/automation/webhook) | Receive inbound HTTP events and route to the agent | Gateway HTTP | No |
| Mechanism | What it does | Runs in | Creates task record |
| ---------------------------------------------- | -------------------------------------------------------- | ------------------------ | ------------------- |
| [Heartbeat](/gateway/heartbeat) | Periodic main-session turn — batches multiple checks | Main session | No |
| [Cron](/automation/cron-jobs) | Scheduled jobs with precise timing | Main or isolated session | Yes (all types) |
| [Background Tasks](/automation/tasks) | Tracks detached work (cron, ACP, subagents, CLI) | N/A (ledger) | N/A |
| [Hooks](/automation/hooks) | Event-driven scripts triggered by agent lifecycle events | Hook runner | No |
| [Standing Orders](/automation/standing-orders) | Persistent instructions injected into the system prompt | Main session | No |
| [Webhooks](/automation/webhook) | Receive inbound HTTP events and route to the agent | Gateway HTTP | No |
### Specialized automation
| Mechanism | What it does |
|---|---|
| [Gmail PubSub](/automation/gmail-pubsub) | Real-time Gmail notifications via Google PubSub |
| [Polling](/automation/poll) | Periodic data source checks (RSS, APIs, etc.) |
| [Auth Monitoring](/automation/auth-monitoring) | Credential health and expiry alerts |
| Mechanism | What it does |
| ---------------------------------------------- | ----------------------------------------------- |
| [Gmail PubSub](/automation/gmail-pubsub) | Real-time Gmail notifications via Google PubSub |
| [Polling](/automation/poll) | Periodic data source checks (RSS, APIs, etc.) |
| [Auth Monitoring](/automation/auth-monitoring) | Credential health and expiry alerts |
## How they work together
@@ -63,11 +51,19 @@ The most effective setups combine multiple mechanisms:
3. **Hooks** react to specific events (tool calls, session resets, compaction) with custom scripts.
4. **Standing Orders** give the agent persistent context ("always check the project board before replying").
5. **Background Tasks** automatically track all detached work so you can inspect and audit it.
6. **ClawFlow** groups related detached tasks into a single flow when the work needs a higher-level job view.
See [Cron vs Heartbeat](/automation/cron-vs-heartbeat) for a detailed comparison of the two scheduling mechanisms.
## ClawFlow
ClawFlow sits above [Background Tasks](/automation/tasks). Tasks still track the detached runs, while ClawFlow groups related task runs into one job that you can inspect or cancel from the CLI.
See [ClawFlow](/automation/clawflow) for the flow overview and [CLI: flows](/cli/flows) for the command surface.
## Related
- [Cron vs Heartbeat](/automation/cron-vs-heartbeat) — detailed comparison guide
- [ClawFlow](/automation/clawflow) — flow-level orchestration above tasks
- [Troubleshooting](/automation/troubleshooting) — debugging automation issues
- [Configuration Reference](/gateway/configuration-reference) — all config keys

View File

@@ -210,6 +210,12 @@ A sweeper runs every **60 seconds** and handles three things:
## How tasks relate to other systems
### Tasks and ClawFlow
ClawFlow is the flow layer above tasks. A flow groups one or more task runs into a single job, owns the parent session context, and gives you a higher-level control surface for blocked or multi-step work.
See [ClawFlow](/automation/clawflow) for the flow overview and [CLI: flows](/cli/flows) for the command surface.
### Tasks and cron
A cron job **definition** lives in `~/.openclaw/cron/jobs.json`. **Every** cron execution creates a task record — both main-session and isolated. Main-session cron tasks default to `silent` notify policy so they track without generating notifications.
@@ -233,7 +239,9 @@ A task's `runId` links to the agent run doing the work. Agent lifecycle events (
## Related
- [Automation Overview](/automation) — all automation mechanisms at a glance
- [ClawFlow](/automation/clawflow) — job-level orchestration above tasks
- [Cron Jobs](/automation/cron-jobs) — scheduling background work
- [Cron vs Heartbeat](/automation/cron-vs-heartbeat) — choosing the right mechanism
- [Heartbeat](/gateway/heartbeat) — periodic main-session turns
- [CLI: flows](/cli/flows) — flow inspection and control commands
- [CLI: Tasks](/cli/index#tasks) — CLI command reference

View File

@@ -84,7 +84,7 @@ Payload:
- `sessionKey` optional (string): The key used to identify the agent's session. By default this field is rejected unless `hooks.allowRequestSessionKey=true`.
- `wakeMode` optional (`now` | `next-heartbeat`): Whether to trigger an immediate heartbeat (default `now`) or wait for the next periodic check.
- `deliver` optional (boolean): If `true`, the agent's response will be sent to the messaging channel. Defaults to `true`. Responses that are only heartbeat acknowledgments are automatically skipped.
- `channel` optional (string): The messaging channel for delivery. Core channels: `last`, `whatsapp`, `telegram`, `discord`, `slack`, `signal`, `imessage`, `irc`, `googlechat`, `line`. Extension channels (plugins): `msteams`, `mattermost`, and others. Defaults to `last`.
- `channel` optional (string): The messaging channel for delivery. Use `last` or any configured channel or plugin id, for example `discord`, `matrix`, `telegram`, or `whatsapp`. Defaults to `last`.
- `to` optional (string): The recipient identifier for the channel (e.g., phone number for WhatsApp/Signal, chat ID for Telegram, channel ID for Discord/Slack/Mattermost (plugin), conversation ID for Microsoft Teams). Defaults to the last recipient in the main session.
- `model` optional (string): Model override (e.g., `anthropic/claude-sonnet-4-6` or an alias). Must be in the allowed model list if restricted.
- `thinking` optional (string): Thinking level override (e.g., `low`, `medium`, `high`).

View File

@@ -25,6 +25,7 @@ Text is supported everywhere; media and reactions vary by channel.
- [Microsoft Teams](/channels/msteams) — Bot Framework; enterprise support (plugin, installed separately).
- [Nextcloud Talk](/channels/nextcloud-talk) — Self-hosted chat via Nextcloud Talk (plugin, installed separately).
- [Nostr](/channels/nostr) — Decentralized DMs via NIP-04 (plugin, installed separately).
- [QQ Bot](/channels/qqbot) — QQ Bot API; private chat, group chat, and rich media (plugin, installed separately).
- [Signal](/channels/signal) — signal-cli; privacy-focused.
- [Slack](/channels/slack) — Bolt SDK; workspace apps.
- [Synology Chat](/channels/synology-chat) — Synology NAS Chat via outgoing+incoming webhooks (plugin, installed separately).

View File

@@ -143,6 +143,7 @@ This is a practical baseline config with DM pairing, room allowlist, and E2EE en
dm: {
policy: "pairing",
threadReplies: "off",
},
groupPolicy: "allowlist",
@@ -501,9 +502,10 @@ The repair flow does not delete old rooms automatically. It only picks the healt
Matrix supports native Matrix threads for both automatic replies and message-tool sends.
- `threadReplies: "off"` keeps replies top-level.
- `threadReplies: "off"` keeps replies top-level and keeps inbound threaded messages on the parent session.
- `threadReplies: "inbound"` replies inside a thread only when the inbound message was already in that thread.
- `threadReplies: "always"` keeps room replies in a thread rooted at the triggering message.
- `threadReplies: "always"` keeps room replies in a thread rooted at the triggering message and routes that conversation through the matching thread-scoped session from the first triggering message.
- `dm.threadReplies` overrides the top-level setting for DMs only. For example, you can keep room threads isolated while keeping DMs flat.
- Inbound threaded messages include the thread root message as extra agent context.
- Message-tool sends now auto-inherit the current Matrix thread when the target is the same room, or the same DM user target, unless an explicit `threadId` is provided.
- Runtime thread bindings are supported for Matrix. `/focus`, `/unfocus`, `/agents`, `/session idle`, `/session max-age`, and thread-bound `/acp spawn` now work in Matrix rooms and DMs.
@@ -577,6 +579,15 @@ Current behavior:
- `reactionNotifications: "off"` disables reaction system events.
- Reaction removals are still not synthesized into system events because Matrix surfaces those as redactions, not as standalone `m.reaction` removals.
## History context
- `channels.matrix.historyLimit` controls how many recent room messages are included as `InboundHistory` when a Matrix room message triggers the agent.
- It falls back to `messages.groupChat.historyLimit`. Set `0` to disable.
- Matrix room history is room-only. DMs keep using normal session history.
- Matrix room history is pending-only: OpenClaw buffers room messages that did not trigger a reply yet, then snapshots that window when a mention or other trigger arrives.
- The current trigger message is not included in `InboundHistory`; it stays in the main inbound body for that turn.
- Retries of the same Matrix event reuse the original history snapshot instead of drifting forward to newer room messages.
## DM and room policy example
```json5
@@ -586,6 +597,7 @@ Current behavior:
dm: {
policy: "allowlist",
allowFrom: ["@admin:example.org"],
threadReplies: "off",
},
groupPolicy: "allowlist",
groupAllowFrom: ["@admin:example.org"],
@@ -633,6 +645,7 @@ See [Pairing](/channels/pairing) for the shared DM pairing flow and storage layo
dm: {
policy: "allowlist",
allowFrom: ["@ops:example.org"],
threadReplies: "off",
},
},
},
@@ -732,6 +745,7 @@ Live directory lookup uses the logged-in Matrix account:
- `groupPolicy`: `open`, `allowlist`, or `disabled`.
- `groupAllowFrom`: allowlist of user IDs for room traffic.
- `groupAllowFrom` entries should be full Matrix user IDs. Unresolved names are ignored at runtime.
- `historyLimit`: max room messages to include as group history context. Falls back to `messages.groupChat.historyLimit`. Set `0` to disable.
- `replyToMode`: `off`, `first`, or `all`.
- `streaming`: `off` (default) or `partial`. `partial` enables single-message draft previews with edit-in-place updates.
- `threadReplies`: `off`, `inbound`, or `always`.
@@ -747,8 +761,9 @@ Live directory lookup uses the logged-in Matrix account:
- `mediaMaxMb`: media size cap in MB for Matrix media handling. It applies to outbound sends and inbound media processing.
- `autoJoin`: invite auto-join policy (`always`, `allowlist`, `off`). Default: `off`.
- `autoJoinAllowlist`: rooms/aliases allowed when `autoJoin` is `allowlist`. Alias entries are resolved to room IDs during invite handling; OpenClaw does not trust alias state claimed by the invited room.
- `dm`: DM policy block (`enabled`, `policy`, `allowFrom`).
- `dm`: DM policy block (`enabled`, `policy`, `allowFrom`, `threadReplies`).
- `dm.allowFrom` entries should be full Matrix user IDs unless you already resolved them through live directory lookup.
- `dm.threadReplies`: DM-only thread policy override (`off`, `inbound`, `always`). It overrides the top-level `threadReplies` setting for both reply placement and session isolation in DMs.
- `accounts`: named per-account overrides. Top-level `channels.matrix` values act as defaults for these entries.
- `groups`: per-room policy map. Prefer room IDs or aliases; unresolved room names are ignored at runtime. Session/group identity uses the stable room ID after resolution, while human-readable labels still come from room names.
- `rooms`: legacy alias for `groups`.

160
docs/channels/qqbot.md Normal file
View File

@@ -0,0 +1,160 @@
---
summary: "QQ Bot channel plugin setup, config, and usage"
read_when:
- You want to connect OpenClaw to QQ
- You need QQ Bot credential setup
- You want QQ Bot group or private chat support
title: QQ Bot
---
# QQ Bot (plugin)
QQ Bot connects to OpenClaw via the official QQ Bot API (WebSocket gateway). The
plugin supports C2C private chat, group @messages, and guild channel messages with
rich media (images, voice, video, files).
Status: supported via plugin. Direct messages, group chats, guild channels, and
media are supported. Reactions and threads are not supported.
## Plugin required
Install the QQ Bot plugin:
```bash
openclaw plugins install @openclaw/qqbot
```
Local checkout (when running from a git repo):
```bash
openclaw plugins install ./extensions/qqbot
```
## Setup
1. Go to the [QQ Open Platform](https://q.qq.com/) and scan the QR code with your
phone QQ to register / log in.
2. Click **Create Bot** to create a new QQ bot.
3. Find **AppID** and **AppSecret** on the bot's settings page and copy them.
> AppSecret is not stored in plaintext — if you leave the page without saving it,
> you'll have to regenerate a new one.
4. Add the channel:
```bash
openclaw channels add --channel qqbot --token "AppID:AppSecret"
```
5. Restart the Gateway.
## Configure
Minimal config:
```json5
{
channels: {
qqbot: {
enabled: true,
appId: "YOUR_APP_ID",
clientSecret: "YOUR_APP_SECRET",
},
},
}
```
### Multi-account setup
Run multiple QQ bots under a single OpenClaw instance:
```json5
{
channels: {
qqbot: {
enabled: true,
appId: "111111111",
clientSecret: "secret-of-bot-1",
accounts: {
bot2: {
enabled: true,
appId: "222222222",
clientSecret: "secret-of-bot-2",
},
},
},
},
}
```
Each account launches its own WebSocket connection and maintains an independent
token cache (isolated by `appId`).
Add a second bot via CLI:
```bash
openclaw channels add --channel qqbot --account bot2 --token "222222222:secret-of-bot-2"
```
### Voice (STT / TTS)
STT and TTS support two-level configuration with priority fallback:
| Setting | Plugin-specific | Framework fallback |
| ------- | -------------------- | ----------------------------- |
| STT | `channels.qqbot.stt` | `tools.media.audio.models[0]` |
| TTS | `channels.qqbot.tts` | `messages.tts` |
```json5
{
channels: {
qqbot: {
stt: {
provider: "your-provider",
model: "your-stt-model",
},
tts: {
provider: "your-provider",
model: "your-tts-model",
voice: "your-voice",
},
},
},
}
```
Set `enabled: false` on either to disable.
## Target formats
| Format | Description |
| -------------------------- | ------------------ |
| `qqbot:c2c:OPENID` | Private chat (C2C) |
| `qqbot:group:GROUP_OPENID` | Group chat |
| `qqbot:channel:CHANNEL_ID` | Guild channel |
> Each bot has its own set of user OpenIDs. An OpenID received by Bot A **cannot**
> be used to send messages via Bot B.
## Slash commands
Built-in commands intercepted before the AI queue:
| Command | Description |
| -------------- | ------------------------------------ |
| `/bot-ping` | Latency test |
| `/bot-version` | Show the OpenClaw framework version |
| `/bot-help` | List all commands |
| `/bot-upgrade` | Show the QQBot upgrade guide link |
| `/bot-logs` | Export recent gateway logs as a file |
Append `?` to any command for usage help (for example `/bot-upgrade ?`).
## Troubleshooting
- **Bot replies "gone to Mars":** credentials not configured or Gateway not started.
- **No inbound messages:** verify `appId` and `clientSecret` are correct, and the
bot is enabled on the QQ Open Platform.
- **Proactive messages not arriving:** QQ may intercept bot-initiated messages if
the user hasn't interacted recently.
- **Voice not transcribed:** ensure STT is configured and the provider is reachable.

View File

@@ -100,8 +100,9 @@ Permission model (client debug mode):
- Auto-approval is allowlist-based and only applies to trusted core tool IDs.
- `read` auto-approval is scoped to the current working directory (`--cwd` when set).
- Unknown/non-core tool names, out-of-scope reads, and dangerous tools always require explicit prompt approval.
- ACP only auto-approves narrow readonly classes: scoped `read` calls under the active cwd plus readonly search tools (`search`, `web_search`, `memory_search`). Unknown/non-core tools, out-of-scope reads, exec-capable tools, control-plane tools, mutating tools, and interactive flows always require explicit prompt approval.
- Server-provided `toolCall.kind` is treated as untrusted metadata (not an authorization source).
- This ACP bridge policy is separate from ACPX harness permissions. If you run OpenClaw through the `acpx` backend, `plugins.entries.acpx.config.permissionMode=approve-all` is the break-glass “yolo” switch for that harness session.
## How to use this

View File

@@ -113,6 +113,10 @@ openclaw config set --batch-json '[
openclaw config set --batch-file ./config-set.batch.json --dry-run
```
Policy note:
- SecretRef assignments are rejected on unsupported runtime-mutable surfaces (for example `hooks.token`, `commands.ownerDisplaySecret`, Discord thread-binding webhook tokens, and WhatsApp creds JSON). See [SecretRef Credential Surface](/reference/secretref-credential-surface).
Batch parsing always uses the batch payload (`--batch-json`/`--batch-file`) as the source of truth.
`--strict-json` / `--json` do not change batch parsing behavior.
@@ -204,6 +208,8 @@ Dry-run behavior:
- Builder mode: runs SecretRef resolvability checks for changed refs/providers.
- JSON mode (`--strict-json`, `--json`, or batch mode): runs schema validation plus SecretRef resolvability checks.
- Policy validation also runs for known unsupported SecretRef target surfaces.
- Policy checks evaluate the full post-change config, so parent-object writes (for example setting `hooks` as an object) cannot bypass unsupported-surface validation.
- Exec SecretRef checks are skipped by default during dry-run to avoid command side effects.
- Use `--allow-exec` with `--dry-run` to opt in to exec SecretRef checks (this may execute provider commands).
- `--allow-exec` is dry-run only and errors if used without `--dry-run`.
@@ -289,6 +295,7 @@ Failure example:
If dry-run fails:
- `config schema validation failed`: your post-change config shape is invalid; fix path/value or provider/ref object shape.
- `Config policy validation failed: unsupported SecretRef usage`: move that credential back to plaintext/string input and keep SecretRefs on supported surfaces only.
- `SecretRef assignment(s) could not be resolved`: referenced provider/ref currently cannot resolve (missing env var, invalid file pointer, exec provider failure, or provider/source mismatch).
- `Dry run note: skipped <n> exec SecretRef resolvability check(s)`: dry-run skipped exec refs; rerun with `--allow-exec` if you need exec resolvability validation.
- For batch mode, fix failing entries and rerun `--dry-run` before writing.

View File

@@ -44,6 +44,8 @@ Notes:
- If a required auth SecretRef is unresolved in this command path, `daemon status --json` reports `rpc.authWarning` when probe connectivity/auth fails; pass `--token`/`--password` explicitly or resolve the secret source first.
- If the probe succeeds, unresolved auth-ref warnings are suppressed to avoid false positives.
- On Linux systemd installs, `status` token-drift checks include both `Environment=` and `EnvironmentFile=` unit sources.
- Drift checks resolve `gateway.auth.token` SecretRefs using merged runtime env (service command env first, then process env fallback).
- If token auth is not effectively active (explicit `gateway.auth.mode` of `password`/`none`/`trusted-proxy`, or mode unset where password can win and no token candidate can win), token-drift checks skip config token resolution.
- When token auth requires a token and `gateway.auth.token` is SecretRef-managed, `install` validates that the SecretRef is resolvable but does not persist the resolved token into service environment metadata.
- If token auth requires a token and the configured token SecretRef is unresolved, install fails closed.
- If both `gateway.auth.token` and `gateway.auth.password` are configured and `gateway.auth.mode` is unset, install is blocked until mode is set explicitly.

54
docs/cli/flows.md Normal file
View File

@@ -0,0 +1,54 @@
---
summary: "CLI reference for `openclaw flows` (list, inspect, cancel)"
read_when:
- You want to inspect or cancel a flow
- You want to see how background tasks roll up into a higher-level job
title: "flows"
---
# `openclaw flows`
Inspect and manage [ClawFlow](/automation/clawflow) jobs.
```bash
openclaw flows list
openclaw flows show <lookup>
openclaw flows cancel <lookup>
```
## Commands
### `flows list`
List tracked flows and their task counts.
```bash
openclaw flows list
openclaw flows list --status blocked
openclaw flows list --json
```
### `flows show`
Show one flow by flow id or owner session key.
```bash
openclaw flows show <lookup>
openclaw flows show <lookup> --json
```
The output includes the flow status, current step, wait target, blocked summary when present, stored output keys, and linked tasks.
### `flows cancel`
Cancel a flow and any active child tasks.
```bash
openclaw flows cancel <lookup>
```
## Related
- [ClawFlow](/automation/clawflow) — job-level orchestration above tasks
- [Background Tasks](/automation/tasks) — detached work ledger
- [CLI reference](/cli/index) — full command tree

View File

@@ -116,6 +116,8 @@ Notes:
- If the probe succeeds, unresolved auth-ref warnings are suppressed to avoid false positives.
- Use `--require-rpc` in scripts and automation when a listening service is not enough and you need the Gateway RPC itself to be healthy.
- On Linux systemd installs, service auth drift checks read both `Environment=` and `EnvironmentFile=` values from the unit (including `%h`, quoted paths, multiple files, and optional `-` files).
- Drift checks resolve `gateway.auth.token` SecretRefs using merged runtime env (service command env first, then process env fallback).
- If token auth is not effectively active (explicit `gateway.auth.mode` of `password`/`none`/`trusted-proxy`, or mode unset where password can win and no token candidate can win), token-drift checks skip config token resolution.
### `gateway probe`

View File

@@ -45,6 +45,7 @@ This page describes the current CLI behavior. If commands change, update this do
- [`tui`](/cli/tui)
- [`browser`](/cli/browser)
- [`cron`](/cli/cron)
- [`flows`](/cli/flows)
- [`dns`](/cli/dns)
- [`docs`](/cli/docs)
- [`hooks`](/cli/hooks)
@@ -171,6 +172,10 @@ openclaw [--dev] [--profile <name>] <command>
show
notify
cancel
flows
list
show
cancel
gateway
call
health
@@ -809,6 +814,14 @@ List and manage [background task](/automation/tasks) runs across agents.
- `tasks cancel <id>` — cancel a running task
- `tasks audit` — surface operational issues (stale, lost, delivery failures)
### `flows`
List and manage [ClawFlow](/automation/clawflow) jobs across agents.
- `flows list` — show active and recent flows
- `flows show <id>` — show details for a specific flow
- `flows cancel <id>` — cancel a flow and its active child tasks
## Gateway
### `gateway`
@@ -905,12 +918,14 @@ Subcommands:
Common RPCs:
- `config.set` (validate + write full config; use `baseHash` for optimistic concurrency)
- `config.apply` (validate + write config + restart + wake)
- `config.patch` (merge a partial update + restart + wake)
- `update.run` (run update + restart + wake)
Tip: when calling `config.set`/`config.apply`/`config.patch` directly, pass `baseHash` from
`config.get` if a config already exists.
Tip: these config write RPCs preflight active SecretRef resolution for refs in the submitted config payload and reject writes when an effectively active submitted ref is unresolved.
## Models

View File

@@ -49,6 +49,7 @@ capabilities.
openclaw plugins install <package> # ClawHub first, then npm
openclaw plugins install clawhub:<package> # ClawHub only
openclaw plugins install <package> --pin # pin version
openclaw plugins install <package> --dangerously-force-unsafe-install
openclaw plugins install <path> # local path
openclaw plugins install <plugin>@<marketplace> # marketplace
openclaw plugins install <plugin> --marketplace <name> # marketplace (explicit)
@@ -57,6 +58,17 @@ openclaw plugins install <plugin> --marketplace <name> # marketplace (explicit)
Bare package names are checked against ClawHub first, then npm. Security note:
treat plugin installs like running code. Prefer pinned versions.
`--dangerously-force-unsafe-install` is a break-glass option for false positives
in the built-in dangerous-code scanner. It allows the install to continue even
when the built-in scanner reports `critical` findings, but it does **not**
bypass plugin `before_install` hook policy blocks and does **not** bypass scan
failures.
This CLI flag applies to `openclaw plugins install`. Gateway-backed skill
dependency installs use the matching `dangerouslyForceUnsafeInstall` request
override, while `openclaw skills install` remains a separate ClawHub skill
download/install flow.
`plugins install` is also the install surface for hook packs that expose
`openclaw.hooks` in `package.json`. Use `openclaw hooks` for filtered hook
visibility and per-hook enablement, not package installation.

View File

@@ -34,3 +34,7 @@ openclaw skills check
`search`/`install`/`update` use ClawHub directly and install into the active
workspace `skills/` directory. `list`/`info`/`check` still inspect the local
skills visible to the current workspace and config.
This CLI `install` command downloads skill folders from ClawHub. Gateway-backed
skill dependency installs triggered from onboarding or Skills settings use the
separate `skills.install` request path instead.

View File

@@ -234,3 +234,10 @@ Suggested `.gitignore` starter:
[Channel routing](/channels/channel-routing) for routing configuration.
- If `agents.defaults.sandbox` is enabled, non-main sessions can use per-session sandbox
workspaces under `agents.defaults.sandbox.workspaceRoot`.
## Related
- [Standing Orders](/automation/standing-orders) — persistent instructions in workspace files
- [Heartbeat](/gateway/heartbeat) — HEARTBEAT.md workspace file
- [Session](/concepts/session) — session storage paths
- [Sandboxing](/gateway/sandboxing) — workspace access in sandboxed environments

View File

@@ -135,3 +135,10 @@ Details: [Gateway protocol](/gateway/protocol), [Pairing](/channels/pairing),
- Exactly one Gateway controls a single Baileys session per host.
- Handshake is mandatory; any nonJSON or nonconnect first frame is a hard close.
- Events are not replayed; clients must refresh on gaps.
## Related
- [Agent Loop](/concepts/agent-loop) — detailed agent execution cycle
- [Gateway Protocol](/gateway/protocol) — WebSocket protocol contract
- [Queue](/concepts/queue) — command queue and concurrency
- [Security](/gateway/security) — trust model and hardening

View File

@@ -84,3 +84,10 @@ survive.
For advanced configuration (reserve tokens, identifier preservation, custom
context engines, OpenAI server-side compaction), see the
[Session Management Deep Dive](/reference/session-management-compaction).
## Related
- [Session](/concepts/session) — session management and lifecycle
- [Session Pruning](/concepts/session-pruning) — trimming tool results
- [Context](/concepts/context) — how context is built for agent turns
- [Hooks](/automation/hooks) — compaction lifecycle hooks (before_compaction, after_compaction)

View File

@@ -266,3 +266,9 @@ OpenClaw resolves when it needs a context engine.
See also: [Compaction](/concepts/compaction), [Context](/concepts/context),
[Plugins](/tools/plugin), [Plugin manifest](/plugins/manifest).
## Related
- [Context](/concepts/context) — how context is built for agent turns
- [Plugin Architecture](/plugins/architecture) — registering context engine plugins
- [Compaction](/concepts/compaction) — summarizing long conversations

View File

@@ -170,3 +170,10 @@ pluggable interface, lifecycle hooks, and configuration.
- `System prompt (estimate)` = computed on the fly when no run report exists (or when running via a CLI backend that doesnt generate the report).
Either way, it reports sizes and top contributors; it does **not** dump the full system prompt or tool schemas.
## Related
- [Context Engine](/concepts/context-engine) — custom context injection via plugins
- [Compaction](/concepts/compaction) — summarizing long conversations
- [System Prompt](/concepts/system-prompt) — how the system prompt is built
- [Agent Loop](/concepts/agent-loop) — the full agent execution cycle

View File

@@ -25,7 +25,7 @@ binary, and can index content beyond your workspace memory files.
### Prerequisites
- Install QMD: `bun install -g https://github.com/tobi/qmd`
- Install QMD: `bun install -g @tobilu/qmd`
- SQLite build that allows extensions (`brew install sqlite` on macOS).
- QMD must be on the gateway's `PATH`.
- macOS and Linux work out of the box. Windows is best supported via WSL2.
@@ -150,6 +150,12 @@ Set to `120000` for slower hardware.
**Empty results in group chats?** Check `memory.qmd.scope` -- the default only
allows DM sessions.
**Workspace-visible temp repos causing `ENAMETOOLONG` or broken indexing?**
QMD traversal currently follows the underlying QMD scanner behavior rather than
OpenClaw's builtin symlink rules. Keep temporary monorepo checkouts under
hidden directories like `.tmp/` or outside indexed QMD roots until QMD exposes
cycle-safe traversal or explicit exclusion controls.
## Configuration
For the full config surface (`memory.qmd.*`), search modes, update intervals,

View File

@@ -152,3 +152,10 @@ Outbound message formatting is centralized in `messages`:
- Reply threading via `replyToMode` and per-channel defaults
Details: [Configuration](/gateway/configuration-reference#messages) and channel docs.
## Related
- [Streaming](/concepts/streaming) — real-time message delivery
- [Retry](/concepts/retry) — message delivery retry behavior
- [Queue](/concepts/queue) — message processing queue
- [Channels](/channels) — messaging platform integrations

View File

@@ -590,3 +590,10 @@ openclaw models list
```
See also: [/gateway/configuration](/gateway/configuration) for full configuration examples.
## Related
- [Models](/concepts/models) — model configuration and aliases
- [Model Failover](/concepts/model-failover) — fallback chains and retry behavior
- [Configuration Reference](/gateway/configuration-reference#agent-defaults) — model config keys
- [Providers](/providers) — per-provider setup guides

View File

@@ -223,3 +223,10 @@ Merge mode precedence for matching provider IDs:
Marker persistence is source-authoritative: OpenClaw writes markers from the active source config snapshot (pre-resolution), not from resolved runtime secret values.
This applies whenever OpenClaw regenerates `models.json`, including command-driven paths like `openclaw agent`.
## Related
- [Model Providers](/concepts/model-providers) — provider routing and auth
- [Model Failover](/concepts/model-failover) — fallback chains
- [Image Generation](/tools/image-generation) — image model configuration
- [Configuration Reference](/gateway/configuration-reference#agent-defaults) — model config keys

View File

@@ -129,6 +129,48 @@ With **multiple agents**, each `agentId` becomes a **fully isolated persona**:
This lets **multiple people** share one Gateway server while keeping their AI “brains” and data isolated.
## Cross-agent QMD memory search
If one agent should search another agent's QMD session transcripts, add
extra collections under `agents.list[].memorySearch.qmd.extraCollections`.
Use `agents.defaults.memorySearch.qmd.extraCollections` only when every agent
should inherit the same shared transcript collections.
```json5
{
agents: {
defaults: {
workspace: "~/workspaces/main",
memorySearch: {
qmd: {
extraCollections: [{ path: "~/agents/family/sessions", name: "family-sessions" }],
},
},
},
list: [
{
id: "main",
workspace: "~/workspaces/main",
memorySearch: {
qmd: {
extraCollections: [{ path: "notes" }], // resolves inside workspace -> collection named "notes-main"
},
},
},
{ id: "family", workspace: "~/workspaces/family" },
],
},
memory: {
backend: "qmd",
qmd: { includeDefaultMemory: false },
},
}
```
The extra collection path can be shared across agents, but the collection name
stays explicit when the path is outside the agent workspace. Paths inside the
workspace remain agent-scoped so each agent keeps its own transcript search set.
## One WhatsApp number, multiple people (DM split)
You can route **different WhatsApp DMs** to different agents while staying on **one WhatsApp account**. Match on sender E.164 (like `+15551234567`) with `peer.kind: "direct"`. Replies still come from the same WhatsApp number (no peragent sender identity).

View File

@@ -185,3 +185,9 @@ Related docs:
- [/concepts/model-failover](/concepts/model-failover) (rotation + cooldown rules)
- [/tools/slash-commands](/tools/slash-commands) (command surface)
## Related
- [Authentication](/gateway/authentication) — model provider auth overview
- [Secrets](/gateway/secrets) — credential storage and SecretRef
- [Configuration Reference](/gateway/configuration-reference#auth-storage) — auth config keys

View File

@@ -153,3 +153,9 @@ Slack:
- `partial` can use Slack native streaming (`chat.startStream`/`append`/`stop`) when available.
- `block` uses append-style draft previews.
- `progress` uses status preview text, then final answer.
## Related
- [Messages](/concepts/messages) — message lifecycle and delivery
- [Retry](/concepts/retry) — retry behavior on delivery failure
- [Channels](/channels) — per-channel streaming support

View File

@@ -984,6 +984,7 @@
"channels/msteams",
"channels/nextcloud-talk",
"channels/nostr",
"channels/qqbot",
"channels/signal",
"channels/slack",
"channels/synology-chat",
@@ -1121,6 +1122,7 @@
"automation/cron-jobs",
"automation/cron-vs-heartbeat",
"automation/tasks",
"automation/clawflow",
"automation/troubleshooting",
"automation/webhook",
"automation/gmail-pubsub",
@@ -1366,11 +1368,7 @@
},
{
"group": "Node features",
"pages": [
"nodes/talk",
"nodes/voicewake",
"nodes/location-command"
]
"pages": ["nodes/talk", "nodes/voicewake", "nodes/location-command"]
}
]
},
@@ -1436,6 +1434,7 @@
"cli/approvals",
"cli/browser",
"cli/cron",
"cli/flows",
"cli/node",
"cli/nodes",
"cli/sandbox"

View File

@@ -6,7 +6,11 @@ read_when:
title: "Authentication"
---
# Authentication
# Authentication (Model Providers)
<Note>
This page covers **model provider** authentication (API keys, OAuth, setup tokens). For **gateway connection** authentication (token, password, trusted-proxy), see [Configuration](/gateway/configuration) and [Trusted Proxy Auth](/gateway/trusted-proxy-auth).
</Note>
OpenClaw supports OAuth and API keys for model providers. For always-on gateway
hosts, API keys are usually the most predictable option. Subscription/OAuth
@@ -101,6 +105,7 @@ Auth profile refs are also supported for static credentials:
- `api_key` credentials can use `keyRef: { source, provider, id }`
- `token` credentials can use `tokenRef: { source, provider, id }`
- OAuth-mode profiles do not support SecretRef credentials; if `auth.profiles.<id>.mode` is set to `"oauth"`, SecretRef-backed `keyRef`/`tokenRef` input for that profile is rejected.
Automation-friendly check (exit `1` when expired/missing, `2` when expiring):

View File

@@ -9,14 +9,9 @@ title: "Bridge Protocol"
# Bridge protocol (legacy node transport)
The Bridge protocol is a **legacy** node transport (TCP JSONL). New node clients
should use the unified Gateway WebSocket protocol instead.
If you are building an operator or node client, use the
[Gateway protocol](/gateway/protocol).
**Note:** Current OpenClaw builds no longer ship the TCP bridge listener; this document is kept for historical reference.
Legacy `bridge.*` config keys are no longer part of the config schema.
<Warning>
The TCP bridge has been **removed**. Current OpenClaw builds do not ship the bridge listener and `bridge.*` config keys are no longer in the schema. This page is kept for historical reference only. Use the [Gateway Protocol](/gateway/protocol) for all node/operator clients.
</Warning>
## Why we have both

View File

@@ -119,7 +119,6 @@ The provider id becomes the left side of your model ref:
input: "arg",
modelArg: "--model",
modelAliases: {
"claude-opus-4-6": "opus",
"claude-opus-4-6": "opus",
"claude-sonnet-4-6": "sonnet",
},

View File

@@ -154,6 +154,7 @@ Save to `~/.openclaw/openclaw.json` and you can DM the bot from that number.
// Session behavior
session: {
scope: "per-sender",
dmScope: "per-channel-peer", // recommended for multi-user inboxes
reset: {
mode: "daily",
atHour: 4,
@@ -288,7 +289,7 @@ Save to `~/.openclaw/openclaw.json` and you can DM the bot from that number.
},
sandbox: {
mode: "non-main",
perSession: true,
scope: "session", // preferred over legacy perSession: true
workspaceRoot: "~/.openclaw/sandboxes",
docker: {
image: "openclaw-sandbox:bookworm-slim",

View File

@@ -1463,6 +1463,7 @@ scripts/sandbox-browser-setup.sh # optional browser image
- `identity` derives defaults: `ackReaction` from `emoji`, `mentionPatterns` from `name`/`emoji`.
- `subagents.allowAgents`: allowlist of agent ids for `sessions_spawn` (`["*"]` = any; default: same agent only).
- Sandbox inheritance guard: if the requester session is sandboxed, `sessions_spawn` rejects targets that would run unsandboxed.
- `subagents.requireAgentId`: when true, block `sessions_spawn` calls that omit `agentId` (forces explicit profile selection; default: false).
---
@@ -3011,7 +3012,8 @@ Notes:
```
- Per-agent profiles are stored at `<agentDir>/auth-profiles.json`.
- `auth-profiles.json` supports value-level refs (`keyRef` for `api_key`, `tokenRef` for `token`).
- `auth-profiles.json` supports value-level refs (`keyRef` for `api_key`, `tokenRef` for `token`) for static credential modes.
- OAuth-mode profiles (`auth.profiles.<id>.mode = "oauth"`) do not support SecretRef-backed auth-profile credentials.
- Static runtime credentials come from in-memory resolved snapshots; legacy static `auth.json` entries are scrubbed when discovered.
- Legacy OAuth imports from `~/.openclaw/credentials/oauth.json`.
- See [OAuth](/concepts/oauth).

View File

@@ -327,7 +327,7 @@ When validation fails:
```
- `every`: duration string (`30m`, `2h`). Set `0m` to disable.
- `target`: `last` | `whatsapp` | `telegram` | `discord` | `none`
- `target`: `last` | `none` | `<channel-id>` (for example `discord`, `matrix`, `telegram`, or `whatsapp`)
- `directPolicy`: `allow` (default) or `block` for DM-style heartbeat targets
- See [Heartbeat](/gateway/heartbeat) for the full guide.

View File

@@ -59,6 +59,8 @@ Troubleshooting and beacon details: [Bonjour](/gateway/bonjour).
- `_openclaw-gw._tcp` (gateway transport beacon)
- TXT keys (non-secret):
- `role=gateway`
- `transport=gateway`
- `displayName=<friendly name>` (operator-configured display name)
- `lanHost=<hostname>.local`
- `sshPort=22` (or whatever is advertised)
- `gatewayPort=18789` (Gateway WS + HTTP)

View File

@@ -61,18 +61,22 @@ cat ~/.openclaw/openclaw.json
- Optional pre-flight update for git installs (interactive only).
- UI protocol freshness check (rebuilds Control UI when the protocol schema is newer).
- Health check + restart prompt.
- Skills status summary (eligible/missing/blocked).
- Skills status summary (eligible/missing/blocked) and plugin status.
- Config normalization for legacy values.
- Browser migration checks for legacy Chrome extension configs and Chrome MCP readiness.
- OpenCode provider override warnings (`models.providers.opencode` / `models.providers.opencode-go`).
- OAuth TLS prerequisites check for OpenAI Codex OAuth profiles.
- Legacy on-disk state migration (sessions/agent dir/WhatsApp auth).
- Legacy plugin manifest contract key migration (`speechProviders`, `mediaUnderstandingProviders`, `imageGenerationProviders``contracts`).
- Legacy cron store migration (`jobId`, `schedule.cron`, top-level delivery/payload fields, payload `provider`, simple `notify: true` webhook fallback jobs).
- Session lock file inspection and stale lock cleanup.
- State integrity and permissions checks (sessions, transcripts, state dir).
- Config file permission checks (chmod 600) when running locally.
- Model auth health: checks OAuth expiry, can refresh expiring tokens, and reports auth-profile cooldown/disabled states.
- Extra workspace dir detection (`~/openclaw`).
- Sandbox image repair when sandboxing is enabled.
- Legacy service migration and extra gateway detection.
- Matrix channel legacy state migration (in `--fix` / `--repair` mode).
- Gateway runtime checks (service installed but not running; cached launchd label).
- Channel status warnings (probed from the running gateway).
- Supervisor config audit (launchd/systemd/schtasks) with optional repair.
@@ -81,6 +85,9 @@ cat ~/.openclaw/openclaw.json
- Security warnings for open DM policies.
- Gateway auth checks for local token mode (offers token generation when no token source exists; does not overwrite token SecretRef configs).
- systemd linger check on Linux.
- Workspace bootstrap file size check (truncation/near-limit warnings for context files).
- Shell completion status check and auto-install/upgrade.
- Memory search embedding provider readiness check (local model, remote API key, or QMD binary).
- Source install checks (pnpm workspace mismatch, missing UI assets, missing tsx binary).
- Writes updated config + wizard metadata.
@@ -122,6 +129,10 @@ Current migrations:
- `routing.agents`/`routing.defaultAgentId``agents.list` + `agents.list[].default`
- `routing.agentToAgent``tools.agentToAgent`
- `routing.transcribeAudio``tools.media.audio.models`
- `messages.tts.<provider>` (`openai`/`elevenlabs`/`microsoft`/`edge`) → `messages.tts.providers.<provider>`
- `channels.discord.voice.tts.<provider>` (`openai`/`elevenlabs`/`microsoft`/`edge`) → `channels.discord.voice.tts.providers.<provider>`
- `channels.discord.accounts.<id>.voice.tts.<provider>` (`openai`/`elevenlabs`/`microsoft`/`edge`) → `channels.discord.accounts.<id>.voice.tts.providers.<provider>`
- `plugins.entries.voice-call.config.tts.<provider>` (`openai`/`elevenlabs`/`microsoft`/`edge`) → `plugins.entries.voice-call.config.tts.providers.<provider>`
- `bindings[].match.accountID``bindings[].match.accountId`
- For channels with named `accounts` but missing `accounts.default`, move account-scoped top-level single-account channel values into `channels.<channel>.accounts.default` when present
- `identity``agents.list[].identity`
@@ -173,6 +184,16 @@ still requires:
This check does **not** apply to Docker, sandbox, remote-browser, or other
headless flows. Those continue to use raw CDP.
### 2d) OAuth TLS prerequisites
When an OpenAI Codex OAuth profile is configured, doctor probes the OpenAI
authorization endpoint to verify that the local Node/OpenSSL TLS stack can
validate the certificate chain. If the probe fails with a certificate error (for
example `UNABLE_TO_GET_ISSUER_CERT_LOCALLY`, expired cert, or self-signed cert),
doctor prints platform-specific fix guidance. On macOS with a Homebrew Node, the
fix is usually `brew postinstall ca-certificates`. With `--deep`, the probe runs
even if the gateway is healthy.
### 3) Legacy state migrations (disk layout)
Doctor can migrate older on-disk layouts into the current structure:
@@ -191,6 +212,14 @@ the legacy sessions + agent dir on startup so history/auth/models land in the
per-agent path without a manual doctor run. WhatsApp auth is intentionally only
migrated via `openclaw doctor`.
### 3a) Legacy plugin manifest migrations
Doctor scans all installed plugin manifests for deprecated top-level capability keys
(`speechProviders`, `mediaUnderstandingProviders`, `imageGenerationProviders`).
When found, it offers to move them into the `contracts` object and rewrite the manifest
file in-place. This migration is idempotent; if the `contracts` key already has the
same values, the legacy key is removed without duplicating the data.
### 3b) Legacy cron store migrations
Doctor also checks the cron job store (`~/.openclaw/cron/jobs.json` by default,
@@ -210,6 +239,15 @@ Doctor only auto-migrates `notify: true` jobs when it can do so without
changing behavior. If a job combines legacy notify fallback with an existing
non-webhook delivery mode, doctor warns and leaves that job for manual review.
### 3c) Session lock cleanup
Doctor scans every agent session directory for stale write-lock files — files left
behind when a session exited abnormally. For each lock file found it reports:
the path, PID, whether the PID is still alive, lock age, and whether it is
considered stale (dead PID or older than 30 minutes). In `--fix` / `--repair`
mode it removes stale lock files automatically; otherwise it prints a note and
instructs you to rerun with `--fix`.
### 4) State integrity checks (session persistence, routing, and safety)
The state directory is the operational brainstem. If it vanishes, you lose
@@ -273,6 +311,15 @@ port. It can also scan for extra gateway-like services and print cleanup hints.
Profile-named OpenClaw gateway services are considered first-class and are not
flagged as "extra."
### 8b) Startup Matrix migration
When a Matrix channel account has a pending or actionable legacy state migration,
doctor (in `--fix` / `--repair` mode) creates a pre-migration snapshot and then
runs the best-effort migration steps: legacy Matrix state migration and legacy
encrypted-state preparation. Both steps are non-fatal; errors are logged and
startup continues. In read-only mode (`openclaw doctor` without `--fix`) this check
is skipped entirely.
### 9) Security warnings
Doctor emits warnings when a provider is open to DMs without an allowlist, or
@@ -283,10 +330,44 @@ when a policy is configured in a dangerous way.
If running as a systemd user service, doctor ensures lingering is enabled so the
gateway stays alive after logout.
### 11) Skills status
### 11) Workspace status (skills, plugins, and legacy dirs)
Doctor prints a quick summary of eligible/missing/blocked skills for the current
workspace.
Doctor prints a summary of the workspace state for the default agent:
- **Skills status**: counts eligible, missing-requirements, and allowlist-blocked skills.
- **Legacy workspace dirs**: warns when `~/openclaw` or other legacy workspace directories
exist alongside the current workspace.
- **Plugin status**: counts loaded/disabled/errored plugins; lists plugin IDs for any
errors; reports bundle plugin capabilities.
- **Plugin compatibility warnings**: flags plugins that have compatibility issues with
the current runtime.
- **Plugin diagnostics**: surfaces any load-time warnings or errors emitted by the
plugin registry.
### 11b) Bootstrap file size
Doctor checks whether workspace bootstrap files (for example `AGENTS.md`,
`CLAUDE.md`, or other injected context files) are near or over the configured
character budget. It reports per-file raw vs. injected character counts, truncation
percentage, truncation cause (`max/file` or `max/total`), and total injected
characters as a fraction of the total budget. When files are truncated or near
the limit, doctor prints tips for tuning `agents.defaults.bootstrapMaxChars`
and `agents.defaults.bootstrapTotalMaxChars`.
### 11c) Shell completion
Doctor checks whether tab completion is installed for the current shell
(zsh, bash, fish, or PowerShell):
- If the shell profile uses a slow dynamic completion pattern
(`source <(openclaw completion ...)`), doctor upgrades it to the faster
cached file variant.
- If completion is configured in the profile but the cache file is missing,
doctor regenerates the cache automatically.
- If no completion is configured at all, doctor prompts to install it
(interactive mode only; skipped with `--non-interactive`).
Run `openclaw completion --write-state` to regenerate the cache manually.
### 12) Gateway auth checks (local token)
@@ -309,6 +390,26 @@ Some repair flows need to inspect configured credentials without weakening runti
Doctor runs a health check and offers to restart the gateway when it looks
unhealthy.
### 13b) Memory search readiness
Doctor checks whether the configured memory search embedding provider is ready
for the default agent. The behavior depends on the configured backend and provider:
- **QMD backend**: probes whether the `qmd` binary is available and startable.
If not, prints fix guidance including the npm package and a manual binary path option.
- **Explicit local provider**: checks for a local model file or a recognized
remote/downloadable model URL. If missing, suggests switching to a remote provider.
- **Explicit remote provider** (`openai`, `voyage`, etc.): verifies an API key is
present in the environment or auth store. Prints actionable fix hints if missing.
- **Auto provider**: checks local model availability first, then tries each remote
provider in auto-selection order.
When a gateway probe result is available (gateway was healthy at the time of the
check), doctor cross-references its result with the CLI-visible config and notes
any discrepancy.
Use `openclaw memory status --deep` to verify embedding readiness at runtime.
### 14) Channel status warnings
If the gateway is healthy, doctor runs a channel status probe and reports

View File

@@ -8,8 +8,6 @@ title: "Gateway Lock"
# Gateway lock
Last updated: 2025-12-11
## Why
- Ensure only one gateway instance runs per base port on the same host; additional gateways must use isolated profiles and unique ports.
@@ -32,3 +30,8 @@ Last updated: 2025-12-11
- If the port is occupied by _another_ process, the error is the same; free the port or choose another with `openclaw gateway --port <port>`.
- The macOS app still maintains its own lightweight PID guard before spawning the gateway; the runtime lock is enforced by the WebSocket bind.
## Related
- [Multiple Gateways](/gateway/multiple-gateways) — running multiple instances with unique ports
- [Troubleshooting](/gateway/troubleshooting) — diagnosing `EADDRINUSE` and port conflicts

View File

@@ -1,7 +1,8 @@
---
summary: "Health check steps for channel connectivity"
summary: "Health check commands and gateway health monitoring"
read_when:
- Diagnosing WhatsApp channel health
- Diagnosing channel connectivity or gateway health
- Understanding health check CLI commands and options
title: "Health Checks"
---
@@ -41,4 +42,12 @@ Short guide to verify channel connectivity without guessing.
## Dedicated "health" command
`openclaw health --json` asks the running Gateway for its health snapshot (no direct channel sockets from the CLI). It reports linked creds/auth age when available, per-channel probe summaries, session-store summary, and a probe duration. It exits non-zero if the Gateway is unreachable or the probe fails/timeouts. Use `--timeout <ms>` to override the 10s default.
`openclaw health --json` asks the running Gateway for its health snapshot (no direct channel sockets from the CLI). It reports linked creds/auth age when available, per-channel probe summaries, session-store summary, and a probe duration. It exits non-zero if the Gateway is unreachable or the probe fails/timeouts.
Options:
- `--json`: machine-readable JSON output
- `--timeout <ms>`: override the default 10s probe timeout
- `--probe`: force a live probe of all channels instead of returning the cached health snapshot
The health snapshot includes: `ok` (boolean), `ts` (timestamp), `durationMs` (probe time), per-channel status, agent availability, and session-store summary.

View File

@@ -227,7 +227,7 @@ Use `accountId` to target a specific account on multi-account channels like Tele
- Session key formats: see [Sessions](/concepts/session) and [Groups](/channels/groups).
- `target`:
- `last`: deliver to the last used external channel.
- explicit channel: `whatsapp` / `telegram` / `discord` / `googlechat` / `slack` / `msteams` / `signal` / `imessage`.
- explicit channel: any configured channel or plugin id, for example `discord`, `matrix`, `telegram`, or `whatsapp`.
- `none` (default): run the heartbeat but **do not deliver** externally.
- `directPolicy`: controls direct/DM delivery behavior:
- `allow` (default): allow direct/DM heartbeat delivery.

View File

@@ -7,6 +7,8 @@ title: "Network model"
# Network Model
> This content has been merged into [Network](/network#core-model). See that page for the current guide.
Most operations flow through the Gateway (`openclaw gateway`), a single long-running
process that owns channel connections and the WebSocket control plane.

View File

@@ -43,9 +43,23 @@ Treat this endpoint as a **full operator-access** surface for the gateway instan
- A valid Gateway token/password for this endpoint should be treated like an owner/operator credential.
- Requests run through the same control-plane agent path as trusted operator actions.
- There is no separate non-owner/per-user tool boundary on this endpoint; once a caller passes Gateway auth here, OpenClaw treats that caller as a trusted operator for this gateway.
- For shared-secret auth modes (`token` and `password`), the endpoint restores the normal full operator defaults even if the caller sends a narrower `x-openclaw-scopes` header.
- Trusted identity-bearing HTTP modes (for example trusted proxy auth or `gateway.auth.mode="none"`) still honor the declared operator scopes on the request.
- If the target agent policy allows sensitive tools, this endpoint can use them.
- Keep this endpoint on loopback/tailnet/private ingress only; do not expose it directly to the public internet.
Auth matrix:
- `gateway.auth.mode="token"` or `"password"` + `Authorization: Bearer ...`
- proves possession of the shared gateway operator secret
- ignores narrower `x-openclaw-scopes`
- restores the full default operator scope set
- treats chat turns on this endpoint as owner-sender turns
- trusted identity-bearing HTTP modes (for example trusted proxy auth, or `gateway.auth.mode="none"` on private ingress)
- authenticate some outer trusted identity or deployment boundary
- honor the declared `x-openclaw-scopes` header
- only get owner semantics when `operator.admin` is actually present in those declared scopes
See [Security](/gateway/security) and [Remote access](/gateway/remote).
## Agent-first model contract

View File

@@ -24,11 +24,24 @@ Operational behavior matches [OpenAI Chat Completions](/gateway/openai-http-api)
- use `Authorization: Bearer <token>` with the normal Gateway auth config
- treat the endpoint as full operator access for the gateway instance
- for shared-secret auth modes (`token` and `password`), ignore narrower bearer-declared `x-openclaw-scopes` values and restore the normal full operator defaults
- for trusted identity-bearing HTTP modes (for example trusted proxy auth or `gateway.auth.mode="none"`), still honor the declared operator scopes on the request
- select agents with `model: "openclaw"`, `model: "openclaw/default"`, `model: "openclaw/<agentId>"`, or `x-openclaw-agent-id`
- use `x-openclaw-model` when you want to override the selected agent's backend model
- use `x-openclaw-session-key` for explicit session routing
- use `x-openclaw-message-channel` when you want a non-default synthetic ingress channel context
Auth matrix:
- `gateway.auth.mode="token"` or `"password"` + `Authorization: Bearer ...`
- proves possession of the shared gateway operator secret
- ignores narrower `x-openclaw-scopes`
- restores the full default operator scope set
- treats chat turns on this endpoint as owner-sender turns
- trusted identity-bearing HTTP modes (for example trusted proxy auth, or `gateway.auth.mode="none"` on private ingress)
- honor the declared `x-openclaw-scopes` header
- only get owner semantics when `operator.admin` is actually present in those declared scopes
Enable or disable this endpoint with `gateway.http.endpoints.responses.enabled`.
The same compatibility surface also includes:

View File

@@ -4,6 +4,8 @@ read_when: "Connecting the macOS app to a remote gateway over SSH"
title: "Remote Gateway Setup"
---
> This content has been merged into [Remote Access](/gateway/remote#macos-persistent-ssh-tunnel-via-launchagent). See that page for the current guide.
# Running OpenClaw.app with a Remote Gateway
OpenClaw.app uses SSH tunneling to connect to a remote gateway. This guide shows you how to set it up.

View File

@@ -151,3 +151,98 @@ Short version: **keep the Gateway loopback-only** unless youre sure you need
- Treat browser control like operator access: tailnet-only + deliberate node pairing.
Deep dive: [Security](/gateway/security).
### macOS: persistent SSH tunnel via LaunchAgent
For macOS clients connecting to a remote gateway, the easiest persistent setup uses an SSH `LocalForward` config entry plus a LaunchAgent to keep the tunnel alive across reboots and crashes.
#### Step 1: add SSH config
Edit `~/.ssh/config`:
```ssh
Host remote-gateway
HostName <REMOTE_IP>
User <REMOTE_USER>
LocalForward 18789 127.0.0.1:18789
IdentityFile ~/.ssh/id_rsa
```
Replace `<REMOTE_IP>` and `<REMOTE_USER>` with your values.
#### Step 2: copy SSH key (one-time)
```bash
ssh-copy-id -i ~/.ssh/id_rsa <REMOTE_USER>@<REMOTE_IP>
```
#### Step 3: configure the gateway token
Store the token in config so it persists across restarts:
```bash
openclaw config set gateway.remote.token "<your-token>"
```
#### Step 4: create the LaunchAgent
Save this as `~/Library/LaunchAgents/ai.openclaw.ssh-tunnel.plist`:
```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>ai.openclaw.ssh-tunnel</string>
<key>ProgramArguments</key>
<array>
<string>/usr/bin/ssh</string>
<string>-N</string>
<string>remote-gateway</string>
</array>
<key>KeepAlive</key>
<true/>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>
```
#### Step 5: load the LaunchAgent
```bash
launchctl bootstrap gui/$UID ~/Library/LaunchAgents/ai.openclaw.ssh-tunnel.plist
```
The tunnel will start automatically at login, restart on crash, and keep the forwarded port live.
Note: if you have a leftover `com.openclaw.ssh-tunnel` LaunchAgent from an older setup, unload and delete it.
#### Troubleshooting
Check if the tunnel is running:
```bash
ps aux | grep "ssh -N remote-gateway" | grep -v grep
lsof -i :18789
```
Restart the tunnel:
```bash
launchctl kickstart -k gui/$UID/ai.openclaw.ssh-tunnel
```
Stop the tunnel:
```bash
launchctl bootout gui/$UID/ai.openclaw.ssh-tunnel
```
| Config entry | What it does |
| ------------------------------------ | ------------------------------------------------------------ |
| `LocalForward 18789 127.0.0.1:18789` | Forwards local port 18789 to remote port 18789 |
| `ssh -N` | SSH without executing remote commands (port-forwarding only) |
| `KeepAlive` | Automatically restarts the tunnel if it crashes |
| `RunAtLoad` | Starts the tunnel when the LaunchAgent loads at login |

View File

@@ -50,8 +50,8 @@ Not sandboxed:
`agents.defaults.sandbox.scope` controls **how many containers** are created:
- `"session"` (default): one container per session.
- `"agent"`: one container per agent.
- `"agent"` (default): one container per agent.
- `"session"`: one container per session.
- `"shared"`: one container shared by all sandboxed sessions.
## Backend

View File

@@ -20,6 +20,7 @@ Secrets are resolved into an in-memory runtime snapshot.
- Resolution is eager during activation, not lazy on request paths.
- Startup fails fast when an effectively active SecretRef cannot be resolved.
- Reload uses atomic swap: full success, or keep the last-known-good snapshot.
- SecretRef policy violations (for example OAuth-mode auth profiles combined with SecretRef input) fail activation before runtime swap.
- Runtime requests read from the active in-memory snapshot only.
- After the first successful config activation/load, runtime code paths keep reading that active in-memory snapshot until a successful reload swaps it.
- Outbound delivery paths also read from that active snapshot (for example Discord reply/thread delivery and Telegram action sends); they do not re-resolve SecretRefs on each send.
@@ -364,6 +365,7 @@ Runtime-minted or rotating credentials and OAuth refresh material are intentiona
- Field without a ref: unchanged.
- Field with a ref: required on active surfaces during activation.
- If both plaintext and ref are present, ref takes precedence on supported precedence paths.
- The redaction sentinel `__OPENCLAW_REDACTED__` is reserved for internal config redaction/restore and is rejected as literal submitted config data.
Warning and audit signals:
@@ -383,12 +385,14 @@ Secret activation runs on:
- Config reload hot-apply path
- Config reload restart-check path
- Manual reload via `secrets.reload`
- Gateway config write RPC preflight (`config.set` / `config.apply` / `config.patch`) for active-surface SecretRef resolvability within the submitted config payload before persisting edits
Activation contract:
- Success swaps the snapshot atomically.
- Startup failure aborts gateway startup.
- Runtime reload failure keeps the last-known-good snapshot.
- Write-RPC preflight failure rejects the submitted config and keeps both disk config and active runtime snapshot unchanged.
- Providing an explicit per-call channel token to an outbound helper/tool call does not trigger SecretRef activation; activation points remain startup, reload, and explicit `secrets.reload`.
## Degraded and recovered signals

View File

@@ -285,6 +285,7 @@ aggregates:
- `hooks.gmail.allowUnsafeExternalContent=true`
- `hooks.mappings[<index>].allowUnsafeExternalContent=true`
- `tools.exec.applyPatch.workspaceOnly=false`
- `plugins.entries.acpx.config.permissionMode=approve-all`
Complete `dangerous*` / `dangerously*` config keys defined in OpenClaw config
schema:
@@ -446,8 +447,11 @@ Plugins run **in-process** with the Gateway. Treat them as trusted code:
- Restart the Gateway after plugin changes.
- If you install plugins (`openclaw plugins install <package>`), treat it like running untrusted code:
- The install path is the per-plugin directory under the active plugin install root.
- OpenClaw runs a built-in dangerous-code scan before install. `critical` findings block by default.
- OpenClaw uses `npm pack` and then runs `npm install --omit=dev` in that directory (npm lifecycle scripts can execute code during install).
- Prefer pinned, exact versions (`@scope/pkg@1.2.3`), and inspect the unpacked code on disk before enabling.
- `--dangerously-force-unsafe-install` is break-glass only for built-in scan false positives. It does not bypass plugin `before_install` hook policy blocks and does not bypass scan failures.
- Gateway-backed skill dependency installs follow the same dangerous/suspicious split: built-in `critical` findings block unless the caller explicitly sets `dangerouslyForceUnsafeInstall`, while suspicious findings still warn only. `openclaw skills install` remains the separate ClawHub skill download/install flow.
Details: [Plugins](/tools/plugin)
@@ -802,7 +806,10 @@ still require token/password auth.
Important boundary note:
- Gateway HTTP bearer auth is effectively all-or-nothing operator access.
- Treat credentials that can call `/v1/chat/completions`, `/v1/responses`, `/tools/invoke`, or `/api/channels/*` as full-access operator secrets for that gateway.
- Treat credentials that can call `/v1/chat/completions`, `/v1/responses`, or `/api/channels/*` as full-access operator secrets for that gateway.
- On the OpenAI-compatible HTTP surface, shared-secret bearer auth restores the full default operator scopes and owner semantics for agent turns; narrower `x-openclaw-scopes` values do not reduce that shared-secret path.
- Per-request scope semantics on HTTP only apply when the request comes from an identity-bearing mode such as trusted proxy auth or `gateway.auth.mode="none"` on a private ingress.
- `/tools/invoke` follows the same shared-secret rule: token/password bearer auth is treated as full operator access there too, while identity-bearing modes still honor declared scopes.
- Do not share these credentials with untrusted callers; prefer separate gateways per trust boundary.
**Trust assumption:** tokenless Serve auth assumes the gateway host is trusted.

View File

@@ -8,7 +8,7 @@ title: "Tools Invoke API"
# Tools Invoke (HTTP)
OpenClaws Gateway exposes a simple HTTP endpoint for invoking a single tool directly. It is always enabled and uses Gateway auth plus tool policy, but callers that pass Gateway bearer auth are treated as trusted operators for that gateway.
OpenClaws Gateway exposes a simple HTTP endpoint for invoking a single tool directly. It is always enabled and uses Gateway auth plus tool policy. Like the OpenAI-compatible `/v1/*` surface, shared-secret bearer auth is treated as trusted operator access for the whole gateway.
- `POST /tools/invoke`
- Same port as the Gateway (WS + HTTP multiplex): `http://<gateway-host>:<port>/tools/invoke`
@@ -26,7 +26,29 @@ Notes:
- When `gateway.auth.mode="token"`, use `gateway.auth.token` (or `OPENCLAW_GATEWAY_TOKEN`).
- When `gateway.auth.mode="password"`, use `gateway.auth.password` (or `OPENCLAW_GATEWAY_PASSWORD`).
- If `gateway.auth.rateLimit` is configured and too many auth failures occur, the endpoint returns `429` with `Retry-After`.
- Treat this credential as a full-access operator secret for that gateway. It is not a scoped API token for a narrower `/tools/invoke` role.
## Security boundary (important)
Treat this endpoint as a **full operator-access** surface for the gateway instance.
- HTTP bearer auth here is not a narrow per-user scope model.
- A valid Gateway token/password for this endpoint should be treated like an owner/operator credential.
- For shared-secret auth modes (`token` and `password`), the endpoint restores the normal full operator defaults even if the caller sends a narrower `x-openclaw-scopes` header.
- Shared-secret auth also treats direct tool invokes on this endpoint as owner-sender turns.
- Trusted identity-bearing HTTP modes (for example trusted proxy auth or `gateway.auth.mode="none"` on a private ingress) still honor the declared operator scopes on the request.
- Keep this endpoint on loopback/tailnet/private ingress only; do not expose it directly to the public internet.
Auth matrix:
- `gateway.auth.mode="token"` or `"password"` + `Authorization: Bearer ...`
- proves possession of the shared gateway operator secret
- ignores narrower `x-openclaw-scopes`
- restores the full default operator scope set
- treats direct tool invokes on this endpoint as owner-sender turns
- trusted identity-bearing HTTP modes (for example trusted proxy auth, or `gateway.auth.mode="none"` on private ingress)
- authenticate some outer trusted identity or deployment boundary
- honor the declared `x-openclaw-scopes` header
- only get owner semantics when `operator.admin` is actually present in those declared scopes
## Request body
@@ -62,17 +84,24 @@ If a tool is not allowed by policy, the endpoint returns **404**.
Important boundary notes:
- `POST /tools/invoke` is in the same trusted-operator bucket as other Gateway HTTP APIs such as `/v1/chat/completions`, `/v1/responses`, and `/api/channels/*`.
- Exec approvals are operator guardrails, not a separate authorization boundary for this HTTP endpoint. If a tool is reachable here via Gateway auth + tool policy, `/tools/invoke` does not add an extra per-call approval prompt.
- Do not share Gateway bearer credentials with untrusted callers. If you need separation across trust boundaries, run separate gateways (and ideally separate OS users/hosts).
Gateway HTTP also applies a hard deny list by default (even if session policy allows the tool):
- `cron`
- `sessions_spawn`
- `sessions_send`
- `gateway`
- `whatsapp_login`
- `exec` — direct command execution (RCE surface)
- `spawn` — arbitrary child process creation (RCE surface)
- `shell` — shell command execution (RCE surface)
- `fs_write` — arbitrary file mutation on the host
- `fs_delete` — arbitrary file deletion on the host
- `fs_move` — arbitrary file move/rename on the host
- `apply_patch` — patch application can rewrite arbitrary files
- `sessions_spawn` — session orchestration; spawning agents remotely is RCE
- `sessions_send` — cross-session message injection
- `cron` — persistent automation control plane
- `gateway` — gateway control plane; prevents reconfiguration via HTTP
- `nodes` — node command relay can reach system.run on paired hosts
- `whatsapp_login` — interactive setup requiring terminal QR scan; hangs on HTTP
You can customize this deny list via `gateway.tools`:
@@ -108,7 +137,7 @@ To help group policies resolve context, you can optionally set:
```bash
curl -sS http://127.0.0.1:18789/tools/invoke \
-H 'Authorization: Bearer YOUR_TOKEN' \
-H 'Authorization: Bearer secret' \
-H 'Content-Type: application/json' \
-d '{
"tool": "sessions_list",

View File

@@ -145,7 +145,8 @@ If logs show nonce/signature errors, update the connecting client and verify it:
Related:
- [/web/control-ui](/web/control-ui)
- [/gateway/authentication](/gateway/authentication)
- [/gateway/configuration](/gateway/configuration) (gateway auth modes)
- [/gateway/trusted-proxy-auth](/gateway/trusted-proxy-auth)
- [/gateway/remote](/gateway/remote)
- [/cli/devices](/cli/devices)

View File

@@ -2784,6 +2784,8 @@ Related: [/concepts/oauth](/concepts/oauth) (OAuth flows, token storage, multi-a
- The target channel supports outbound media and isn't blocked by allowlists.
- The file is within the provider's size limits (images are resized to max 2048px).
- `tools.fs.workspaceOnly=true` keeps local-path sends limited to workspace, temp/media-store, and sandbox-validated files.
- `tools.fs.workspaceOnly=false` lets `MEDIA:` send host-local files the agent can already read, but only for media plus safe document types (images, audio, video, PDF, and Office docs). Plain text and secret-like files are still blocked.
See [Images](/nodes/images).

View File

@@ -66,6 +66,7 @@ Think of the suites as “increasing realism” (and increasing flakiness/cost):
- Shared unit, extension, channel, and gateway runs all stay on Vitest `forks`.
- The wrapper keeps measured fork-isolated exceptions and heavy singleton lanes explicit in `test/fixtures/test-parallel.behavior.json`.
- The wrapper peels the heaviest measured files into dedicated lanes instead of relying on a growing hand-maintained exclusion list.
- CLI startup benchmarking now has distinct saved outputs: `pnpm test:startup:bench:smoke` writes the targeted smoke artifact at `.artifacts/cli-startup-bench-smoke.json`, `pnpm test:startup:bench:save` writes the full-suite artifact at `.artifacts/cli-startup-bench-all.json` with `runs=5` and `warmup=1`, and `pnpm test:startup:bench:update` refreshes the checked-in fixture at `test/fixtures/cli-startup-bench.json` with `runs=5` and `warmup=1`.
- For surface-only local runs, unit, extension, and channel shared lanes can overlap their isolated hotspots instead of waiting behind one serial prefix.
- For multi-surface local runs, the wrapper keeps the shared surface phases ordered, but batches inside the same shared phase now fan out together, deferred isolated work can overlap the next shared phase, and spare `unit-fast` headroom now starts that deferred work earlier instead of leaving those slots idle.
- Refresh the timing snapshots with `pnpm test:perf:update-timings` and `pnpm test:perf:update-timings:extensions` after major suite shape changes.
@@ -205,7 +206,7 @@ Live tests are split into two layers so we can isolate failures:
- `pnpm test:live` (or `OPENCLAW_LIVE_TEST=1` if invoking Vitest directly)
- Set `OPENCLAW_LIVE_MODELS=modern` (or `all`, alias for modern) to actually run this suite; otherwise it skips to keep `pnpm test:live` focused on gateway smoke
- How to select models:
- `OPENCLAW_LIVE_MODELS=modern` to run the modern allowlist (Opus/Sonnet/Haiku 4.5, GPT-5.x + Codex, Gemini 3, GLM 4.7, MiniMax M2.7, Grok 4)
- `OPENCLAW_LIVE_MODELS=modern` to run the modern allowlist (Opus/Sonnet 4.6+, GPT-5.x + Codex, Gemini 3, GLM 4.7, MiniMax M2.7, Grok 4)
- `OPENCLAW_LIVE_MODELS=all` is an alias for the modern allowlist
- or `OPENCLAW_LIVE_MODELS="openai/gpt-5.2,anthropic/claude-opus-4-6,..."` (comma allowlist)
- How to select providers:
@@ -236,7 +237,7 @@ Live tests are split into two layers so we can isolate failures:
- How to enable:
- `pnpm test:live` (or `OPENCLAW_LIVE_TEST=1` if invoking Vitest directly)
- How to select models:
- Default: modern allowlist (Opus/Sonnet/Haiku 4.5, GPT-5.x + Codex, Gemini 3, GLM 4.7, MiniMax M2.7, Grok 4)
- Default: modern allowlist (Opus/Sonnet 4.6+, GPT-5.x + Codex, Gemini 3, GLM 4.7, MiniMax M2.7, Grok 4)
- `OPENCLAW_LIVE_GATEWAY_MODELS=all` is an alias for the modern allowlist
- Or set `OPENCLAW_LIVE_GATEWAY_MODELS="provider/model"` (or comma list) to narrow
- How to select providers (avoid “OpenRouter everything”):

View File

@@ -342,3 +342,11 @@ flowchart TD
</Accordion>
</AccordionGroup>
## Related
- [FAQ](/help/faq) — frequently asked questions
- [Gateway Troubleshooting](/gateway/troubleshooting) — gateway-specific issues
- [Doctor](/gateway/doctor) — automated health checks and repairs
- [Channel Troubleshooting](/channels/troubleshooting) — channel connectivity issues
- [Automation Troubleshooting](/automation/troubleshooting) — cron and heartbeat issues

View File

@@ -48,7 +48,7 @@ update **without** changing your persisted channel:
```bash
# Install a specific version
openclaw update --tag 2026.3.30-beta.1
openclaw update --tag 2026.3.31-beta.1
# Install from the beta dist-tag (one-off, does not persist)
openclaw update --tag beta
@@ -57,7 +57,7 @@ openclaw update --tag beta
openclaw update --tag main
# Install a specific npm package spec
openclaw update --tag openclaw@2026.3.30-beta.1
openclaw update --tag openclaw@2026.3.31-beta.1
```
Notes:
@@ -75,7 +75,7 @@ Preview what `openclaw update` would do without making changes:
```bash
openclaw update --dry-run
openclaw update --channel beta --dry-run
openclaw update --tag 2026.3.30-beta.1 --dry-run
openclaw update --tag 2026.3.31-beta.1 --dry-run
openclaw update --dry-run --json
```

View File

@@ -394,3 +394,11 @@ scripts/sandbox-setup.sh
</Accordion>
</AccordionGroup>
## Related
- [Install Overview](/install) — all installation methods
- [Podman](/install/podman) — Podman alternative to Docker
- [ClawDock](/install/clawdock) — Docker Compose community setup
- [Updating](/install/updating) — keeping OpenClaw up to date
- [Configuration](/gateway/configuration) — gateway configuration after install

View File

@@ -136,3 +136,9 @@ export PATH="$HOME/.npm-global/bin:$PATH"
```
Add the `export PATH=...` line to your `~/.bashrc` or `~/.zshrc` to make it permanent.
## Related
- [Install Overview](/install) — all installation methods
- [Updating](/install/updating) — keeping OpenClaw up to date
- [Getting Started](/start/getting-started) — first steps after install

View File

@@ -126,3 +126,9 @@ To return to latest: `git checkout main && git pull`.
- Run `openclaw doctor` again and read the output carefully.
- Check: [Troubleshooting](/gateway/troubleshooting)
- Ask in Discord: [https://discord.gg/clawd](https://discord.gg/clawd)
## Related
- [Install Overview](/install) — all installation methods
- [Doctor](/gateway/doctor) — health checks after updates
- [Migrating](/install/migrating) — major version migration guides

View File

@@ -350,3 +350,8 @@ Queues + sessions:
- **Logs empty?** Check that the Gateway is running and writing to the file path
in `logging.file`.
- **Need more detail?** Set `logging.level` to `debug` or `trace` and retry.
## Related
- [Gateway Logging Internals](/gateway/logging) — WS log styles, subsystem prefixes, and console capture
- [Diagnostics](/gateway/configuration-reference#diagnostics) — OpenTelemetry export and cache trace config

View File

@@ -14,6 +14,15 @@ devices across localhost, LAN, and tailnet.
## Core model
Most operations flow through the Gateway (`openclaw gateway`), a single long-running process that owns channel connections and the WebSocket control plane.
- **Loopback first**: the Gateway WS defaults to `ws://127.0.0.1:18789`. Tokens are required for non-loopback binds.
- **One Gateway per host** is recommended. For isolation, run multiple gateways with isolated profiles and ports ([Multiple Gateways](/gateway/multiple-gateways)).
- **Canvas host** is served on the same port as the Gateway (`/__openclaw__/canvas/`, `/__openclaw__/a2ui/`), protected by Gateway auth when bound beyond loopback.
- **Remote access** is typically SSH tunnel or Tailscale VPN ([Remote Access](/gateway/remote)).
Key references:
- [Gateway architecture](/concepts/architecture)
- [Gateway protocol](/gateway/protocol)
- [Gateway runbook](/gateway)

View File

@@ -20,6 +20,7 @@ The macOS app surfaces OpenClaw skills via the gateway; it does not parse skills
- `metadata.openclaw.install` defines install options (brew/node/go/uv).
- The app calls `skills.install` to run installers on the gateway host.
- Built-in dangerous-code `critical` findings block `skills.install` by default; suspicious findings still warn only. The dangerous override exists on the gateway request, but the default app flow stays fail-closed.
- The gateway surfaces only one preferred installer when multiple are provided
(brew when available, otherwise node manager from `skills.install`, default npm).

View File

@@ -273,3 +273,11 @@ internal imports — never import your own plugin through its SDK path.
Full manifest schema reference
</Card>
</CardGroup>
## Related
- [Plugin Architecture](/plugins/architecture) — internal architecture deep dive
- [SDK Overview](/plugins/sdk-overview) — Plugin SDK reference
- [Manifest](/plugins/manifest) — plugin manifest format
- [Channel Plugins](/plugins/sdk-channel-plugins) — building channel plugins
- [Provider Plugins](/plugins/sdk-provider-plugins) — building provider plugins

View File

@@ -276,3 +276,9 @@ See [Configuration reference](/gateway/configuration) for the full `plugins.*` s
- If your plugin depends on native modules, document the build steps and any
package-manager allowlist requirements (for example, pnpm `allow-build-scripts`
- `pnpm rebuild <package>`).
## Related
- [Building Plugins](/plugins/building-plugins) — getting started with plugins
- [Plugin Architecture](/plugins/architecture) — internal architecture
- [SDK Overview](/plugins/sdk-overview) — Plugin SDK reference

View File

@@ -219,9 +219,11 @@ streaming speech on calls. You can override it under the plugin config with the
{
tts: {
provider: "elevenlabs",
elevenlabs: {
voiceId: "pMsXgVXv3BLzUgSXRplE",
modelId: "eleven_multilingual_v2",
providers: {
elevenlabs: {
voiceId: "pMsXgVXv3BLzUgSXRplE",
modelId: "eleven_multilingual_v2",
},
},
},
}
@@ -229,9 +231,11 @@ streaming speech on calls. You can override it under the plugin config with the
Notes:
- Legacy `tts.<provider>` keys inside plugin config (`openai`, `elevenlabs`, `microsoft`, `edge`) are auto-migrated to `tts.providers.<provider>` on load. Prefer the `providers` shape in committed config.
- **Microsoft speech is ignored for voice calls** (telephony audio needs PCM; the current Microsoft transport does not expose telephony PCM output).
- Core TTS is used when Twilio media streaming is enabled; otherwise calls fall back to provider native voices.
- If a Twilio media stream is already active, Voice Call does not fall back to TwiML `<Say>`. If telephony TTS is unavailable in that state, the playback request fails instead of mixing two playback paths.
- When telephony TTS falls back to a secondary provider, Voice Call logs a warning with the provider chain (`from`, `to`, `attempts`) for debugging.
### More examples
@@ -242,7 +246,9 @@ Use core TTS only (no override):
messages: {
tts: {
provider: "openai",
openai: { voice: "alloy" },
providers: {
openai: { voice: "alloy" },
},
},
},
}
@@ -258,10 +264,12 @@ Override to ElevenLabs just for calls (keep core default elsewhere):
config: {
tts: {
provider: "elevenlabs",
elevenlabs: {
apiKey: "elevenlabs_key",
voiceId: "pMsXgVXv3BLzUgSXRplE",
modelId: "eleven_multilingual_v2",
providers: {
elevenlabs: {
apiKey: "elevenlabs_key",
voiceId: "pMsXgVXv3BLzUgSXRplE",
modelId: "eleven_multilingual_v2",
},
},
},
},
@@ -280,9 +288,11 @@ Override only the OpenAI model for calls (deepmerge example):
"voice-call": {
config: {
tts: {
openai: {
model: "gpt-4o-mini-tts",
voice: "marin",
providers: {
openai: {
model: "gpt-4o-mini-tts",
voice: "marin",
},
},
},
},

View File

@@ -53,6 +53,10 @@ OpenClaw has three public release lanes:
- npm release preflight fails closed unless the tarball includes both
`dist/control-ui/index.html` and a non-empty `dist/control-ui/assets/` payload
so we do not ship an empty browser dashboard again
- If the release work touched CI planning, extension timing manifests, or fast
test matrices, regenerate and review the planner-owned `checks-fast-extensions`
shard plan via `node scripts/ci-write-manifest-outputs.mjs --workflow ci`
before approval so release notes do not describe a stale CI layout
- Stable macOS release readiness also includes the updater surfaces:
- the GitHub release must end up with the packaged `.zip`, `.dmg`, and `.dSYM.zip`
- `appcast.xml` on `main` must point at the new stable zip after publish

View File

@@ -185,7 +185,18 @@ Evergreen files (`MEMORY.md`, non-dated files in `memory/`) are never decayed.
```
Paths can be absolute or workspace-relative. Directories are scanned
recursively for `.md` files. Symlinks are ignored.
recursively for `.md` files. Symlink handling depends on the active backend:
the builtin engine ignores symlinks, while QMD follows the underlying QMD
scanner behavior.
For agent-scoped cross-agent transcript search, use
`agents.list[].memorySearch.qmd.extraCollections` instead of `memory.qmd.paths`.
Those extra collections follow the same `{ path, name, pattern? }` shape, but
they are merged per agent and can preserve explicit shared names when the path
points outside the current workspace.
If the same resolved path appears in both `memory.qmd.paths` and
`memorySearch.qmd.extraCollections`, QMD keeps the first entry and skips the
duplicate.
---

View File

@@ -99,8 +99,8 @@ Scope intent:
### `auth-profiles.json` targets (`secrets configure` + `secrets apply` + `secrets audit`)
- `profiles.*.keyRef` (`type: "api_key"`)
- `profiles.*.tokenRef` (`type: "token"`)
- `profiles.*.keyRef` (`type: "api_key"`; unsupported when `auth.profiles.<id>.mode = "oauth"`)
- `profiles.*.tokenRef` (`type: "token"`; unsupported when `auth.profiles.<id>.mode = "oauth"`)
[//]: # "secretref-supported-list-end"
@@ -109,6 +109,7 @@ Notes:
- Auth-profile plan targets require `agentId`.
- Plan entries target `profiles.*.key` / `profiles.*.token` and write sibling refs (`keyRef` / `tokenRef`).
- Auth-profile refs are included in runtime resolution and audit coverage.
- OAuth policy guard: `auth.profiles.<id>.mode = "oauth"` cannot be combined with SecretRef inputs for that profile. Startup/reload and auth-profile resolution fail fast when this policy is violated.
- For SecretRef-managed model providers, generated `agents/*/agent/models.json` entries persist non-secret markers (not resolved secret values) for `apiKey`/header surfaces.
- Marker persistence is source-authoritative: OpenClaw writes markers from the active source config snapshot (pre-resolution), not from resolved runtime secret values.
- For web search:
@@ -128,8 +129,10 @@ Out-of-scope credentials include:
- `hooks.gmail.pushToken`
- `hooks.mappings[].sessionKey`
- `auth-profiles.oauth.*`
- `discord.threadBindings.*.webhookToken`
- `whatsapp.creds.json`
- `channels.discord.threadBindings.webhookToken`
- `channels.discord.accounts.*.threadBindings.webhookToken`
- `channels.whatsapp.creds.json`
- `channels.whatsapp.accounts.*.creds.json`
[//]: # "secretref-unsupported-list-end"

View File

@@ -9,8 +9,10 @@
"hooks.gmail.pushToken",
"hooks.mappings[].sessionKey",
"auth-profiles.oauth.*",
"discord.threadBindings.*.webhookToken",
"whatsapp.creds.json"
"channels.discord.threadBindings.webhookToken",
"channels.discord.accounts.*.threadBindings.webhookToken",
"channels.whatsapp.creds.json",
"channels.whatsapp.accounts.*.creds.json"
],
"entries": [
{

View File

@@ -64,19 +64,40 @@ Script: [`scripts/bench-cli-startup.ts`](https://github.com/openclaw/openclaw/bl
Usage:
- `pnpm test:startup:bench`
- `pnpm test:startup:bench:smoke`
- `pnpm test:startup:bench:save`
- `pnpm test:startup:bench:update`
- `pnpm test:startup:bench:check`
- `pnpm tsx scripts/bench-cli-startup.ts`
- `pnpm tsx scripts/bench-cli-startup.ts --runs 12`
- `pnpm tsx scripts/bench-cli-startup.ts --entry dist/entry.js --timeout-ms 45000`
- `pnpm tsx scripts/bench-cli-startup.ts --preset real`
- `pnpm tsx scripts/bench-cli-startup.ts --preset real --case status --case gatewayStatus --runs 3`
- `pnpm tsx scripts/bench-cli-startup.ts --entry openclaw.mjs --entry-secondary dist/entry.js --preset all`
- `pnpm tsx scripts/bench-cli-startup.ts --preset all --output .artifacts/cli-startup-bench-all.json`
- `pnpm tsx scripts/bench-cli-startup.ts --preset real --case gatewayStatusJson --output .artifacts/cli-startup-bench-smoke.json`
- `pnpm tsx scripts/bench-cli-startup.ts --preset real --cpu-prof-dir .artifacts/cli-cpu`
- `pnpm tsx scripts/bench-cli-startup.ts --json`
This benchmarks these commands:
Presets:
- `--version`
- `--help`
- `health --json`
- `status --json`
- `status`
- `startup`: `--version`, `--help`, `health`, `health --json`, `status --json`, `status`
- `real`: `health`, `status`, `status --json`, `sessions`, `sessions --json`, `agents list --json`, `gateway status`, `gateway status --json`, `gateway health --json`, `config get gateway.port`
- `all`: both presets
Output includes avg, p50, p95, min/max, and exit-code/signal distribution for each command.
Output includes `sampleCount`, avg, p50, p95, min/max, exit-code/signal distribution, and max RSS summaries for each command. Optional `--cpu-prof-dir` / `--heap-prof-dir` writes V8 profiles per run so timing and profile capture use the same harness.
Saved output conventions:
- `pnpm test:startup:bench:smoke` writes the targeted smoke artifact at `.artifacts/cli-startup-bench-smoke.json`
- `pnpm test:startup:bench:save` writes the full-suite artifact at `.artifacts/cli-startup-bench-all.json` using `runs=5` and `warmup=1`
- `pnpm test:startup:bench:update` refreshes the checked-in baseline fixture at `test/fixtures/cli-startup-bench.json` using `runs=5` and `warmup=1`
Checked-in fixture:
- `test/fixtures/cli-startup-bench.json`
- Refresh with `pnpm test:startup:bench:update`
- Compare current results against the fixture with `pnpm test:startup:bench:check`
## Onboarding E2E (Docker)

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