Compare commits

...

265 Commits

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

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

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

* fix(acpx): avoid duplicate queue owner recovery

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

---------

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

* Docs: address flows review comments

* docs: remove stale flows subtree from cli index

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

---------

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

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

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

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

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

---------

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

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

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

Closes #34498

* fix(telegram): track error cooldown per message

* fix(telegram): prune expired error cooldowns

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

---------

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

* fix: preserve anthropic thinking blocks on replay

* fix: preserve anthropic thinking replay (#58916)

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

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

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

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

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

---------

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

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

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

Fixes #51525

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

* fix: add braces to satisfy oxlint requirement

* fix(telegram): keep send retries strict

* test(telegram): cover wrapped retry paths

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

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

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

---------

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

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

* fix(gateway): address review feedback

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

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

* fix(qqbot): harden bot logs allowlist guard

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

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

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

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

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

* fix(exec): handle headless cron approval conflicts

* fix(exec): make allow-always durable

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

* fix(doctor): match host exec fallback

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

* Doctor: surface allow-always ask bypass

* Doctor: match effective exec policy

* Exec: match node durable command text

* Exec: tighten durable approval security

* Exec: restore owner approver fallback

* Config: refresh Slack approval metadata

---------

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

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

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

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

Fixes #58572

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

* fix: restore latest-main gates for #58707

---------

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

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

* fix(subagents): align lifecycle terminal timestamps

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

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

* fix(sandbox): tag browser runtime removals correctly

* test(sandbox): share docker backend test config

* fix(sandbox): normalize browser runtime image matching

---------

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

* fix: ship npm runner in packed installs

* fix: address bundled runtime staging review feedback

* fix: include npm runner in docker build contexts

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

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

* test(status): use real task snapshot semantics

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

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

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

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

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

* docs(changelog): note qmd collection flag fix

---------

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

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

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

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

---------

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

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

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

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

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

---------

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

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

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

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

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

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

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

Resolves #58546

Made-with: Cursor

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

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

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

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

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

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

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

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

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

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

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

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

* docs(bedrock): add guardrails configuration documentation

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

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

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

* chore: remove kiro spec files from PR

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

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

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

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

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

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

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

* fix: address review feedback

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

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

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

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

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

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

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

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

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

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

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

---------

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

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

* fix(agents): remove stale ollama compat import

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

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

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

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

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

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

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

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

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

Closes #58435

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

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

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

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

Closes #58357

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

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

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

* Changelog: add Ollama model picker fix

* Changelog: move Ollama fix entry to section tail

---------

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

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

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

* test: remove duplicate coverage files

* test: move helper coverage into extensions

* test: trim duplicate helper assertions

* test: remove cloudflare helper import from agent test

* test: align stale expectations with current main
2026-03-31 15:18:29 -07:00
Peter Steinberger d7e9d341cc fix: require npm auth for dist-tag mirror 2026-03-31 23:14:19 +01:00
Peter Steinberger 4f83409345 docs: add theme-aware sponsor logos 2026-04-01 07:14:05 +09:00
Peter Steinberger 091c6105a4 style(test): format skills install test 2026-03-31 23:11:53 +01:00
Peter Steinberger aa6cf87814 refactor(approvals): share origin target reconciliation 2026-03-31 23:11:53 +01:00
Peter Steinberger ddce362d34 refactor(approvals): share native delivery runtime 2026-03-31 23:11:53 +01:00
Peter Steinberger 5997317c09 docs: add NVIDIA sponsor logo 2026-04-01 06:59:06 +09:00
Peter Steinberger 9ea7e06460 build: bump version to 2026.4.1 2026-03-31 22:53:17 +01:00
Peter Steinberger 313a27d82f docs: update appcast for 2026.3.31 2026-03-31 22:51:49 +01:00
Vincent Koc 11318ef9b9 fix(status): align session_status with /status 2026-04-01 06:40:50 +09:00
Vincent Koc 94d72efedc fix(slack): accept bare approve fallback 2026-04-01 06:34:01 +09:00
Vincent Koc ee8baf6766 fix(reply): stop mention-wrapped status double replies 2026-04-01 06:32:34 +09:00
Peter Steinberger ad06d5ab4d build: reuse release preflight artifacts 2026-04-01 06:30:36 +09:00
Gustavo Madeira Santana 6679690737 fix(regression): restore diffs viewer toolbar buttons 2026-03-31 17:26:20 -04:00
Vincent Koc db0f7c2cd5 changelog: note discord and telegram approval UX fixes 2026-04-01 06:21:56 +09:00
Vincent Koc b73dd9b326 fix(approvals): suppress manual native approval narration 2026-04-01 06:21:56 +09:00
Vincent Koc 211b5a51af docs(changelog): note status followups 2026-04-01 06:16:15 +09:00
Vincent Koc e1d2b299f6 fix(reply): avoid double status replies 2026-04-01 06:12:47 +09:00
Peter Steinberger 968bc3d5b0 fix(ci): preserve workspace openclaw plugin links 2026-03-31 22:05:59 +01:00
Vincent Koc f85aba43a9 fix(approvals): restore native DM approval behavior 2026-04-01 06:02:04 +09:00
Vincent Koc fdad8ea3b0 fix(status): show agent-local task counts when session tasks are empty 2026-04-01 06:01:29 +09:00
Vincent Koc 93e2d0e3de build(bluebubbles): align openclaw dependency specifiers 2026-04-01 06:00:55 +09:00
Peter Steinberger 213a704b71 fix: unblock 2026.3.31 release preflight
Docker Release / validate_manual_backfill (push) Has been cancelled
Docker Release / approve_manual_backfill (push) Has been cancelled
Docker Release / build-amd64 (push) Has been cancelled
Docker Release / build-arm64 (push) Has been cancelled
Docker Release / create-manifest (push) Has been cancelled
2026-03-31 21:54:12 +01:00
Vincent Koc 44baf3bb2b build(pnpm): exclude openclaw from minimum release age 2026-04-01 05:50:35 +09:00
Vincent Koc 107fefd255 docs(changelog): note pi tui reply flush fix 2026-04-01 05:47:10 +09:00
Peter Steinberger 6f111516ef docs: refresh plugin sdk api baseline 2026-03-31 21:46:21 +01:00
Vincent Koc f425ea06bf fix(pi): flush message-boundary block replies on message end 2026-04-01 05:44:10 +09:00
Vincent Koc 0a7024e209 test(tasks): allow status command task-registry import 2026-04-01 05:42:45 +09:00
Peter Steinberger 418fa12dfa fix: make overload failover configurable 2026-03-31 21:34:35 +01:00
Peter Steinberger 2a60e34f2a build: prepare 2026.3.31 stable release 2026-03-31 21:32:38 +01:00
Peter Steinberger eee37bf836 fix(slack): prevent duplicate draft replies 2026-03-31 21:22:50 +01:00
Vincent Koc fc169215d7 fix(exec): deliver approval followups directly to chat 2026-04-01 05:22:35 +09:00
Vincent Koc 58ee76fc84 feat(status): show session task counts in slash status 2026-04-01 05:09:37 +09:00
Peter Steinberger 913e7d5eba fix: correct callable plugin sdk facades 2026-03-31 21:00:52 +01:00
Vincent Koc a4f45c55b2 fix(slack): restore callable directory facade exports 2026-04-01 04:51:36 +09:00
949 changed files with 28221 additions and 10600 deletions
@@ -66,7 +66,8 @@ Use this skill for Parallels guest workflows and smoke interpretation. Do not lo
- Windows global `npm install -g` phases can stay quiet for a minute or more even when healthy; inspect the phase log before calling it hung, and only treat it as a regression once the retry wrapper or timeout trips.
- Fresh Windows ref-mode onboard should use the same background PowerShell runner plus done-file/log-drain pattern as the npm-update helper, including startup materialization checks, host-side timeouts on short poll `prlctl exec` calls, and retry-on-poll-failure behavior for transient transport flakes.
- Fresh Windows ref-mode agent verification should set `OPENAI_API_KEY` in the PowerShell environment before invoking `openclaw.cmd agent`, for the same pairing-required fallback reason as macOS.
- The Windows upgrade smoke lane should restart the managed gateway after `upgrade.install-main` and before `upgrade.onboard-ref`, or the old process can keep the previous gateway token and fail `gateway-health` with `unauthorized: gateway token mismatch`.
- The standalone Windows upgrade smoke lane should stop the managed gateway after `upgrade.install-main` and before `upgrade.onboard-ref`. Restarting before onboard can leave the old process alive on the pre-onboard token while onboard rewrites `~/.openclaw/openclaw.json`, which then fails `gateway-health` with `unauthorized: gateway token mismatch`.
- If standalone Windows upgrade fails with a gateway token mismatch but `pnpm test:parallels:npm-update` passes, trust the mismatch as a standalone ref-onboard ordering bug first; the npm-update helper does not re-run ref-mode onboard on the same guest.
- Keep onboarding and status output ASCII-clean in logs; fancy punctuation becomes mojibake in current capture paths.
- If you hit an older run with `rc=255` plus an empty `fresh.install-main.log` or `upgrade.install-main.log`, treat it as a likely `prlctl exec` transport drop after guest start-up, not immediate proof of an npm/package failure.
@@ -79,8 +80,8 @@ Use this skill for Parallels guest workflows and smoke interpretation. Do not lo
- Fresh snapshots may be missing `curl`, and `apt-get update` can fail on clock skew. Bootstrap with `apt-get -o Acquire::Check-Date=false update` and install `curl ca-certificates`.
- Fresh `main` tgz smoke still needs the latest-release installer first because the snapshot has no Node or npm before bootstrap.
- This snapshot does not have a usable `systemd --user` session; managed daemon install is unsupported.
- `prlctl exec` reaps detached Linux child processes on this snapshot, so detached background gateway runs are not trustworthy smoke signals.
- Treat `gateway=skipped-no-detached-linux-gateway` plus `daemon=systemd-user-unavailable` as baseline on that Linux lane, not a regression.
- The Linux smoke now falls back to a manual `setsid openclaw gateway run --bind loopback --port 18789 --force` launch with `HOME=/root` and the provider secret exported, then verifies `gateway status --deep --require-rpc` when available.
- If Linux gateway bring-up fails, inspect `/tmp/openclaw-parallels-linux-gateway.log` in the guest phase logs first; the common failure mode is a missing provider secret in the launched gateway environment.
## Discord roundtrip
@@ -64,7 +64,8 @@ Use this skill for release and publish-time workflow. Keep ordinary development
Before tagging or publishing, run:
```bash
node --import tsx scripts/release-check.ts
pnpm build
pnpm ui:build
pnpm release:check
pnpm test:install:smoke
```
@@ -92,7 +93,7 @@ node --import tsx scripts/openclaw-npm-postpublish-verify.ts <published-version>
- Default release checks:
- `pnpm check`
- `pnpm build`
- `node --import tsx scripts/release-check.ts`
- `pnpm ui:build`
- `pnpm release:check`
- `OPENCLAW_INSTALL_SMOKE_SKIP_NONROOT=1 pnpm test:install:smoke`
- Check all release-related build surfaces touched by the release, not only the npm package.
@@ -119,6 +120,8 @@ node --import tsx scripts/openclaw-npm-postpublish-verify.ts <published-version>
- The npm workflow and the private mac publish workflow accept
`preflight_only=true` to run validation/build/package steps without uploading
public release assets.
- Both workflows also accept a prior successful preflight run id so a real
publish can promote the prepared artifacts without rebuilding them again.
- The private mac workflow also accepts `smoke_test_only=true` for branch-safe
workflow smoke tests that use ad-hoc signing, skip notarization, skip shared
appcast generation, and do not prove release readiness.
@@ -206,31 +209,38 @@ node --import tsx scripts/openclaw-npm-postpublish-verify.ts <published-version>
7. Create and push the git tag.
8. Create or refresh the matching GitHub release.
9. Start `.github/workflows/openclaw-npm-release.yml` with `preflight_only=true`
and wait for it to pass.
and wait for it to pass. Save that run id if you want the real publish to
reuse the prepared npm tarball.
10. Start `.github/workflows/macos-release.yml` in `openclaw/openclaw` and wait
for the public validation-only run to pass.
11. Start
`openclaw/releases-private/.github/workflows/openclaw-macos-publish.yml`
with `preflight_only=true` and wait for it to pass.
with `preflight_only=true` and wait for it to pass. Save that run id if you
want the real publish to reuse the notarized mac artifacts.
12. If any preflight or validation run fails, fix the issue on a new commit,
delete the tag and matching GitHub release, recreate them from the fixed
commit, and rerun all relevant preflights from scratch before continuing.
Never reuse old preflight results after the commit changes.
13. Start `.github/workflows/openclaw-npm-release.yml` with the same tag for
the real publish.
14. Wait for `npm-release` approval from `@openclaw/openclaw-release-managers`.
15. Start
the real publish. When the preflight run id is available, pass it via
`preflight_run_id` to skip the second npm rebuild.
14. Start the real private mac publish with the same tag. When the private
preflight run id is available, pass it via `preflight_run_id` to skip the
second mac build/sign/notarize cycle and promote those prepared artifacts
directly to the public release.
15. Wait for `npm-release` approval from `@openclaw/openclaw-release-managers`.
16. Start
`openclaw/releases-private/.github/workflows/openclaw-macos-publish.yml`
for the real publish and wait for success.
16. Verify the successful real private mac run uploaded the `.zip`, `.dmg`,
17. Verify the successful real private mac run uploaded the `.zip`, `.dmg`,
and `.dSYM.zip` artifacts to the existing GitHub release in
`openclaw/openclaw`.
17. For stable releases, download `macos-appcast-<tag>` from the successful
18. For stable releases, download `macos-appcast-<tag>` from the successful
private mac run, update `appcast.xml` on `main`, and verify the feed.
18. For beta releases, publish the mac assets but expect no shared production
19. For beta releases, publish the mac assets but expect no shared production
`appcast.xml` artifact and do not update the shared production feed unless a
separate beta feed exists.
19. After publish, verify npm and the attached release artifacts.
20. After publish, verify npm and the attached release artifacts.
## GHSA advisory work
-110
View File
@@ -1,110 +0,0 @@
name: CI Bun
on:
push:
branches: [main]
concurrency:
group: ci-bun-push-${{ github.run_id }}
cancel-in-progress: false
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
jobs:
preflight:
runs-on: blacksmith-16vcpu-ubuntu-2404
timeout-minutes: 20
outputs:
run_bun_checks: ${{ steps.manifest.outputs.run_bun_checks }}
bun_checks_matrix: ${{ steps.manifest.outputs.bun_checks_matrix }}
steps:
- name: Checkout
uses: actions/checkout@v6
with:
submodules: false
- name: Setup Node environment
uses: ./.github/actions/setup-node-env
with:
install-bun: "false"
install-deps: "false"
use-sticky-disk: "false"
- name: Build Bun CI manifest
id: manifest
env:
OPENCLAW_CI_DOCS_ONLY: "false"
OPENCLAW_CI_DOCS_CHANGED: "false"
OPENCLAW_CI_RUN_NODE: "true"
OPENCLAW_CI_RUN_MACOS: "false"
OPENCLAW_CI_RUN_ANDROID: "false"
OPENCLAW_CI_RUN_WINDOWS: "false"
OPENCLAW_CI_RUN_SKILLS_PYTHON: "false"
OPENCLAW_CI_HAS_CHANGED_EXTENSIONS: "false"
OPENCLAW_CI_CHANGED_EXTENSIONS_MATRIX: '{"include":[]}'
run: node scripts/ci-write-manifest-outputs.mjs --workflow ci-bun
build-bun-artifacts:
needs: [preflight]
if: needs.preflight.outputs.run_bun_checks == 'true'
runs-on: blacksmith-16vcpu-ubuntu-2404
timeout-minutes: 20
steps:
- name: Checkout
uses: actions/checkout@v6
with:
submodules: false
- name: Setup Node environment
uses: ./.github/actions/setup-node-env
with:
install-bun: "false"
use-sticky-disk: "false"
- name: Build A2UI bundle
run: pnpm canvas:a2ui:bundle
- name: Upload A2UI bundle artifact
uses: actions/upload-artifact@v7
with:
name: canvas-a2ui-bundle
path: src/canvas-host/a2ui/
include-hidden-files: true
retention-days: 1
bun-checks:
name: ${{ matrix.check_name }}
needs: [preflight, build-bun-artifacts]
if: needs.preflight.outputs.run_bun_checks == 'true'
runs-on: blacksmith-16vcpu-ubuntu-2404
timeout-minutes: 60
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.preflight.outputs.bun_checks_matrix) }}
steps:
- name: Checkout
uses: actions/checkout@v6
with:
submodules: false
- name: Setup Node environment
uses: ./.github/actions/setup-node-env
with:
install-bun: "true"
use-sticky-disk: "false"
- name: Download A2UI bundle artifact
uses: actions/download-artifact@v8
with:
name: canvas-a2ui-bundle
path: src/canvas-host/a2ui/
- name: Run Bun test shard
env:
SHARD_COUNT: ${{ matrix.shard_count }}
SHARD_INDEX: ${{ matrix.shard_index }}
shell: bash
run: |
set -euo pipefail
OPENCLAW_TEST_ISOLATE=1 bunx vitest run --config vitest.unit.config.ts --shard "$SHARD_INDEX/$SHARD_COUNT"
+65 -1
View File
@@ -12,6 +12,10 @@ on:
required: true
default: false
type: boolean
preflight_run_id:
description: Existing preflight workflow run id to promote without rebuilding
required: false
type: string
concurrency:
group: openclaw-npm-release-${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref }}
@@ -82,6 +86,7 @@ jobs:
run: pnpm ui:build
- name: Validate release tag and package metadata
if: ${{ inputs.preflight_run_id == '' }}
env:
RELEASE_TAG: ${{ inputs.tag }}
RELEASE_MAIN_REF: origin/main
@@ -97,6 +102,28 @@ jobs:
- name: Verify release contents
run: pnpm release:check
- name: Pack prepared npm tarball
id: packed_tarball
env:
OPENCLAW_PREPACK_PREPARED: "1"
run: |
set -euo pipefail
PACK_JSON="$(npm pack --json)"
echo "$PACK_JSON"
PACK_PATH="$(printf '%s\n' "$PACK_JSON" | node -e 'const chunks=[]; process.stdin.on("data", (chunk) => chunks.push(chunk)); process.stdin.on("end", () => { const parsed = JSON.parse(Buffer.concat(chunks).toString("utf8")); const first = Array.isArray(parsed) ? parsed[0] : null; if (!first || typeof first.filename !== "string" || !first.filename) { process.exit(1); } process.stdout.write(first.filename); });')"
if [[ -z "$PACK_PATH" || ! -f "$PACK_PATH" ]]; then
echo "npm pack did not produce a tarball file." >&2
exit 1
fi
echo "path=$PACK_PATH" >> "$GITHUB_OUTPUT"
- name: Upload prepared npm tarball
uses: actions/upload-artifact@v7
with:
name: openclaw-npm-preflight-${{ inputs.tag }}
path: ${{ steps.packed_tarball.outputs.path }}
if-no-files-found: error
validate_publish_dispatch_ref:
if: ${{ !inputs.preflight_only }}
runs-on: ubuntu-latest
@@ -120,6 +147,7 @@ jobs:
runs-on: ubuntu-latest
environment: npm-release
permissions:
actions: read
contents: read
id-token: write
steps:
@@ -159,13 +187,26 @@ jobs:
echo "Publishing openclaw@${PACKAGE_VERSION}"
- name: Download prepared npm tarball
if: ${{ inputs.preflight_run_id != '' }}
uses: actions/download-artifact@v8
with:
name: openclaw-npm-preflight-${{ inputs.tag }}
path: preflight-tarball
repository: ${{ github.repository }}
run-id: ${{ inputs.preflight_run_id }}
github-token: ${{ github.token }}
- name: Build
if: ${{ inputs.preflight_run_id == '' }}
run: pnpm build
- name: Build Control UI
if: ${{ inputs.preflight_run_id == '' }}
run: pnpm ui:build
- name: Validate release tag and package metadata
if: ${{ inputs.preflight_run_id == '' }}
env:
RELEASE_TAG: ${{ inputs.tag }}
RELEASE_MAIN_REF: origin/main
@@ -178,5 +219,28 @@ jobs:
git fetch --no-tags origin +refs/heads/main:refs/remotes/origin/main
pnpm release:openclaw:npm:check
- name: Resolve publish tarball
id: publish_tarball
if: ${{ inputs.preflight_run_id != '' }}
run: |
set -euo pipefail
TARBALL_PATH="$(find preflight-tarball -maxdepth 1 -type f -name '*.tgz' -print | sort | tail -n 1)"
if [[ -z "$TARBALL_PATH" ]]; then
echo "Prepared preflight tarball not found." >&2
ls -la preflight-tarball >&2 || true
exit 1
fi
echo "path=$TARBALL_PATH" >> "$GITHUB_OUTPUT"
- name: Publish
run: bash scripts/openclaw-npm-publish.sh --publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
OPENCLAW_PREPACK_PREPARED: "1"
run: |
set -euo pipefail
publish_target="${{ steps.publish_tarball.outputs.path }}"
if [[ -n "${publish_target}" ]]; then
publish_target="./${publish_target}"
fi
bash scripts/openclaw-npm-publish.sh --publish "${publish_target}"
+3
View File
@@ -211,4 +211,7 @@ jobs:
fi
- name: Publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
run: bash scripts/plugin-npm-publish.sh --publish "${{ matrix.plugin.packageDir }}"
+3
View File
@@ -33,6 +33,9 @@
"img",
"a",
"br",
"table",
"tr",
"td",
"details",
"summary",
"p",
+1
View File
@@ -181,6 +181,7 @@
- Run `pnpm test` (or `pnpm test:coverage`) before pushing when you touch logic.
- Write tests to clean up timers, env, globals, mocks, sockets, temp dirs, and module state so `--isolate=false` stays green.
- Test performance guardrail: do not put `vi.resetModules()` plus `await import(...)` in `beforeEach`/per-test loops for heavy modules unless module state truly requires it. Prefer static imports or one-time `beforeAll` imports, then reset mocks/runtime state directly.
- Test performance guardrail: if a test file uses stable `vi.mock(...)` hoists or other static module mocks, do not pair them with `vi.resetModules()` and a fresh `await import(...)` in every `beforeEach`. Import the heavy module once in `beforeAll`, then reset/prime mocks in `beforeEach` so Browser/Matrix-style hotspot tests do not pay the module graph cost per case.
- Test performance guardrail: inside an extension package, prefer a thin local seam (`./api.ts`, `./runtime-api.ts`, or a narrower local `*.runtime-api.ts`) over direct `openclaw/plugin-sdk/*` imports for internal production code. Keep local seams curated and lightweight; only reach for direct `plugin-sdk/*` imports when you are crossing a real package boundary or when no suitable local seam exists yet.
- Test performance guardrail: keep expensive runtime fallback work such as snapshotting, migration, installs, or bootstrap behind dedicated `*.runtime.ts` boundaries so tests can mock the seam instead of accidentally invoking real work.
- Test performance guardrail: for import-only/runtime-wrapper tests, keep the wrapper lazy. Do not eagerly load heavy verification/bootstrap/runtime modules at module top level if the exported function can import them on demand.
+180 -1
View File
@@ -4,6 +4,180 @@ Docs: https://docs.openclaw.ai
## Unreleased
### Changes
### Fixes
## 2026.4.1
### Changes
- Tasks/chat: add `/tasks` as a chat-native background task board for the current session, with recent task details and agent-local fallback counts when no linked tasks are visible. Related #54226. Thanks @vincentkoc.
- Web search/SearXNG: add the bundled SearXNG provider plugin for `web_search` with configurable host support. (#57317) Thanks @cgdusek.
- Amazon Bedrock/Guardrails: add Bedrock Guardrails support to the bundled provider. (#58588) Thanks @MikeORed.
- macOS/Voice Wake: add the Voice Wake option to trigger Talk Mode. (#58490) Thanks @SmoothExec.
- Feishu/comments: add a dedicated Drive comment-event flow with comment-thread context resolution, in-thread replies, and `feishu_drive` comment actions for document collaboration workflows. (#58497) Thanks @wittam-01.
- Gateway/webchat: make `chat.history` text truncation configurable with `gateway.webchat.chatHistoryMaxChars` and per-request `maxChars`, while preserving silent-reply filtering and existing default payload limits. (#58900)
- Agents/default params: add `agents.defaults.params` for global default provider parameters. (#58548) Thanks @lpender.
- Agents/failover: cap prompt-side and assistant-side same-provider auth-profile retries for rate-limit failures before cross-provider model fallback, add the `auth.cooldowns.rateLimitedProfileRotations` knob, and document the new fallback behavior. (#58707) Thanks @Forgely3D
- Cron/tools allowlist: add `openclaw cron --tools` for per-job tool allowlists. (#58504) Thanks @andyk-ms.
- Channels/session routing: move provider-specific session conversation grammar into plugin-owned session-key surfaces, preserving Telegram topic routing and Feishu scoped inheritance across bootstrap, model override, restart, and tool-policy paths.
- WhatsApp/reactions: add `reactionLevel` guidance for agent reactions. Thanks @mcaxtr.
- Telegram/errors: add configurable `errorPolicy` and `errorCooldownMs` controls so Telegram can suppress repeated delivery errors per account, chat, and topic without muting distinct failures. (#51914) Thanks @chinar-amrutkar
- ZAI/models: add `glm-5.1` and `glm-5v-turbo` to the bundled Z.AI provider catalog. (#58793) Thanks @tomsun28
- Agents/compaction: resolve `agents.defaults.compaction.model` consistently for manual `/compact` and other context-engine compaction paths, so engine-owned compaction uses the configured override model across runtime entrypoints. (#56710) Thanks @oliviareid-svg
### Fixes
- Chat/error replies: stop leaking raw provider/runtime failures into external chat channels, return a friendly retry message instead, and add a specific `/new` hint for Bedrock toolResult/toolUse session mismatches. (#58831) Thanks @ImLukeF.
- Gateway/reload: ignore startup config writes by persisted hash in the config reloader so generated auth tokens and seeded Control UI origins do not trigger a restart loop, while real `gateway.auth.*` edits still require restart. (#58678) Thanks @yelog
- Tasks/gateway: keep the task registry maintenance sweep from stalling the gateway event loop under synchronous SQLite pressure, so upgraded gateways stop hanging about a minute after startup. (#58670) Thanks @openperf
- Tasks/status: hide stale completed background tasks from `/status` and `session_status`, prefer live task context, and show recent failures only when no active work remains. (#58661) Thanks @vincentkoc
- Tasks/gateway: re-check the current task record before maintenance marks runs lost or prunes them, so a task heartbeat or cleanup update that lands during a sweep no longer gets overwritten by stale snapshot state.
- Exec/approvals: honor `exec-approvals.json` security defaults when inline or configured tool policy is unset, and keep Slack and Discord native approval handling aligned with inferred approvers and real channel enablement so remote exec stops falling into false approval timeouts and disabled states. Thanks @scoootscooob and @vincentkoc.
- Exec/approvals: make `allow-always` persist as durable user-approved trust instead of behaving like `allow-once`, reuse exact-command trust on shell-wrapper paths that cannot safely persist an executable allowlist entry, keep static allowlist entries from silently bypassing `ask:"always"`, and require explicit approval when Windows cannot build an allowlist execution plan instead of hard-dead-ending remote exec. Thanks @scoootscooob and @vincentkoc.
- Exec/cron: resolve isolated cron no-route approval dead-ends from the effective host fallback policy when trusted automation is allowed, and make `openclaw doctor` warn when `tools.exec` is broader than `~/.openclaw/exec-approvals.json` so stricter host-policy conflicts are explicit. Thanks @scoootscooob and @vincentkoc.
- Sessions/model switching: keep `/model` changes queued behind busy runs instead of interrupting the active turn, and retarget queued followups so later work picks up the new model as soon as the current turn finishes.
- Gateway/HTTP: skip failing HTTP request stages so one broken facade no longer forces every HTTP endpoint to return 500. (#58746) Thanks @yelog
- Gateway/nodes: stop pinning live node commands to the approved node-pair record. Node pairing remains a trust/token flow, while per-node `system.run` policy stays in that node's exec approvals config. Fixes #58824.
- WebChat/exec approvals: use native approval UI guidance in agent system prompts instead of telling agents to paste manual `/approve` commands in webchat sessions. Thanks @vincentkoc.
- Web UI/OpenResponses: preserve rewritten stream snapshots in webchat and keep OpenResponses final streamed text aligned when models rewind earlier output. (#58641) Thanks @neeravmakwana
- Discord/inbound media: pass Discord attachment and sticker downloads through the shared idle-timeout and worker-abort path so slow or stuck inbound media fetches stop hanging message processing. (#58593) Thanks @aquaright1
- Telegram/retries: keep non-idempotent sends on the strict safe-send path, retry wrapped pre-connect failures, and preserve `429` / `retry_after` backoff for safe delivery retries. (#51895) Thanks @chinar-amrutkar
- Telegram/exec approvals: route topic-aware exec approval followups through Telegram-owned threading and approval-target parsing, so forum-topic approvals stay in the originating topic instead of falling back to the root chat. (#58783)
- Telegram/local Bot API: preserve media MIME types for absolute-path downloads so local audio files still trigger transcription and other MIME-based handling. (#54603) Thanks @jzakirov
- Channels/WhatsApp: pass inbound message timestamp to model context so the AI can see when WhatsApp messages were sent. (#58590) Thanks @Maninae
- Channels/QQ Bot: keep `/bot-logs` export gated behind a truly explicit QQBot allowlist, rejecting wildcard and mixed wildcard entries while preserving the real framework command path. Thanks @vincentkoc.
- Channels/plugins: keep bundled channel plugins loadable from legacy `channels.<id>` config even under restrictive plugin allowlists, and make `openclaw doctor` warn only on real plugin blockers instead of misleading setup guidance. (#58873) Thanks @obviyus
- Plugins/bundled runtimes: restore externalized bundled plugin runtime dependency staging across packed installs, Docker builds, and local runtime staging so bundled plugins keep their declared runtime deps after the 2026.3.31 externalization change. (#58782)
- LINE/runtime: resolve the packaged runtime contract from the built `dist/plugins/runtime` layout so LINE channels start correctly again after global npm installs on `2026.3.31`. (#58799) Thanks @vincentkoc.
- MiniMax/plugins: auto-enable the bundled MiniMax plugin for API-key auth/config so MiniMax image generation and other plugin-owned capabilities load without manual plugin allowlisting. (#57127) Thanks @tars90percent.
- Ollama/model picker: show only Ollama models after provider selection in the CLI picker. (#55290) Thanks @Luckymingxuan.
- CDP/profiles: prefer `cdpPort` over stale WebSocket URLs so browser automation reconnects cleanly. (#58499) Thanks @Mlightsnow.
- Media/paths: resolve relative `MEDIA` paths against the agent workspace so local attachment references keep working. (#58624) Thanks @aquaright1.
- Memory/session indexing: keep full reindexes from skipping session transcripts when sync is triggered by `session-start` or `watch`, so restart-driven reindexes preserve session memory. (#39732) Thanks @upupc
- Memory/QMD: prefer `--mask` over `--glob` when creating QMD collections so default memory collections keep their intended patterns and stop colliding on restart. (#58643) Thanks @GitZhangChi.
- Subagents/tasks: keep subagent completion and cleanup from crashing when task-registry writes fail, so a corrupt or missing task row no longer takes down the gateway during lifecycle finalization. Thanks @vincentkoc.
- Sandbox/browser: compare browser runtime inspection against `agents.defaults.sandbox.browser.image` so `openclaw sandbox list --browser` stops reporting healthy browser containers as image mismatches. (#58759) Thanks @sandpile.
- Plugins/install: forward `--dangerously-force-unsafe-install` through archive and npm-spec plugin installs so the documented override reaches the security scanner on those install paths. (#58879) Thanks @ryanlee-gemini.
- Auto-reply/commands: strip inbound metadata before slash command detection so wrapped `/model`, `/new`, and `/status` commands are recognized. (#58725) Thanks @Mlightsnow.
- Agents/Anthropic: preserve thinking blocks and signatures across replay, cache-control patching, and context pruning so compacted Anthropic sessions continue working instead of failing on later turns. (#58916) Thanks @obviyus
- Agents/failover: unify structured and raw provider error classification so provider-specific `400`/`422` payloads no longer get forced into generic format failures before retry, billing, or compaction logic can inspect them. (#58856) Thanks @aaron-he-zhu.
- Auth profiles/store: coerce misplaced SecretRef objects out of plaintext `key` and `token` fields during store load so agents without ACP runtime stop crashing on `.trim()` after upgrade. (#58923) Thanks @openperf.
- ACPX/runtime: repair `queue owner unavailable` session recovery by replacing dead named sessions and resuming the backend session when ACPX exposes a stable session id, so the first ACP prompt no longer inherits a dead handle. (#58669) Thanks @neeravmakwana
- ACPX/runtime: retry dead-session queue-owner repair without `--resume-session` when the reported ACPX session id is stale, so recovery still creates a fresh named session instead of failing session init. Thanks @obviyus.
- Auth/OpenAI Codex: persist plugin-refreshed OAuth credentials to `auth-profiles.json` before returning them, so rotated Codex refresh tokens survive restart and stop falling into `refresh_token_reused` loops. (#53082)
- Discord/gateway: hand reconnect ownership back to Carbon, keep runtime status aligned with close/reconnect state, and force-stop sockets that open without reaching READY so Discord monitors recover promptly instead of waiting on stale health timeouts. (#59019) Thanks @obviyus
## 2026.3.31
### Breaking
- Nodes/exec: remove the duplicated `nodes.run` shell wrapper from the CLI and agent `nodes` tool so node shell execution always goes through `exec host=node`, keeping node-specific capabilities on `nodes invoke` and the dedicated media/location/notify actions.
- Plugin SDK: deprecate the legacy provider compat subpaths plus the older bundled provider setup and channel-runtime compatibility shims, emit migration warnings, and keep the current documented `openclaw/plugin-sdk/*` entrypoints plus local `api.ts` / `runtime-api.ts` barrels as the forward path ahead of a future major-release removal.
- Skills/install and Plugins/install: built-in dangerous-code `critical` findings and install-time scan failures now fail closed by default, so plugin installs and gateway-backed skill dependency installs that previously succeeded may now require an explicit dangerous override such as `--dangerously-force-unsafe-install` to proceed.
- Gateway/auth: `trusted-proxy` now rejects mixed shared-token configs, and local-direct fallback requires the configured token instead of implicitly authenticating same-host callers. Thanks @zhangning-agent, @jacobtomlinson, and @vincentkoc.
- Gateway/node commands: node commands now stay disabled until node pairing is approved, so device pairing alone is no longer enough to expose declared node commands. (#57777) Thanks @jacobtomlinson.
- Gateway/node events: node-originated runs now stay on a reduced trusted surface, so notification-driven or node-triggered flows that previously relied on broader host/session tool access may need adjustment. (#57691) Thanks @jacobtomlinson.
### Changes
- ACP/plugins: add an explicit default-off ACPX plugin-tools MCP bridge config, document the trust boundary, and harden the built-in bridge packaging/logging path so global installs and stdio MCP sessions work reliably. (#56867) Thanks @joe2643.
- Agents/LLM: add a configurable idle-stream timeout for embedded runner requests so stalled model streams abort cleanly instead of hanging until the broader run timeout fires. (#55072) Thanks @liuy.
- Agents/MCP: materialize bundle MCP tools with provider-safe names (`serverName__toolName`), support optional `streamable-http` transport selection plus per-server connection timeouts, and preserve real tool results from aborted/error turns unless truncation explicitly drops them. (#49505) Thanks @ziomancer.
- Android/notifications: add notification-forwarding controls with package filtering, quiet hours, rate limiting, and safer picker behavior for forwarded notification events. (#40175) Thanks @nimbleenigma.
- Background tasks: turn tasks into a real shared background-run control plane instead of ACP-only bookkeeping by unifying ACP, subagent, cron, and background CLI execution under one SQLite-backed ledger, routing detached lifecycle updates through the executor seam, adding audit/maintenance/status visibility, tightening auto-cleanup and lost-run recovery, improving task awareness in internal status/tool surfaces, and clarifying the split between heartbeat/main-session automation and detached scheduled runs. Thanks @mbelinky and @vincentkoc.
- Background tasks: add the first linear task flow control surface with `openclaw tasks list|show|cancel`, keep manual multi-task flows separate from one-task auto-sync flows, and surface doctor recovery hints for obviously orphaned or broken flow/task linkage. Thanks @mbelinky and @vincentkoc.
- Channels/QQ Bot: add QQ Bot as a bundled channel plugin with multi-account setup, SecretRef-aware credentials, slash commands, reminders, and media send/receive support. (#52986) Thanks @sliverp.
- Diffs: skip unused viewer-versus-file SSR preload work so `diffs` view-only and file-only runs do less render work while keeping mode outputs aligned. (#57909) thanks @gumadeiras.
- Tasks: add a minimal SQLite-backed task flow registry plus task-to-flow linkage scaffolding, so orchestrated work can start gaining a first-class parent record without changing current task delivery behavior. Thanks @mbelinky and @vincentkoc.
- Tasks: persist blocked state on one-task task flows and let the same flow reopen cleanly on retry, so blocked detached work can carry a parent-level reason and continue without fragmenting into a new job. Thanks @mbelinky and @vincentkoc.
- Tasks: route one-task ACP and subagent updates through a parent task-flow owner context, so detached work can emerge back through the intended parent thread/session instead of speaking only as a raw child task. Thanks @mbelinky and @vincentkoc.
- LINE/outbound media: add LINE image, video, and audio outbound sends on the LINE-specific delivery path, including explicit preview/tracking handling for videos while keeping generic media sends on the existing image-only route. (#45826) Thanks @masatohoshino.
- Matrix/history: add optional room history context for Matrix group triggers via `channels.matrix.historyLimit`, with per-agent watermarks and retry-safe snapshots so failed trigger retries do not drift into newer room messages. (#57022) thanks @chain710.
- Matrix/network: add explicit `channels.matrix.proxy` config for routing Matrix traffic through an HTTP(S) proxy, including account-level overrides and matching probe/runtime behavior. (#56931) thanks @patrick-yingxi-pan.
- Matrix/streaming: add draft streaming so partial Matrix replies update the same message in place instead of sending a new message for each chunk. (#56387) Thanks @jrusz.
- Matrix/threads: add per-DM `threadReplies` overrides and keep thread session isolation aligned with the effective room or DM thread policy from the triggering message onward. (#57995) thanks @teconomix.
- MCP: add remote HTTP/SSE server support for `mcp.servers` URL configs, including auth headers and safer config redaction for MCP credentials. (#50396) Thanks @dhananjai1729.
- Memory/QMD: add per-agent `memorySearch.qmd.extraCollections` so agents can opt into cross-agent session search without flattening every transcript collection into one shared QMD namespace. Thanks @vincentkoc.
- Microsoft Teams/member info: add a Graph-backed member info action so Teams automations and tools can resolve channel member details directly from Microsoft Graph. (#57528) Thanks @sudie-codes.
- Nostr/inbound DMs: verify inbound event signatures before pairing or sender-authorization side effects, so forged DM events no longer create pairing requests or trigger reply attempts. Thanks @smaeljaish771 and @vincentkoc.
- OpenAI/Responses: forward configured `text.verbosity` across Responses HTTP and WebSocket transports, surface it in `/status`, and keep per-agent verbosity precedence aligned with runtime behavior. (#47106) Thanks @merc1305 and @vincentkoc.
- Pi/Codex: add native Codex web search support for embedded Pi runs, including config/docs/wizard coverage and managed-tool suppression when native Codex search is active. (#46579) Thanks @Evizero.
- Slack/exec approvals: add native Slack approval routing and approver authorization so exec approval prompts can stay in Slack instead of falling back to the Web UI or terminal. Thanks @vincentkoc.
- TTS: Add structured provider diagnostics and fallback attempt analytics. (#57954) Thanks @joshavant.
- WhatsApp/reactions: agents can now react with emoji on incoming WhatsApp messages, enabling more natural conversational interactions like acknowledging a photo with ❤️ instead of typing a reply. Thanks @mcaxtr.
- Agents/BTW: force `/btw` side questions to disable provider reasoning so Anthropic adaptive-thinking sessions stop failing with `No BTW response generated`. Fixes #55376. Thanks @Catteres and @vincentkoc.
- CLI/onboarding: reset the remote gateway URL prompt to the safe loopback default after declining a discovered endpoint, so onboarding does not keep a previously rejected remote URL. (#57828)
- Agents/exec defaults: honor per-agent `tools.exec` defaults when no inline directive or session override is present, so configured exec host, security, ask, and node settings actually apply. (#57689)
- Sandbox/networking: sanitize SSH subprocess env vars through the shared sandbox policy and route marketplace archive downloads plus Ollama discovery, auth, and pull requests through the guarded fetch path so sandboxed execution and remote fetches follow the repo's trust boundaries. (#57848, #57850)
### Fixes
- Slack: stop retry-driven duplicate replies when draft-finalization edits fail ambiguously, and log configured allowlisted users/channels by readable name instead of raw IDs.
- Agents/OpenAI Responses: normalize raw bundled MCP tool schemas on the WebSocket/Responses path so bare-object, object-ish, and top-level union MCP tools no longer get rejected by OpenAI during tool registration. (#58299) Thanks @yelog.
- ACP/security: replace ACP's dangerous-tool name override with semantic approval classes, so only narrow readonly reads/searches can auto-approve while indirect exec-capable and control-plane tools always require explicit prompt approval. Thanks @vincentkoc.
- ACP/sessions_spawn: register ACP child runs for completion tracking and lifecycle cleanup, and make registration-failure cleanup explicitly best-effort so callers do not assume an already-started ACP turn was fully aborted. (#40885) Thanks @xaeon2026 and @vincentkoc.
- ACP/tasks: mark cleanly exited ACP runs as blocked when they end on deterministic write or authorization blockers, and wake the parent session with a follow-up instead of falsely reporting success.
- ACPX/runtime: derive the bundled ACPX expected version from the extension package metadata instead of hardcoding a separate literal, so plugin-local ACPX installs stop drifting out of health-check parity after version bumps. (#49089) Thanks @jiejiesks and @vincentkoc.
- Agents/Anthropic failover: treat Anthropic `api_error` payloads with `An unexpected error occurred while processing the response` as transient so retry/fallback can engage instead of surfacing a terminal failure. (#57441) Thanks @zijiess and @vincentkoc.
- Agents/compaction: keep late compaction-retry completions from double-resolving finished compaction futures, so interrupted or timed-out compactions stop surfacing spurious second-completion races. (#57796) Thanks @joshavant.
- Agents/disabled providers: make disabled providers disappear from default model selection and embedded provider fallback, while letting explicitly pinned disabled providers fail with a clear config error instead of silently taking traffic. (#57735) Thanks @rileybrown-dev and @vincentkoc.
- Agents/OAuth output: force exec-host OAuth output readers through the gateway fs policy so embedded gateway runs stop crashing when provider auth writes land outside the current sandbox workspace. (#58249) Thanks @joshavant.
- Agents/system prompt: fix `agent.name` interpolation in the embedded runtime system prompt and make provider/model fallback text reflect the effective runtime selection after start. (#57625) Thanks @StllrSvr and @vincentkoc.
- Android/device info: read the app's version metadata from the package manager instead of hidden APIs so Android 15+ onboarding and device info no longer fail to compile or report placeholder values. (#58126) Thanks @L3ER0Y.
- Android/pairing: stop appending duplicate push receiver entries to `gateway-service.conf` on repeated QR pairing and keep push registration bounded to the current successful pairing, so Android push delivery stays healthy across re-pair and token rotation. (#58256) Thanks @surrealroad.
- App install smoke: pin the latest-release lookup to `latest`, cache the first stable install version across the rerun, and relax prerelease package assertions so the Parallels smoke lane can validate stable-to-main upgrades even when `beta` moves ahead or the guest starts from an older stable. (#58177) Thanks @vincentkoc.
- Auth/profiles: keep the last successful config load in memory for the running process and refresh that snapshot on successful writes/reloads, so hot paths stop reparsing `openclaw.json` between watcher-driven swaps.
- Config/SecretRef + Control UI: harden SecretRef redaction round-trip restore, block unsafe raw fallback (force Form mode when raw is unavailable), and preflight submitted-config SecretRefs before config write RPC persistence. (#58044) Thanks @joshavant.
- Config/Telegram: migrate removed `channels.telegram.groupMentionsOnly` into `channels.telegram.groups[\"*\"].requireMention` on load so legacy configs no longer crash at startup. (#55336) thanks @jameslcowan.
- Config/update: stop `openclaw doctor` write-backs from persisting plugin-injected channel defaults, so `openclaw update` no longer seeds config keys that later break service refresh validation. (#56834) Thanks @openperf.
- Control UI/agents: auto-load agent workspace files on initial Files panel open, and populate overview model/workspace/fallbacks from effective runtime agent metadata so defaulted models no longer show as `Not set`. (#56637) Thanks @dxsx84.
- Control UI/slash commands: make `/steer` and `/redirect` work from the chat command palette with visible pending state for active-run `/steer`, correct redirected-run tracking, and a single canonical `/steer` entry in the command menu. (#54625) Thanks @fuller-stack-dev.
- Cron/announce: preserve all deliverable text payloads for announce mode instead of collapsing to the last chunk, so multi-line cron reports deliver in full to Telegram forum topics.
- Cron/isolated sessions: carry the full live-session provider, model, and auth-profile selection across retry restarts so cron jobs with model overrides no longer fail or loop on mid-run model-switch requests. (#57972) Thanks @issaba1.
- Diffs/config: preserve schema-shaped plugin config parsing from `diffsPluginConfigSchema.safeParse()`, so direct callers keep `defaults` and `security` sections instead of receiving flattened tool defaults. (#57904) Thanks @gumadeiras.
- Diffs: fall back to plain text when `lang` hints are invalid during diff render and viewer hydration, so bad or stale language values no longer break the diff viewer. (#57902) Thanks @gumadeiras.
- Discord/voice: enforce the same guild channel and member allowlist checks on spoken voice ingress before transcription, so joined voice channels no longer accept speech from users outside the configured Discord access policy. Thanks @cyjhhh and @vincentkoc.
- Docker/setup: force BuildKit for local image builds (including sandbox image builds) so `./docker-setup.sh` no longer fails on `RUN --mount=...` when hosts default to Docker's legacy builder. (#56681) Thanks @zhanghui-china.
- Docs/anchors: fix broken English docs links and make Mint anchor audits run against the English-source docs tree. (#57039) thanks @velvet-shark.
- Doctor/plugins: skip false Matrix legacy-helper warnings when no migration plans exist, and keep bundled `enabledByDefault` plugins in the gateway startup set. (#57931) Thanks @dinakars777.
- Exec approvals/macOS: unwrap `arch` and `xcrun` before deriving shell payloads and allow-always patterns, so wrapper approvals stay bound to the carried command instead of the outer carrier. Thanks @tdjackey and @vincentkoc.
- Exec approvals: unwrap `caffeinate` and `sandbox-exec` before persisting allow-always trust so later shell payload changes still require a fresh approval. Thanks @tdjackey and @vincentkoc.
- Exec/approvals: infer Discord and Telegram exec approvers from existing owner config when `execApprovals.approvers` is unset, extend the default approval window to 30 minutes, and clarify approval-unavailable guidance so approvals do not appear to silently disappear.
- Pi/TUI: flush message-boundary replies at `message_end` so turns stop looking stuck until the next nudge when the final reply was already ready. Thanks @vincentkoc.
- Status/tasks: fall back to same-agent task counts in `/status` when the current session has no linked tasks, keeping the default view useful without exposing other sessions' task details. Thanks @vincentkoc.
- Status/auto-reply: stop status-only turns from replying twice when inline `/status` handling already produced the reply, so Discord and other chat surfaces no longer emit duplicate status cards. Thanks @vincentkoc.
- Exec/approvals: keep `awk` and `sed` family binaries out of the low-risk `safeBins` fast path, and stop doctor profile scaffolding from treating them like ordinary custom filters. Thanks @vincentkoc.
- Exec/env: block proxy, TLS, and Docker endpoint env overrides in host execution so request-scoped commands cannot silently reroute outbound traffic or trust attacker-supplied certificate settings. Thanks @AntAISecurityLab.
- Exec/env: block Python package index override variables from request-scoped host exec environment sanitization so package fetches cannot be redirected through a caller-supplied index. Thanks @nexrin and @vincentkoc.
- Exec/node: stop gateway-side workdir fallback from rewriting explicit `host=node` cwd values to the gateway filesystem, so remote node exec approval and runs keep using the intended node-local directory. (#50961) Thanks @openperf.
- Exec/runtime: default implicit exec to `host=auto`, resolve that target to sandbox only when a sandbox runtime exists, keep explicit `host=sandbox` fail-closed without sandbox, and show `/exec` effective host state in runtime status/docs.
- Exec: fail closed when the implicit sandbox host has no sandbox runtime, and stop denied async approval followups from reusing prior command output from the same session. (#56800) Thanks @scoootscooob.
- Feishu/groups: keep quoted replies and topic bootstrap context aligned with group sender allowlists so only allowlisted thread messages seed agent context. Thanks @AntAISecurityLab and @vincentkoc.
- Gateway/attachments: offload large inbound images without leaking `media://` markers into text-only runs, preserve mixed attachment order for model input/transcripts, and fail closed when model image capability cannot be resolved. (#55513) Thanks @Syysean.
- Gateway/auth: keep shared-auth rate limiting active during WebSocket handshake attempts even when callers also send device-token candidates, so bogus device-token fields no longer suppress shared-secret brute-force tracking. Thanks @kexinoh and @vincentkoc.
- Gateway/auth: reject mismatched browser `Origin` headers on trusted-proxy HTTP operator requests while keeping origin-less headless proxy clients working. Thanks @AntAISecurityLab and @vincentkoc.
- Gateway/device tokens: disconnect active device sessions after token rotation so newly rotated credentials revoke existing live connections immediately instead of waiting for those sockets to close naturally. Thanks @zsxsoft and @vincentkoc.
- Gateway/health: carry webhook-vs-polling account mode from channel descriptors into runtime snapshots so passive channels like LINE and BlueBubbles skip false stale-socket health failures. (#47488) Thanks @karesansui-u.
- Gateway/pairing: restore QR bootstrap onboarding handoff so fresh `/pair qr` iPhone setup can auto-approve the initial node pairing, receive a reusable node device token, and stop retrying with spent bootstrap auth. (#58382) Thanks @ngutman.
- Gateway/OpenAI compatibility: accept flat Responses API function tool definitions on `/v1/responses` and preserve `strict` when normalizing hosted tools into the embedded runner, so spec-compliant clients like Codex no longer fail validation or silently lose strict tool enforcement. Thanks @malaiwah and @vincentkoc.
- Gateway/OpenAI HTTP: restore default operator scopes for bearer-authenticated requests that omit `x-openclaw-scopes`, so headless `/v1/chat/completions` and session-history callers work again after the recent method-scope hardening. (#57596) Thanks @openperf.
- Gateway/plugins: scope plugin-auth HTTP route runtime clients to read-only access and keep gateway-authenticated plugin routes on write scope, so plugin-owned webhook handlers do not inherit write-capable runtime access by default. Thanks @davidluzsilva and @vincentkoc.
- Gateway/SecretRef: resolve restart token drift checks with merged service/runtime env sources and hard-fail unsupported mutable SecretRef plus OAuth-profile combinations so restart warnings and policy enforcement match runtime behavior. (#58141) Thanks @joshavant.
- Gateway/tools HTTP: tighten HTTP tool-invoke authorization so owner-only tools stay off HTTP invoke paths. (#57773) Thanks @jacobtomlinson.
- Harden async approval followup delivery in webchat-only sessions (#57359) Thanks @joshavant.
- Heartbeat/auth: prevent exec-event heartbeat runs from inheriting owner-only tool access from the session delivery target, so node exec output stays on the non-owner tool surface even when the target session belongs to the owner. Thanks @AntAISecurityLab and @vincentkoc.
- Hooks/config: accept runtime channel plugin ids in `hooks.mappings[].channel` (for example `feishu`) instead of rejecting non-core channels during config validation. (#56226) Thanks @AiKrai001.
- Hooks/session routing: rebind hook-triggered `agent:` session keys to the actual target agent before isolated dispatch so dedicated hook agents keep their own session-scoped tool and plugin identity. Thanks @kexinoh and @vincentkoc.
- Host exec/env: block additional request-scoped env overrides that can redirect Docker endpoints, trust roots, compiler include paths, package resolution, or Python environment roots during approved host runs. Thanks @tdjackey and @vincentkoc.
- Image generation/build: write stable runtime alias files into `dist/` and route provider-auth runtime lookups through those aliases so image-generation providers keep resolving auth/runtime modules after rebuilds instead of crashing on missing hashed chunk files.
- iOS/Live Activities: mark the `ActivityKit` import in `LiveActivityManager.swift` as `@preconcurrency` so Xcode 26.4 / Swift 6 builds stop failing on strict concurrency checks. (#57180) Thanks @ngutman.
- LINE/ACP: add current-conversation binding and inbound binding-routing parity so `/acp spawn ... --thread here`, configured ACP bindings, and active conversation-bound ACP sessions work on LINE like the other conversation channels.
- LINE/markdown: preserve underscores inside Latin, Cyrillic, and CJK words when stripping markdown, while still removing standalone `_italic_` markers on the shared text-runtime path used by LINE and TTS. (#47465) Thanks @jackjin1997.
- Agents/failover: make overloaded same-provider retry count and retry delay configurable via `auth.cooldowns`, default to one retry with no delay, and document the model-fallback behavior.
- Ollama/model picker: include configured Ollama models in the opted-in non-PI-native model catalog path so Ollama onboarding shows available models directly after provider selection. (#55290) Thanks @Luckymingxuan.
## 2026.3.31-beta.1
### Breaking
@@ -22,7 +196,7 @@ Docs: https://docs.openclaw.ai
- Agents/MCP: materialize bundle MCP tools with provider-safe names (`serverName__toolName`), support optional `streamable-http` transport selection plus per-server connection timeouts, and preserve real tool results from aborted/error turns unless truncation explicitly drops them. (#49505) Thanks @ziomancer.
- Android/notifications: add notification-forwarding controls with package filtering, quiet hours, rate limiting, and safer picker behavior for forwarded notification events. (#40175) Thanks @nimbleenigma.
- Background tasks: turn tasks into a real shared background-run control plane instead of ACP-only bookkeeping by unifying ACP, subagent, cron, and background CLI execution under one SQLite-backed ledger, routing detached lifecycle updates through the executor seam, adding audit/maintenance/status visibility, tightening auto-cleanup and lost-run recovery, improving task awareness in internal status/tool surfaces, and clarifying the split between heartbeat/main-session automation and detached scheduled runs. Thanks @mbelinky and @vincentkoc.
- Background tasks: add the first linear task flow control surface with `openclaw 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. Thanks @mbelinky and @vincentkoc.
- Background tasks: add the first linear task flow control surface with `openclaw tasks list|show|cancel`, keep manual multi-task flows separate from one-task auto-sync flows, and surface doctor recovery hints for obviously orphaned or broken flow/task linkage. Thanks @mbelinky and @vincentkoc.
- Channels/QQ Bot: add QQ Bot as a bundled channel plugin with multi-account setup, SecretRef-aware credentials, slash commands, reminders, and media send/receive support. (#52986) Thanks @sliverp.
- Diffs: skip unused viewer-versus-file SSR preload work so `diffs` view-only and file-only runs do less render work while keeping mode outputs aligned. (#57909) thanks @gumadeiras.
- Tasks: add a minimal SQLite-backed task flow registry plus task-to-flow linkage scaffolding, so orchestrated work can start gaining a first-class parent record without changing current task delivery behavior. Thanks @mbelinky and @vincentkoc.
@@ -182,6 +356,8 @@ Docs: https://docs.openclaw.ai
- TTS: Restore 3.28 schema compatibility and fallback observability. (#57953) Thanks @joshavant.
- TUI/chat: keep optimistic outbound user messages visible during active runs by deferring local-run binding until the first gateway chat event reveals the real run id, preventing premature history reloads from wiping pending local sends. (#54722) Thanks @seanturner001.
- TUI/model picker: keep searchable `/model` and `/models` input mode from hijacking `j`/`k` as navigation keys, and harden width bounds under `m`-filtered model lists so search no longer crashes on long rows. (#30156) Thanks @briannicholls.
- Discord/exec approvals: stop mixing native interactive approvals with extra plain `/approve` narration, so Discord approval prompts stay button-driven instead of showing both manual and interactive flows. Thanks @vincentkoc.
- Telegram/exec approvals: normalize 1:1 approval routing and suppress duplicate approval prompts, so Telegram direct chats show a single working interactive approval instead of multiple conflicting approval messages. Thanks @vincentkoc.
- Voice Call/media stream: cap inbound WebSocket frame size before `start` validation so oversized pre-start frames are dropped before JSON parsing. Thanks @Kazamayc and @vincentkoc.
- Voice call/Plivo: pin stored callback bases to the configured public webhook URL so later call-control redirects stay on the intended origin even if webhook transport metadata differs. Thanks @zsxsoft and @vincentkoc.
- Web UI/markdown: stop bare auto-links from swallowing adjacent CJK text while preserving valid mixed-script path and query characters in rendered links. (#48410) Thanks @jnuyao.
@@ -264,6 +440,9 @@ 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.
- Control UI/config: keep sensitive raw config hidden by default, replace the blank blocked editor with an explicit reveal-to-edit state, and restore raw JSON editing without auto-exposing secrets. Fixes #55322.
- CLI/zsh: defer `compdef` registration until `compinit` is available so zsh completion loads cleanly with plugin managers and manual setups. (#56555)
+4 -1
View File
@@ -159,7 +159,10 @@ We are currently prioritizing:
- **Skills**: For skill contributions, head to [ClawHub](https://clawhub.ai/) — the community hub for OpenClaw skills.
- **Performance**: Optimizing token usage and compaction logic.
Check the [GitHub Issues](https://github.com/openclaw/openclaw/issues) for "good first issue" labels!
Check the [GitHub Issues](https://github.com/openclaw/openclaw/issues) for
["good first issue"](https://github.com/openclaw/openclaw/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22)
labels. If none are open, pick a small docs or bug issue and leave a quick comment saying
you'd like to work on it.
## Maintainers
+1 -1
View File
@@ -64,7 +64,7 @@ WORKDIR /app
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml .npmrc ./
COPY ui/package.json ./ui/package.json
COPY patches ./patches
COPY scripts/postinstall-bundled-plugins.mjs ./scripts/postinstall-bundled-plugins.mjs
COPY scripts/postinstall-bundled-plugins.mjs scripts/npm-runner.mjs ./scripts/
COPY --from=ext-deps /out/ ./${OPENCLAW_BUNDLED_PLUGIN_DIR}/
+44 -3
View File
@@ -32,9 +32,50 @@ New install? Start here: [Getting started](https://docs.openclaw.ai/start/gettin
## Sponsors
| OpenAI | Vercel | Blacksmith | Convex |
| ----------------------------------------------------------------- | ----------------------------------------------------------------- | ---------------------------------------------------------------------------- | --------------------------------------------------------------------- |
| [![OpenAI](docs/assets/sponsors/openai.svg)](https://openai.com/) | [![Vercel](docs/assets/sponsors/vercel.svg)](https://vercel.com/) | [![Blacksmith](docs/assets/sponsors/blacksmith.svg)](https://blacksmith.sh/) | [![Convex](docs/assets/sponsors/convex.svg)](https://www.convex.dev/) |
<table>
<tr>
<td align="center" width="20%">
<a href="https://openai.com/">
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/openclaw/openclaw/main/docs/assets/sponsors/openai-light.svg">
<img src="https://raw.githubusercontent.com/openclaw/openclaw/main/docs/assets/sponsors/openai.svg" alt="OpenAI" height="28">
</picture>
</a>
</td>
<td align="center" width="20%">
<a href="https://www.nvidia.com/">
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/openclaw/openclaw/main/docs/assets/sponsors/nvidia.svg">
<img src="https://raw.githubusercontent.com/openclaw/openclaw/main/docs/assets/sponsors/nvidia-dark.svg" alt="NVIDIA" height="28">
</picture>
</a>
</td>
<td align="center" width="20%">
<a href="https://vercel.com/">
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/openclaw/openclaw/main/docs/assets/sponsors/vercel-light.svg">
<img src="https://raw.githubusercontent.com/openclaw/openclaw/main/docs/assets/sponsors/vercel.svg" alt="Vercel" height="24">
</picture>
</a>
</td>
<td align="center" width="20%">
<a href="https://blacksmith.sh/">
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/openclaw/openclaw/main/docs/assets/sponsors/blacksmith-light.svg">
<img src="https://raw.githubusercontent.com/openclaw/openclaw/main/docs/assets/sponsors/blacksmith.svg" alt="Blacksmith" height="28">
</picture>
</a>
</td>
<td align="center" width="20%">
<a href="https://www.convex.dev/">
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/openclaw/openclaw/main/docs/assets/sponsors/convex-light.svg">
<img src="https://raw.githubusercontent.com/openclaw/openclaw/main/docs/assets/sponsors/convex.svg" alt="Convex" height="24">
</picture>
</a>
</td>
</tr>
</table>
**Subscriptions (OAuth):**
+117 -42
View File
@@ -2,6 +2,122 @@
<rss xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle" version="2.0">
<channel>
<title>OpenClaw</title>
<item>
<title>2026.3.31</title>
<pubDate>Tue, 31 Mar 2026 21:47:15 +0000</pubDate>
<link>https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml</link>
<sparkle:version>2026033190</sparkle:version>
<sparkle:shortVersionString>2026.3.31</sparkle:shortVersionString>
<sparkle:minimumSystemVersion>15.0</sparkle:minimumSystemVersion>
<description><![CDATA[<h2>OpenClaw 2026.3.31</h2>
<h3>Breaking</h3>
<ul>
<li>Nodes/exec: remove the duplicated <code>nodes.run</code> shell wrapper from the CLI and agent <code>nodes</code> tool so node shell execution always goes through <code>exec host=node</code>, keeping node-specific capabilities on <code>nodes invoke</code> and the dedicated media/location/notify actions.</li>
<li>Plugin SDK: deprecate the legacy provider compat subpaths plus the older bundled provider setup and channel-runtime compatibility shims, emit migration warnings, and keep the current documented <code>openclaw/plugin-sdk/*</code> entrypoints plus local <code>api.ts</code> / <code>runtime-api.ts</code> barrels as the forward path ahead of a future major-release removal.</li>
<li>Skills/install and Plugins/install: built-in dangerous-code <code>critical</code> findings and install-time scan failures now fail closed by default, so plugin installs and gateway-backed skill dependency installs that previously succeeded may now require an explicit dangerous override such as <code>--dangerously-force-unsafe-install</code> to proceed.</li>
<li>Gateway/auth: <code>trusted-proxy</code> now rejects mixed shared-token configs, and local-direct fallback requires the configured token instead of implicitly authenticating same-host callers. Thanks @zhangning-agent, @jacobtomlinson, and @vincentkoc.</li>
<li>Gateway/node commands: node commands now stay disabled until node pairing is approved, so device pairing alone is no longer enough to expose declared node commands. (#57777) Thanks @jacobtomlinson.</li>
<li>Gateway/node events: node-originated runs now stay on a reduced trusted surface, so notification-driven or node-triggered flows that previously relied on broader host/session tool access may need adjustment. (#57691) Thanks @jacobtomlinson.</li>
</ul>
<h3>Changes</h3>
<ul>
<li>ACP/plugins: add an explicit default-off ACPX plugin-tools MCP bridge config, document the trust boundary, and harden the built-in bridge packaging/logging path so global installs and stdio MCP sessions work reliably. (#56867) Thanks @joe2643.</li>
<li>Agents/LLM: add a configurable idle-stream timeout for embedded runner requests so stalled model streams abort cleanly instead of hanging until the broader run timeout fires. (#55072) Thanks @liuy.</li>
<li>Agents/MCP: materialize bundle MCP tools with provider-safe names (<code>serverName__toolName</code>), support optional <code>streamable-http</code> transport selection plus per-server connection timeouts, and preserve real tool results from aborted/error turns unless truncation explicitly drops them. (#49505) Thanks @ziomancer.</li>
<li>Android/notifications: add notification-forwarding controls with package filtering, quiet hours, rate limiting, and safer picker behavior for forwarded notification events. (#40175) Thanks @nimbleenigma.</li>
<li>Background tasks: turn tasks into a real shared background-run control plane instead of ACP-only bookkeeping by unifying ACP, subagent, cron, and background CLI execution under one SQLite-backed ledger, routing detached lifecycle updates through the executor seam, adding audit/maintenance/status visibility, tightening auto-cleanup and lost-run recovery, improving task awareness in internal status/tool surfaces, and clarifying the split between heartbeat/main-session automation and detached scheduled runs. Thanks @mbelinky and @vincentkoc.</li>
<li>Background tasks: add the first linear task flow control surface with <code>openclaw flows list|show|cancel</code>, keep manual multi-task flows separate from one-task auto-sync flows, and surface doctor recovery hints for obviously orphaned or broken flow/task linkage. Thanks @mbelinky and @vincentkoc.</li>
<li>Channels/QQ Bot: add QQ Bot as a bundled channel plugin with multi-account setup, SecretRef-aware credentials, slash commands, reminders, and media send/receive support. (#52986) Thanks @sliverp.</li>
<li>Diffs: skip unused viewer-versus-file SSR preload work so <code>diffs</code> view-only and file-only runs do less render work while keeping mode outputs aligned. (#57909) thanks @gumadeiras.</li>
<li>Tasks: add a minimal SQLite-backed task flow registry plus task-to-flow linkage scaffolding, so orchestrated work can start gaining a first-class parent record without changing current task delivery behavior. Thanks @mbelinky and @vincentkoc.</li>
<li>Tasks: persist blocked state on one-task task flows and let the same flow reopen cleanly on retry, so blocked detached work can carry a parent-level reason and continue without fragmenting into a new job. Thanks @mbelinky and @vincentkoc.</li>
<li>Tasks: route one-task ACP and subagent updates through a parent task-flow owner context, so detached work can emerge back through the intended parent thread/session instead of speaking only as a raw child task. Thanks @mbelinky and @vincentkoc.</li>
<li>LINE/outbound media: add LINE image, video, and audio outbound sends on the LINE-specific delivery path, including explicit preview/tracking handling for videos while keeping generic media sends on the existing image-only route. (#45826) Thanks @masatohoshino.</li>
<li>Matrix/history: add optional room history context for Matrix group triggers via <code>channels.matrix.historyLimit</code>, with per-agent watermarks and retry-safe snapshots so failed trigger retries do not drift into newer room messages. (#57022) thanks @chain710.</li>
<li>Matrix/network: add explicit <code>channels.matrix.proxy</code> config for routing Matrix traffic through an HTTP(S) proxy, including account-level overrides and matching probe/runtime behavior. (#56931) thanks @patrick-yingxi-pan.</li>
<li>Matrix/streaming: add draft streaming so partial Matrix replies update the same message in place instead of sending a new message for each chunk. (#56387) Thanks @jrusz.</li>
<li>Matrix/threads: add per-DM <code>threadReplies</code> overrides and keep thread session isolation aligned with the effective room or DM thread policy from the triggering message onward. (#57995) thanks @teconomix.</li>
<li>MCP: add remote HTTP/SSE server support for <code>mcp.servers</code> URL configs, including auth headers and safer config redaction for MCP credentials. (#50396) Thanks @dhananjai1729.</li>
<li>Memory/QMD: add per-agent <code>memorySearch.qmd.extraCollections</code> so agents can opt into cross-agent session search without flattening every transcript collection into one shared QMD namespace. Thanks @vincentkoc.</li>
<li>Microsoft Teams/member info: add a Graph-backed member info action so Teams automations and tools can resolve channel member details directly from Microsoft Graph. (#57528) Thanks @sudie-codes.</li>
<li>Nostr/inbound DMs: verify inbound event signatures before pairing or sender-authorization side effects, so forged DM events no longer create pairing requests or trigger reply attempts. Thanks @smaeljaish771 and @vincentkoc.</li>
<li>OpenAI/Responses: forward configured <code>text.verbosity</code> across Responses HTTP and WebSocket transports, surface it in <code>/status</code>, and keep per-agent verbosity precedence aligned with runtime behavior. (#47106) Thanks @merc1305 and @vincentkoc.</li>
<li>Pi/Codex: add native Codex web search support for embedded Pi runs, including config/docs/wizard coverage and managed-tool suppression when native Codex search is active. (#46579) Thanks @Evizero.</li>
<li>Slack/exec approvals: add native Slack approval routing and approver authorization so exec approval prompts can stay in Slack instead of falling back to the Web UI or terminal. Thanks @vincentkoc.</li>
<li>TTS: Add structured provider diagnostics and fallback attempt analytics. (#57954) Thanks @joshavant.</li>
<li>WhatsApp/reactions: agents can now react with emoji on incoming WhatsApp messages, enabling more natural conversational interactions like acknowledging a photo with ❤️ instead of typing a reply. Thanks @mcaxtr.</li>
<li>Agents/BTW: force <code>/btw</code> side questions to disable provider reasoning so Anthropic adaptive-thinking sessions stop failing with <code>No BTW response generated</code>. Fixes #55376. Thanks @Catteres and @vincentkoc.</li>
<li>CLI/onboarding: reset the remote gateway URL prompt to the safe loopback default after declining a discovered endpoint, so onboarding does not keep a previously rejected remote URL. (#57828)</li>
<li>Agents/exec defaults: honor per-agent <code>tools.exec</code> defaults when no inline directive or session override is present, so configured exec host, security, ask, and node settings actually apply. (#57689)</li>
<li>Sandbox/networking: sanitize SSH subprocess env vars through the shared sandbox policy and route marketplace archive downloads plus Ollama discovery, auth, and pull requests through the guarded fetch path so sandboxed execution and remote fetches follow the repo's trust boundaries. (#57848, #57850)</li>
</ul>
<h3>Fixes</h3>
<ul>
<li>Slack: stop retry-driven duplicate replies when draft-finalization edits fail ambiguously, and log configured allowlisted users/channels by readable name instead of raw IDs.</li>
<li>Agents/OpenAI Responses: normalize raw bundled MCP tool schemas on the WebSocket/Responses path so bare-object, object-ish, and top-level union MCP tools no longer get rejected by OpenAI during tool registration. (#58299) Thanks @yelog.</li>
<li>ACP/security: replace ACP's dangerous-tool name override with semantic approval classes, so only narrow readonly reads/searches can auto-approve while indirect exec-capable and control-plane tools always require explicit prompt approval. Thanks @vincentkoc.</li>
<li>ACP/sessions_spawn: register ACP child runs for completion tracking and lifecycle cleanup, and make registration-failure cleanup explicitly best-effort so callers do not assume an already-started ACP turn was fully aborted. (#40885) Thanks @xaeon2026 and @vincentkoc.</li>
<li>ACP/tasks: mark cleanly exited ACP runs as blocked when they end on deterministic write or authorization blockers, and wake the parent session with a follow-up instead of falsely reporting success.</li>
<li>ACPX/runtime: derive the bundled ACPX expected version from the extension package metadata instead of hardcoding a separate literal, so plugin-local ACPX installs stop drifting out of health-check parity after version bumps. (#49089) Thanks @jiejiesks and @vincentkoc.</li>
<li>Agents/Anthropic failover: treat Anthropic <code>api_error</code> payloads with <code>An unexpected error occurred while processing the response</code> as transient so retry/fallback can engage instead of surfacing a terminal failure. (#57441) Thanks @zijiess and @vincentkoc.</li>
<li>Agents/compaction: keep late compaction-retry completions from double-resolving finished compaction futures, so interrupted or timed-out compactions stop surfacing spurious second-completion races. (#57796) Thanks @joshavant.</li>
<li>Agents/disabled providers: make disabled providers disappear from default model selection and embedded provider fallback, while letting explicitly pinned disabled providers fail with a clear config error instead of silently taking traffic. (#57735) Thanks @rileybrown-dev and @vincentkoc.</li>
<li>Agents/OAuth output: force exec-host OAuth output readers through the gateway fs policy so embedded gateway runs stop crashing when provider auth writes land outside the current sandbox workspace. (#58249) Thanks @joshavant.</li>
<li>Agents/system prompt: fix <code>agent.name</code> interpolation in the embedded runtime system prompt and make provider/model fallback text reflect the effective runtime selection after start. (#57625) Thanks @StllrSvr and @vincentkoc.</li>
<li>Android/device info: read the app's version metadata from the package manager instead of hidden APIs so Android 15+ onboarding and device info no longer fail to compile or report placeholder values. (#58126) Thanks @L3ER0Y.</li>
<li>Android/pairing: stop appending duplicate push receiver entries to <code>gateway-service.conf</code> on repeated QR pairing and keep push registration bounded to the current successful pairing, so Android push delivery stays healthy across re-pair and token rotation. (#58256) Thanks @surrealroad.</li>
<li>App install smoke: pin the latest-release lookup to <code>latest</code>, cache the first stable install version across the rerun, and relax prerelease package assertions so the Parallels smoke lane can validate stable-to-main upgrades even when <code>beta</code> moves ahead or the guest starts from an older stable. (#58177) Thanks @vincentkoc.</li>
<li>Auth/profiles: keep the last successful config load in memory for the running process and refresh that snapshot on successful writes/reloads, so hot paths stop reparsing <code>openclaw.json</code> between watcher-driven swaps.</li>
<li>Config/SecretRef + Control UI: harden SecretRef redaction round-trip restore, block unsafe raw fallback (force Form mode when raw is unavailable), and preflight submitted-config SecretRefs before config write RPC persistence. (#58044) Thanks @joshavant.</li>
<li>Config/Telegram: migrate removed <code>channels.telegram.groupMentionsOnly</code> into <code>channels.telegram.groups[\"*\"].requireMention</code> on load so legacy configs no longer crash at startup. (#55336) thanks @jameslcowan.</li>
<li>Config/update: stop <code>openclaw doctor</code> write-backs from persisting plugin-injected channel defaults, so <code>openclaw update</code> no longer seeds config keys that later break service refresh validation. (#56834) Thanks @openperf.</li>
<li>Control UI/agents: auto-load agent workspace files on initial Files panel open, and populate overview model/workspace/fallbacks from effective runtime agent metadata so defaulted models no longer show as <code>Not set</code>. (#56637) Thanks @dxsx84.</li>
<li>Control UI/slash commands: make <code>/steer</code> and <code>/redirect</code> work from the chat command palette with visible pending state for active-run <code>/steer</code>, correct redirected-run tracking, and a single canonical <code>/steer</code> entry in the command menu. (#54625) Thanks @fuller-stack-dev.</li>
<li>Cron/announce: preserve all deliverable text payloads for announce mode instead of collapsing to the last chunk, so multi-line cron reports deliver in full to Telegram forum topics.</li>
<li>Cron/isolated sessions: carry the full live-session provider, model, and auth-profile selection across retry restarts so cron jobs with model overrides no longer fail or loop on mid-run model-switch requests. (#57972) Thanks @issaba1.</li>
<li>Diffs/config: preserve schema-shaped plugin config parsing from <code>diffsPluginConfigSchema.safeParse()</code>, so direct callers keep <code>defaults</code> and <code>security</code> sections instead of receiving flattened tool defaults. (#57904) Thanks @gumadeiras.</li>
<li>Diffs: fall back to plain text when <code>lang</code> hints are invalid during diff render and viewer hydration, so bad or stale language values no longer break the diff viewer. (#57902) Thanks @gumadeiras.</li>
<li>Discord/voice: enforce the same guild channel and member allowlist checks on spoken voice ingress before transcription, so joined voice channels no longer accept speech from users outside the configured Discord access policy. Thanks @cyjhhh and @vincentkoc.</li>
<li>Docker/setup: force BuildKit for local image builds (including sandbox image builds) so <code>./docker-setup.sh</code> no longer fails on <code>RUN --mount=...</code> when hosts default to Docker's legacy builder. (#56681) Thanks @zhanghui-china.</li>
<li>Docs/anchors: fix broken English docs links and make Mint anchor audits run against the English-source docs tree. (#57039) thanks @velvet-shark.</li>
<li>Doctor/plugins: skip false Matrix legacy-helper warnings when no migration plans exist, and keep bundled <code>enabledByDefault</code> plugins in the gateway startup set. (#57931) Thanks @dinakars777.</li>
<li>Exec approvals/macOS: unwrap <code>arch</code> and <code>xcrun</code> before deriving shell payloads and allow-always patterns, so wrapper approvals stay bound to the carried command instead of the outer carrier. Thanks @tdjackey and @vincentkoc.</li>
<li>Exec approvals: unwrap <code>caffeinate</code> and <code>sandbox-exec</code> before persisting allow-always trust so later shell payload changes still require a fresh approval. Thanks @tdjackey and @vincentkoc.</li>
<li>Exec/approvals: infer Discord and Telegram exec approvers from existing owner config when <code>execApprovals.approvers</code> is unset, extend the default approval window to 30 minutes, and clarify approval-unavailable guidance so approvals do not appear to silently disappear.</li>
<li>Pi/TUI: flush message-boundary replies at <code>message_end</code> so turns stop looking stuck until the next nudge when the final reply was already ready. Thanks @vincentkoc.</li>
<li>Exec/approvals: keep <code>awk</code> and <code>sed</code> family binaries out of the low-risk <code>safeBins</code> fast path, and stop doctor profile scaffolding from treating them like ordinary custom filters. Thanks @vincentkoc.</li>
<li>Exec/env: block proxy, TLS, and Docker endpoint env overrides in host execution so request-scoped commands cannot silently reroute outbound traffic or trust attacker-supplied certificate settings. Thanks @AntAISecurityLab.</li>
<li>Exec/env: block Python package index override variables from request-scoped host exec environment sanitization so package fetches cannot be redirected through a caller-supplied index. Thanks @nexrin and @vincentkoc.</li>
<li>Exec/node: stop gateway-side workdir fallback from rewriting explicit <code>host=node</code> cwd values to the gateway filesystem, so remote node exec approval and runs keep using the intended node-local directory. (#50961) Thanks @openperf.</li>
<li>Exec/runtime: default implicit exec to <code>host=auto</code>, resolve that target to sandbox only when a sandbox runtime exists, keep explicit <code>host=sandbox</code> fail-closed without sandbox, and show <code>/exec</code> effective host state in runtime status/docs.</li>
<li>Exec: fail closed when the implicit sandbox host has no sandbox runtime, and stop denied async approval followups from reusing prior command output from the same session. (#56800) Thanks @scoootscooob.</li>
<li>Feishu/groups: keep quoted replies and topic bootstrap context aligned with group sender allowlists so only allowlisted thread messages seed agent context. Thanks @AntAISecurityLab and @vincentkoc.</li>
<li>Gateway/attachments: offload large inbound images without leaking <code>media://</code> markers into text-only runs, preserve mixed attachment order for model input/transcripts, and fail closed when model image capability cannot be resolved. (#55513) Thanks @Syysean.</li>
<li>Gateway/auth: keep shared-auth rate limiting active during WebSocket handshake attempts even when callers also send device-token candidates, so bogus device-token fields no longer suppress shared-secret brute-force tracking. Thanks @kexinoh and @vincentkoc.</li>
<li>Gateway/auth: reject mismatched browser <code>Origin</code> headers on trusted-proxy HTTP operator requests while keeping origin-less headless proxy clients working. Thanks @AntAISecurityLab and @vincentkoc.</li>
<li>Gateway/device tokens: disconnect active device sessions after token rotation so newly rotated credentials revoke existing live connections immediately instead of waiting for those sockets to close naturally. Thanks @zsxsoft and @vincentkoc.</li>
<li>Gateway/health: carry webhook-vs-polling account mode from channel descriptors into runtime snapshots so passive channels like LINE and BlueBubbles skip false stale-socket health failures. (#47488) Thanks @karesansui-u.</li>
<li>Gateway/pairing: restore QR bootstrap onboarding handoff so fresh <code>/pair qr</code> iPhone setup can auto-approve the initial node pairing, receive a reusable node device token, and stop retrying with spent bootstrap auth. (#58382) Thanks @ngutman.</li>
<li>Gateway/OpenAI compatibility: accept flat Responses API function tool definitions on <code>/v1/responses</code> and preserve <code>strict</code> when normalizing hosted tools into the embedded runner, so spec-compliant clients like Codex no longer fail validation or silently lose strict tool enforcement. Thanks @malaiwah and @vincentkoc.</li>
<li>Gateway/OpenAI HTTP: restore default operator scopes for bearer-authenticated requests that omit <code>x-openclaw-scopes</code>, so headless <code>/v1/chat/completions</code> and session-history callers work again after the recent method-scope hardening. (#57596) Thanks @openperf.</li>
<li>Gateway/plugins: scope plugin-auth HTTP route runtime clients to read-only access and keep gateway-authenticated plugin routes on write scope, so plugin-owned webhook handlers do not inherit write-capable runtime access by default. Thanks @davidluzsilva and @vincentkoc.</li>
<li>Gateway/SecretRef: resolve restart token drift checks with merged service/runtime env sources and hard-fail unsupported mutable SecretRef plus OAuth-profile combinations so restart warnings and policy enforcement match runtime behavior. (#58141) Thanks @joshavant.</li>
<li>Gateway/tools HTTP: tighten HTTP tool-invoke authorization so owner-only tools stay off HTTP invoke paths. (#57773) Thanks @jacobtomlinson.</li>
<li>Harden async approval followup delivery in webchat-only sessions (#57359) Thanks @joshavant.</li>
<li>Heartbeat/auth: prevent exec-event heartbeat runs from inheriting owner-only tool access from the session delivery target, so node exec output stays on the non-owner tool surface even when the target session belongs to the owner. Thanks @AntAISecurityLab and @vincentkoc.</li>
<li>Hooks/config: accept runtime channel plugin ids in <code>hooks.mappings[].channel</code> (for example <code>feishu</code>) instead of rejecting non-core channels during config validation. (#56226) Thanks @AiKrai001.</li>
<li>Hooks/session routing: rebind hook-triggered <code>agent:</code> session keys to the actual target agent before isolated dispatch so dedicated hook agents keep their own session-scoped tool and plugin identity. Thanks @kexinoh and @vincentkoc.</li>
<li>Host exec/env: block additional request-scoped env overrides that can redirect Docker endpoints, trust roots, compiler include paths, package resolution, or Python environment roots during approved host runs. Thanks @tdjackey and @vincentkoc.</li>
<li>Image generation/build: write stable runtime alias files into <code>dist/</code> and route provider-auth runtime lookups through those aliases so image-generation providers keep resolving auth/runtime modules after rebuilds instead of crashing on missing hashed chunk files.</li>
<li>iOS/Live Activities: mark the <code>ActivityKit</code> import in <code>LiveActivityManager.swift</code> as <code>@preconcurrency</code> so Xcode 26.4 / Swift 6 builds stop failing on strict concurrency checks. (#57180) Thanks @ngutman.</li>
<li>LINE/ACP: add current-conversation binding and inbound binding-routing parity so <code>/acp spawn ... --thread here</code>, configured ACP bindings, and active conversation-bound ACP sessions work on LINE like the other conversation channels.</li>
<li>LINE/markdown: preserve underscores inside Latin, Cyrillic, and CJK words when stripping markdown, while still removing standalone <code>_italic_</code> markers on the shared text-runtime path used by LINE and TTS. (#47465) Thanks @jackjin1997.</li>
<li>Agents/failover: make overloaded same-provider retry count and retry delay configurable via <code>auth.cooldowns</code>, default to one retry with no delay, and document the model-fallback behavior.</li>
</ul>
<p><a href="https://github.com/openclaw/openclaw/blob/main/CHANGELOG.md">View full changelog</a></p>
]]></description>
<enclosure url="https://github.com/openclaw/openclaw/releases/download/v2026.3.31/OpenClaw-2026.3.31.zip" length="25820093" type="application/octet-stream" sparkle:edSignature="NjpuH/j7OaNASEatBTpQ4uQy6+oUNq/lIwjrY69rJfkgGSk3/kU8vgxo9osjSgx034m7TpuZvWyulu57OBsQCg=="/>
</item>
<item>
<title>2026.3.28</title>
<pubDate>Sun, 29 Mar 2026 02:10:40 +0000</pubDate>
@@ -195,46 +311,5 @@
]]></description>
<enclosure url="https://github.com/openclaw/openclaw/releases/download/v2026.3.24/OpenClaw-2026.3.24.zip" length="24749233" type="application/octet-stream" sparkle:edSignature="gLm2VvI+PPEnNy4klYSs9WmZLkJTF5BcfFparrtPdnmeE4xgc8kFfICg445I039ev9/A6xGav7pm08reUHDcAg=="/>
</item>
<item>
<title>2026.3.23</title>
<pubDate>Mon, 23 Mar 2026 16:59:51 -0700</pubDate>
<link>https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml</link>
<sparkle:version>2026032390</sparkle:version>
<sparkle:shortVersionString>2026.3.23</sparkle:shortVersionString>
<sparkle:minimumSystemVersion>15.0</sparkle:minimumSystemVersion>
<description><![CDATA[<h2>OpenClaw 2026.3.23</h2>
<h3>Breaking</h3>
<h3>Changes</h3>
<h3>Fixes</h3>
<ul>
<li>Browser/Chrome MCP: wait for existing-session browser tabs to become usable after attach instead of treating the initial Chrome MCP handshake as ready, which reduces user-profile timeouts and repeated consent churn on macOS Chrome attach flows. Fixes #52930. Thanks @vincentkoc.</li>
<li>Browser/CDP: reuse an already-running loopback browser after a short initial reachability miss instead of immediately falling back to relaunch detection, which fixes second-run browser start/open regressions on slower headless Linux setups. Fixes #53004. Thanks @vincentkoc.</li>
<li>ClawHub/macOS auth: honor macOS auth config and XDG auth paths for saved ClawHub credentials, so <code>openclaw skills ...</code> and gateway skill browsing keep using the signed-in auth state instead of silently falling back to unauthenticated mode. Fixes #53034.</li>
<li>ClawHub/macOS: read the local ClawHub login from the macOS Application Support path and still honor XDG config on macOS, so skill browsing uses the logged-in token on both default and XDG-style setups. Fixes #52949. Thanks @scoootscooob.</li>
<li>ClawHub/skills: resolve the local ClawHub auth token for gateway skill browsing and switch browse-all requests to search so ClawControl stops falling into unauthenticated 429s and empty authenticated skill lists. Fixes #52949. Thanks @vincentkoc.</li>
<li>Plugins/message tool: make Discord <code>components</code> and Slack <code>blocks</code> optional again, and route Feishu <code>message(..., media=...)</code> sends through the outbound media path, so pin/unpin/react flows stop failing schema validation and Feishu file/image attachments actually send. Fixes #52970 and #52962. Thanks @vincentkoc.</li>
<li>Gateway/model pricing: stop <code>openrouter/auto</code> pricing refresh from recursing indefinitely during bootstrap, so OpenRouter auto routes can populate cached pricing and <code>usage.cost</code> again. Fixes #53035. Thanks @vincentkoc.</li>
<li>Mistral/models: lower bundled Mistral max-token defaults to safe output budgets and teach <code>openclaw doctor --fix</code> to repair old persisted Mistral provider configs that still carry context-sized output limits, avoiding deterministic Mistral 422 rejects on fresh and existing setups. Fixes #52599. Thanks @vincentkoc.</li>
<li>Agents/web_search: use the active runtime <code>web_search</code> provider instead of stale/default selection, so agent turns keep hitting the provider you actually configured. Fixes #53020. Thanks @jzakirov.</li>
<li>Models/OpenAI Codex OAuth: bootstrap the env-configured HTTP/HTTPS proxy dispatcher on the stored-credential refresh path before token renewal runs, so expired Codex OAuth profiles can refresh successfully in proxy-required environments instead of locking users out after the first token expiry.</li>
<li>Plugins/memory-lancedb: bootstrap LanceDB into plugin runtime state on first use when the bundled npm install does not already have it, so <code>plugins.slots.memory="memory-lancedb"</code> works again after global npm installs without moving LanceDB into OpenClaw core dependencies. Fixes #26100.</li>
<li>Config/plugins: treat stale unknown <code>plugins.allow</code> ids as warnings instead of fatal config errors, so recovery commands like <code>plugins install</code>, <code>doctor --fix</code>, and <code>status</code> still run when a plugin is missing locally. Fixes #52992. Thanks @vincentkoc.</li>
<li>Doctor/WhatsApp: stop auto-enable from appending built-in channel ids like <code>whatsapp</code> to <code>plugins.allow</code>, so <code>openclaw doctor --fix</code> no longer writes schema-invalid plugin allowlist entries when repairing built-in channels. Fixes #52931. Thanks @vincentkoc.</li>
<li>Telegram/auto-reply: preserve same-chat inbound debounce order without stranding stale busy-session followups, and keep same-key overflow turns ordered when tracked debounce keys are saturated. (#52998) Thanks @osolmaz.</li>
<li>Discord/commands: return an explicit unauthorized reply for privileged native slash commands instead of falling through to Discord's misleading generic completion when auth gates reject the sender. Fixes #53041. Thanks @scoootscooob.</li>
<li>Channels/catalog: let external channel catalogs override shipped fallback metadata and honor overridden npm specs during channel setup, so custom channel catalogs no longer fall back to bundled packages when a channel id matches. (#52988)</li>
<li>Voice-call/Plivo: stabilize Plivo v2 replay keys so webhook retries and replay protection stop colliding on valid follow-up deliveries.</li>
<li>Agents/skills: prefer the active resolved runtime snapshot for embedded skill config and env injection, so <code>skills.entries.<skill>.apiKey</code> SecretRefs resolve correctly during embedded startup instead of failing on raw source config. Fixes #53098. Thanks @vincentkoc.</li>
<li>Agents/subagents: recheck timed-out worker waits against the latest runtime snapshot before sending completion events, so fast-finishing workers stop being reported as timed out when they actually succeeded. Fixes #53106. Thanks @vincentkoc.</li>
<li>Agents/Anthropic: preserve latest assistant thinking and redacted-thinking block ordering during transcript image sanitization so follow-up turns do not trip Anthropic's unmodified-thinking validation. (#52961) Thanks @vincentkoc.</li>
<li>Gateway/probe: stop successful gateway handshakes from timing out as unreachable while post-connect detail RPCs are still loading, so slow devices report a reachable RPC failure instead of a false negative dead gateway. Fixes #52927. Thanks @vincentkoc.</li>
<li>Gateway/supervision: stop lock conflicts from crash-looping under launchd and systemd by keeping the duplicate process in a retry wait instead of exiting as a failure while another healthy gateway still owns the lock. Fixes #52922. Thanks @vincentkoc.</li>
<li>Gateway/auth: require auth for canvas routes and admin scope for agent session reset, so anonymous canvas access and non-admin reset requests fail closed.</li>
<li>Release/install: keep previously released bundled plugins and Control UI assets in published openclaw npm installs, and fail release checks when those shipped artifacts are missing. Thanks @vincentkoc.</li>
</ul>
<p><a href="https://github.com/openclaw/openclaw/blob/main/CHANGELOG.md">View full changelog</a></p>
]]></description>
<enclosure url="https://github.com/openclaw/openclaw/releases/download/v2026.3.23/OpenClaw-2026.3.23.zip" length="24522883" type="application/octet-stream" sparkle:edSignature="ptBgHYLBqq/TSdONYCfIB5d6aP/ij/9G0gYQ5mJI9jf8Y31sbQIh5CqpJVxEEWLTMIGQKsHQir/kXZjtRvvZAg=="/>
</item>
</channel>
</rss>
</rss>
+2 -2
View File
@@ -65,8 +65,8 @@ android {
applicationId = "ai.openclaw.app"
minSdk = 31
targetSdk = 36
versionCode = 2026033101
versionName = "2026.3.31-beta.1"
versionCode = 2026040101
versionName = "2026.4.1"
ndk {
// Support all major ABIs — native libs are tiny (~47 KB per ABI)
abiFilters += listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
+3 -3
View File
@@ -1,8 +1,8 @@
// Shared iOS version defaults.
// Generated overrides live in build/Version.xcconfig (git-ignored).
OPENCLAW_GATEWAY_VERSION = 2026.3.31-beta.1
OPENCLAW_MARKETING_VERSION = 2026.3.31
OPENCLAW_BUILD_VERSION = 2026033101
OPENCLAW_GATEWAY_VERSION = 2026.4.1
OPENCLAW_MARKETING_VERSION = 2026.4.1
OPENCLAW_BUILD_VERSION = 2026040101
#include? "../build/Version.xcconfig"
+3 -3
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.31-beta.1` becomes:
- `CFBundleShortVersionString = 2026.3.31`
- `CFBundleVersion = next TestFlight build number for 2026.3.31`
- A root version like `2026.4.1-beta.1` becomes:
- `CFBundleShortVersionString = 2026.4.1`
- `CFBundleVersion = next TestFlight build number for 2026.4.1`
Required env for beta builds:
@@ -136,6 +136,17 @@ final class AppState {
forKey: voicePushToTalkEnabledKey) } }
}
var voiceWakeTriggersTalkMode: Bool {
didSet {
self.ifNotPreview {
UserDefaults.standard.set(self.voiceWakeTriggersTalkMode, forKey: voiceWakeTriggersTalkModeKey)
if self.swabbleEnabled {
Task { await VoiceWakeRuntime.shared.refresh(state: self) }
}
}
}
}
var talkEnabled: Bool {
didSet {
self.ifNotPreview {
@@ -275,6 +286,8 @@ final class AppState {
.stringArray(forKey: voiceWakeAdditionalLocalesKey) ?? []
self.voicePushToTalkEnabled = UserDefaults.standard
.object(forKey: voicePushToTalkEnabledKey) as? Bool ?? false
self.voiceWakeTriggersTalkMode = UserDefaults.standard
.object(forKey: voiceWakeTriggersTalkModeKey) as? Bool ?? false
self.talkEnabled = UserDefaults.standard.bool(forKey: talkEnabledKey)
self.seamColorHex = nil
if let storedHeartbeats = UserDefaults.standard.object(forKey: heartbeatsEnabledKey) as? Bool {
@@ -22,6 +22,7 @@ let voiceWakeMicNameKey = "openclaw.voiceWakeMicName"
let voiceWakeLocaleKey = "openclaw.voiceWakeLocaleID"
let voiceWakeAdditionalLocalesKey = "openclaw.voiceWakeAdditionalLocaleIDs"
let voicePushToTalkEnabledKey = "openclaw.voicePushToTalkEnabled"
let voiceWakeTriggersTalkModeKey = "openclaw.voiceWakeTriggersTalkMode"
let talkEnabledKey = "openclaw.talkEnabled"
let iconOverrideKey = "openclaw.iconOverride"
let connectionModeKey = "openclaw.connectionMode"
@@ -618,11 +618,13 @@ extension GatewayConnection {
func chatHistory(
sessionKey: String,
limit: Int? = nil,
maxChars: Int? = nil,
timeoutMs: Int? = nil) async throws -> OpenClawChatHistoryPayload
{
let resolvedKey = self.canonicalizeSessionKey(sessionKey)
var params: [String: AnyCodable] = ["sessionKey": AnyCodable(resolvedKey)]
if let limit { params["limit"] = AnyCodable(limit) }
if let maxChars { params["maxChars"] = AnyCodable(maxChars) }
let timeout = timeoutMs.map { Double($0) }
return try await self.requestDecoded(
method: .chatHistory,
@@ -15,9 +15,9 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>2026.3.31-beta.1</string>
<string>2026.4.1</string>
<key>CFBundleVersion</key>
<string>2026033101</string>
<string>2026040101</string>
<key>CFBundleIconFile</key>
<string>OpenClaw</string>
<key>CFBundleURLTypes</key>
@@ -18,6 +18,12 @@ final class TalkModeController {
TalkOverlayController.shared.dismiss()
}
await TalkModeRuntime.shared.setEnabled(enabled)
// Resume voice wake listener *after* TalkMode audio is fully torn down.
// Check swabbleEnabled (not voiceWakeTriggersTalkMode) so the paused wake listener
// resumes even if the user toggled "Trigger Talk Mode" off during the session.
if !enabled, AppStateStore.shared.swabbleEnabled {
Task { await VoiceWakeRuntime.shared.refresh(state: AppStateStore.shared) }
}
}
func updatePhase(_ phase: TalkModePhase) {
@@ -335,6 +335,11 @@ actor TalkModeRuntime {
self.lastHeard = nil
self.phase = .thinking
await MainActor.run { TalkModeController.shared.updatePhase(.thinking) }
// Play "send" chime when the user's speech is finalized and about to be sent
let sendChime = await MainActor.run { AppStateStore.shared.voiceWakeSendChime }
if sendChime != .none {
await MainActor.run { VoiceWakeChimePlayer.play(sendChime, reason: "talk.send") }
}
await self.stopRecognition()
await self.sendAndSpeak(text)
}
@@ -456,6 +461,7 @@ actor TalkModeRuntime {
private func playAssistant(text: String) async {
guard let input = await self.preparePlaybackInput(text: text) else { return }
switch Self.playbackPlan(apiKey: input.apiKey, voiceId: input.voiceId) {
case let .elevenLabsThenSystemVoice(apiKey, voiceId):
do {
@@ -82,6 +82,7 @@ actor VoiceWakeRuntime {
let localeID: String?
let triggerChime: VoiceWakeChime
let sendChime: VoiceWakeChime
let triggersTalkMode: Bool
}
private struct RecognitionUpdate {
@@ -100,7 +101,8 @@ actor VoiceWakeRuntime {
micID: state.voiceWakeMicID.isEmpty ? nil : state.voiceWakeMicID,
localeID: state.voiceWakeLocaleID.isEmpty ? nil : state.voiceWakeLocaleID,
triggerChime: state.voiceWakeTriggerChime,
sendChime: state.voiceWakeSendChime)
sendChime: state.voiceWakeSendChime,
triggersTalkMode: state.voiceWakeTriggersTalkMode)
return (enabled, config)
}
@@ -529,6 +531,21 @@ actor VoiceWakeRuntime {
}
private func beginCapture(command: String, triggerEndTime: TimeInterval?, config: RuntimeConfig) async {
// When "Trigger Talk Mode" is enabled, skip the capture/overlay flow entirely
// and activate Talk Mode immediately. Talk Mode handles its own STT pipeline.
// Pause the wake listener to avoid two audio pipelines competing on the mic
// (mirrors the push-to-talk coordination pattern).
if config.triggersTalkMode {
self.logger.info("voicewake trigger -> activating Talk Mode (skipping capture)")
DiagnosticsFileLog.shared.log(category: "voicewake.runtime", event: "triggerTalkMode")
if config.triggerChime != .none {
await MainActor.run { VoiceWakeChimePlayer.play(config.triggerChime, reason: "voicewake.trigger") }
}
self.pauseForPushToTalk()
await AppStateStore.shared.setTalkEnabled(true)
return
}
self.listeningState = .voiceWake
self.isCapturing = true
DiagnosticsFileLog.shared.log(category: "voicewake.runtime", event: "beginCapture")
@@ -53,6 +53,16 @@ struct VoiceWakeSettings: View {
binding: self.voiceWakeBinding)
.disabled(!voiceWakeSupported)
SettingsToggleRow(
title: "Trigger Talk Mode",
subtitle: """
When a wake phrase is detected, activate Talk Mode for a full voice \
conversation (STT, LLM response, TTS playback) instead of sending a \
text message to the chat.
""",
binding: self.$state.voiceWakeTriggersTalkMode)
.disabled(!self.state.swabbleEnabled)
SettingsToggleRow(
title: "Hold Right Option to talk",
subtitle: """
@@ -3710,18 +3710,22 @@ public struct DevicePairResolvedEvent: Codable, Sendable {
public struct ChatHistoryParams: Codable, Sendable {
public let sessionkey: String
public let limit: Int?
public let maxchars: Int?
public init(
sessionkey: String,
limit: Int?)
limit: Int?,
maxchars: Int?)
{
self.sessionkey = sessionkey
self.limit = limit
self.maxchars = maxchars
}
private enum CodingKeys: String, CodingKey {
case sessionkey = "sessionKey"
case limit
case maxchars = "maxChars"
}
}
@@ -3710,18 +3710,22 @@ public struct DevicePairResolvedEvent: Codable, Sendable {
public struct ChatHistoryParams: Codable, Sendable {
public let sessionkey: String
public let limit: Int?
public let maxchars: Int?
public init(
sessionkey: String,
limit: Int?)
limit: Int?,
maxchars: Int?)
{
self.sessionkey = sessionkey
self.limit = limit
self.maxchars = maxchars
}
private enum CodingKeys: String, CodingKey {
case sessionkey = "sessionKey"
case limit
case maxchars = "maxChars"
}
}
+613 -1
View File
@@ -3014,6 +3014,25 @@
"tags": [],
"hasChildren": false
},
{
"path": "agents.defaults.params",
"kind": "core",
"type": "object",
"required": false,
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": true
},
{
"path": "agents.defaults.params.*",
"kind": "core",
"required": false,
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": false
},
{
"path": "agents.defaults.pdfMaxBytesMb",
"kind": "core",
@@ -7855,6 +7874,56 @@
"help": "Failure window (hours) for backoff counters (default: 24).",
"hasChildren": false
},
{
"path": "auth.cooldowns.overloadedBackoffMs",
"kind": "core",
"type": "integer",
"required": false,
"deprecated": false,
"sensitive": false,
"tags": [
"access",
"auth",
"reliability",
"storage"
],
"label": "Overloaded Backoff (ms)",
"help": "Fixed delay in milliseconds before retrying an overloaded provider/profile rotation (default: 0).",
"hasChildren": false
},
{
"path": "auth.cooldowns.overloadedProfileRotations",
"kind": "core",
"type": "integer",
"required": false,
"deprecated": false,
"sensitive": false,
"tags": [
"access",
"auth",
"storage"
],
"label": "Overloaded Profile Rotations",
"help": "Maximum same-provider auth-profile rotations allowed for overloaded errors before switching to model fallback (default: 1).",
"hasChildren": false
},
{
"path": "auth.cooldowns.rateLimitedProfileRotations",
"kind": "core",
"type": "integer",
"required": false,
"deprecated": false,
"sensitive": false,
"tags": [
"access",
"auth",
"performance",
"storage"
],
"label": "Rate-Limited Profile Rotations",
"help": "Maximum same-provider auth-profile rotations allowed for rate-limit errors before switching to model fallback (default: 1).",
"hasChildren": false
},
{
"path": "auth.order",
"kind": "core",
@@ -31452,7 +31521,7 @@
"network"
],
"label": "Slack Exec Approval Approvers",
"help": "Slack user IDs allowed to approve exec requests for this workspace account. Use Slack user IDs or user targets such as `U123`, `user:U123`, or `<@U123>`. If you leave this unset, OpenClaw falls back to owner IDs inferred from channels.slack.allowFrom, channels.slack.dm.allowFrom, and defaultTo when possible.",
"help": "Slack user IDs allowed to approve exec requests for this workspace account. Use Slack user IDs or user targets such as `U123`, `user:U123`, or `<@U123>`. If you leave this unset, OpenClaw falls back to commands.ownerAllowFrom when possible.",
"hasChildren": true
},
{
@@ -32699,6 +32768,31 @@
"tags": [],
"hasChildren": false
},
{
"path": "channels.telegram.accounts.*.direct.*.errorCooldownMs",
"kind": "channel",
"type": "integer",
"required": false,
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": false
},
{
"path": "channels.telegram.accounts.*.direct.*.errorPolicy",
"kind": "channel",
"type": "string",
"required": false,
"enumValues": [
"always",
"once",
"silent"
],
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": false
},
{
"path": "channels.telegram.accounts.*.direct.*.requireTopic",
"kind": "channel",
@@ -32962,6 +33056,31 @@
"tags": [],
"hasChildren": false
},
{
"path": "channels.telegram.accounts.*.direct.*.topics.*.errorCooldownMs",
"kind": "channel",
"type": "integer",
"required": false,
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": false
},
{
"path": "channels.telegram.accounts.*.direct.*.topics.*.errorPolicy",
"kind": "channel",
"type": "string",
"required": false,
"enumValues": [
"always",
"once",
"silent"
],
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": false
},
{
"path": "channels.telegram.accounts.*.direct.*.topics.*.groupPolicy",
"kind": "channel",
@@ -33124,6 +33243,31 @@
"tags": [],
"hasChildren": false
},
{
"path": "channels.telegram.accounts.*.errorCooldownMs",
"kind": "channel",
"type": "integer",
"required": false,
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": false
},
{
"path": "channels.telegram.accounts.*.errorPolicy",
"kind": "channel",
"type": "string",
"required": false,
"enumValues": [
"always",
"once",
"silent"
],
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": false
},
{
"path": "channels.telegram.accounts.*.execApprovals",
"kind": "channel",
@@ -33324,6 +33468,31 @@
"tags": [],
"hasChildren": false
},
{
"path": "channels.telegram.accounts.*.groups.*.errorCooldownMs",
"kind": "channel",
"type": "integer",
"required": false,
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": false
},
{
"path": "channels.telegram.accounts.*.groups.*.errorPolicy",
"kind": "channel",
"type": "string",
"required": false,
"enumValues": [
"always",
"once",
"silent"
],
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": false
},
{
"path": "channels.telegram.accounts.*.groups.*.groupPolicy",
"kind": "channel",
@@ -33602,6 +33771,31 @@
"tags": [],
"hasChildren": false
},
{
"path": "channels.telegram.accounts.*.groups.*.topics.*.errorCooldownMs",
"kind": "channel",
"type": "integer",
"required": false,
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": false
},
{
"path": "channels.telegram.accounts.*.groups.*.topics.*.errorPolicy",
"kind": "channel",
"type": "string",
"required": false,
"enumValues": [
"always",
"once",
"silent"
],
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": false
},
{
"path": "channels.telegram.accounts.*.groups.*.topics.*.groupPolicy",
"kind": "channel",
@@ -34736,6 +34930,31 @@
"tags": [],
"hasChildren": false
},
{
"path": "channels.telegram.direct.*.errorCooldownMs",
"kind": "channel",
"type": "integer",
"required": false,
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": false
},
{
"path": "channels.telegram.direct.*.errorPolicy",
"kind": "channel",
"type": "string",
"required": false,
"enumValues": [
"always",
"once",
"silent"
],
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": false
},
{
"path": "channels.telegram.direct.*.requireTopic",
"kind": "channel",
@@ -34999,6 +35218,31 @@
"tags": [],
"hasChildren": false
},
{
"path": "channels.telegram.direct.*.topics.*.errorCooldownMs",
"kind": "channel",
"type": "integer",
"required": false,
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": false
},
{
"path": "channels.telegram.direct.*.topics.*.errorPolicy",
"kind": "channel",
"type": "string",
"required": false,
"enumValues": [
"always",
"once",
"silent"
],
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": false
},
{
"path": "channels.telegram.direct.*.topics.*.groupPolicy",
"kind": "channel",
@@ -35167,6 +35411,31 @@
"tags": [],
"hasChildren": false
},
{
"path": "channels.telegram.errorCooldownMs",
"kind": "channel",
"type": "integer",
"required": false,
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": false
},
{
"path": "channels.telegram.errorPolicy",
"kind": "channel",
"type": "string",
"required": false,
"enumValues": [
"always",
"once",
"silent"
],
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": false
},
{
"path": "channels.telegram.execApprovals",
"kind": "channel",
@@ -35398,6 +35667,31 @@
"tags": [],
"hasChildren": false
},
{
"path": "channels.telegram.groups.*.errorCooldownMs",
"kind": "channel",
"type": "integer",
"required": false,
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": false
},
{
"path": "channels.telegram.groups.*.errorPolicy",
"kind": "channel",
"type": "string",
"required": false,
"enumValues": [
"always",
"once",
"silent"
],
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": false
},
{
"path": "channels.telegram.groups.*.groupPolicy",
"kind": "channel",
@@ -35676,6 +35970,31 @@
"tags": [],
"hasChildren": false
},
{
"path": "channels.telegram.groups.*.topics.*.errorCooldownMs",
"kind": "channel",
"type": "integer",
"required": false,
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": false
},
{
"path": "channels.telegram.groups.*.topics.*.errorPolicy",
"kind": "channel",
"type": "string",
"required": false,
"enumValues": [
"always",
"once",
"silent"
],
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": false
},
{
"path": "channels.telegram.groups.*.topics.*.groupPolicy",
"kind": "channel",
@@ -37777,6 +38096,22 @@
"tags": [],
"hasChildren": false
},
{
"path": "channels.whatsapp.accounts.*.reactionLevel",
"kind": "channel",
"type": "string",
"required": false,
"enumValues": [
"off",
"ack",
"minimal",
"extensive"
],
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": false
},
{
"path": "channels.whatsapp.accounts.*.responsePrefix",
"kind": "channel",
@@ -38466,6 +38801,22 @@
"tags": [],
"hasChildren": false
},
{
"path": "channels.whatsapp.reactionLevel",
"kind": "channel",
"type": "string",
"required": false,
"enumValues": [
"off",
"ack",
"minimal",
"extensive"
],
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": false
},
{
"path": "channels.whatsapp.responsePrefix",
"kind": "channel",
@@ -42652,6 +43003,31 @@
"tags": [],
"hasChildren": false
},
{
"path": "gateway.webchat",
"kind": "core",
"type": "object",
"required": false,
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": true
},
{
"path": "gateway.webchat.chatHistoryMaxChars",
"kind": "core",
"type": "integer",
"required": false,
"deprecated": false,
"sensitive": false,
"tags": [
"network",
"performance"
],
"label": "WebChat History Max Chars",
"help": "Max characters per text field in chat.history responses before truncation (default: 12000).",
"hasChildren": false
},
{
"path": "hooks",
"kind": "core",
@@ -47074,6 +47450,65 @@
],
"label": "@openclaw/amazon-bedrock-provider Config",
"help": "Plugin-defined config payload for amazon-bedrock.",
"hasChildren": true
},
{
"path": "plugins.entries.amazon-bedrock.config.guardrail",
"kind": "plugin",
"type": "object",
"required": false,
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": true
},
{
"path": "plugins.entries.amazon-bedrock.config.guardrail.guardrailIdentifier",
"kind": "plugin",
"type": "string",
"required": true,
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": false
},
{
"path": "plugins.entries.amazon-bedrock.config.guardrail.guardrailVersion",
"kind": "plugin",
"type": "string",
"required": true,
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": false
},
{
"path": "plugins.entries.amazon-bedrock.config.guardrail.streamProcessingMode",
"kind": "plugin",
"type": "string",
"required": false,
"enumValues": [
"sync",
"async"
],
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": false
},
{
"path": "plugins.entries.amazon-bedrock.config.guardrail.trace",
"kind": "plugin",
"type": "string",
"required": false,
"enumValues": [
"enabled",
"disabled",
"enabled_full"
],
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": false
},
{
@@ -55564,6 +55999,183 @@
"help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.",
"hasChildren": false
},
{
"path": "plugins.entries.searxng",
"kind": "plugin",
"type": "object",
"required": false,
"deprecated": false,
"sensitive": false,
"tags": [
"advanced"
],
"label": "@openclaw/searxng-plugin",
"help": "OpenClaw SearXNG plugin (plugin: searxng)",
"hasChildren": true
},
{
"path": "plugins.entries.searxng.config",
"kind": "plugin",
"type": "object",
"required": false,
"deprecated": false,
"sensitive": false,
"tags": [
"advanced"
],
"label": "@openclaw/searxng-plugin Config",
"help": "Plugin-defined config payload for searxng.",
"hasChildren": true
},
{
"path": "plugins.entries.searxng.config.webSearch",
"kind": "plugin",
"type": "object",
"required": false,
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": true
},
{
"path": "plugins.entries.searxng.config.webSearch.baseUrl",
"kind": "plugin",
"type": [
"object",
"string"
],
"required": false,
"deprecated": false,
"sensitive": false,
"tags": [
"advanced",
"url-secret"
],
"label": "SearXNG Base URL",
"help": "Base URL of your SearXNG instance, such as http://localhost:8080 or https://search.example.com/searxng.",
"hasChildren": false
},
{
"path": "plugins.entries.searxng.config.webSearch.categories",
"kind": "plugin",
"type": "string",
"required": false,
"deprecated": false,
"sensitive": false,
"tags": [
"advanced"
],
"label": "SearXNG Categories",
"help": "Optional comma-separated categories such as general, news, or science.",
"hasChildren": false
},
{
"path": "plugins.entries.searxng.config.webSearch.language",
"kind": "plugin",
"type": "string",
"required": false,
"deprecated": false,
"sensitive": false,
"tags": [
"advanced"
],
"label": "SearXNG Language",
"help": "Optional language code for results such as en, de, or fr.",
"hasChildren": false
},
{
"path": "plugins.entries.searxng.enabled",
"kind": "plugin",
"type": "boolean",
"required": false,
"deprecated": false,
"sensitive": false,
"tags": [
"advanced"
],
"label": "Enable @openclaw/searxng-plugin",
"hasChildren": false
},
{
"path": "plugins.entries.searxng.hooks",
"kind": "plugin",
"type": "object",
"required": false,
"deprecated": false,
"sensitive": false,
"tags": [
"advanced"
],
"label": "Plugin Hook Policy",
"help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.",
"hasChildren": true
},
{
"path": "plugins.entries.searxng.hooks.allowPromptInjection",
"kind": "plugin",
"type": "boolean",
"required": false,
"deprecated": false,
"sensitive": false,
"tags": [
"access"
],
"label": "Allow Prompt Injection Hooks",
"help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.",
"hasChildren": false
},
{
"path": "plugins.entries.searxng.subagent",
"kind": "plugin",
"type": "object",
"required": false,
"deprecated": false,
"sensitive": false,
"tags": [
"advanced"
],
"label": "Plugin Subagent Policy",
"help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.",
"hasChildren": true
},
{
"path": "plugins.entries.searxng.subagent.allowedModels",
"kind": "plugin",
"type": "array",
"required": false,
"deprecated": false,
"sensitive": false,
"tags": [
"access"
],
"label": "Plugin Subagent Allowed Models",
"help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.",
"hasChildren": true
},
{
"path": "plugins.entries.searxng.subagent.allowedModels.*",
"kind": "plugin",
"type": "string",
"required": false,
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": false
},
{
"path": "plugins.entries.searxng.subagent.allowModelOverride",
"kind": "plugin",
"type": "boolean",
"required": false,
"deprecated": false,
"sensitive": false,
"tags": [
"access"
],
"label": "Allow Plugin Subagent Model Override",
"help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.",
"hasChildren": false
},
{
"path": "plugins.entries.sglang",
"kind": "plugin",
+50 -3
View File
@@ -1,4 +1,4 @@
{"generatedBy":"scripts/generate-config-doc-baseline.ts","recordType":"meta","totalPaths":5718}
{"generatedBy":"scripts/generate-config-doc-baseline.ts","recordType":"meta","totalPaths":5765}
{"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}
@@ -251,6 +251,8 @@
{"recordType":"path","path":"agents.defaults.models.*.params","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
{"recordType":"path","path":"agents.defaults.models.*.params.*","kind":"core","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"agents.defaults.models.*.streaming","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"agents.defaults.params","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
{"recordType":"path","path":"agents.defaults.params.*","kind":"core","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"agents.defaults.pdfMaxBytesMb","kind":"core","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":["performance"],"label":"PDF Max Size (MB)","help":"Maximum PDF file size in megabytes for the PDF tool (default: 10).","hasChildren":false}
{"recordType":"path","path":"agents.defaults.pdfMaxPages","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["performance"],"label":"PDF Max Pages","help":"Maximum number of PDF pages to process for the PDF tool (default: 20).","hasChildren":false}
{"recordType":"path","path":"agents.defaults.pdfModel","kind":"core","type":["object","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
@@ -697,6 +699,9 @@
{"recordType":"path","path":"auth.cooldowns.billingBackoffHoursByProvider.*","kind":"core","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"auth.cooldowns.billingMaxHours","kind":"core","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":["access","auth","performance"],"label":"Billing Backoff Cap (hours)","help":"Cap (hours) for billing backoff (default: 24).","hasChildren":false}
{"recordType":"path","path":"auth.cooldowns.failureWindowHours","kind":"core","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":["access","auth"],"label":"Failover Window (hours)","help":"Failure window (hours) for backoff counters (default: 24).","hasChildren":false}
{"recordType":"path","path":"auth.cooldowns.overloadedBackoffMs","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["access","auth","reliability","storage"],"label":"Overloaded Backoff (ms)","help":"Fixed delay in milliseconds before retrying an overloaded provider/profile rotation (default: 0).","hasChildren":false}
{"recordType":"path","path":"auth.cooldowns.overloadedProfileRotations","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["access","auth","storage"],"label":"Overloaded Profile Rotations","help":"Maximum same-provider auth-profile rotations allowed for overloaded errors before switching to model fallback (default: 1).","hasChildren":false}
{"recordType":"path","path":"auth.cooldowns.rateLimitedProfileRotations","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["access","auth","performance","storage"],"label":"Rate-Limited Profile Rotations","help":"Maximum same-provider auth-profile rotations allowed for rate-limit errors before switching to model fallback (default: 1).","hasChildren":false}
{"recordType":"path","path":"auth.order","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["access","auth"],"label":"Auth Profile Order","help":"Ordered auth profile IDs per provider (used for automatic failover).","hasChildren":true}
{"recordType":"path","path":"auth.order.*","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
{"recordType":"path","path":"auth.order.*.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
@@ -2824,7 +2829,7 @@
{"recordType":"path","path":"channels.slack.execApprovals","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Slack Exec Approvals","help":"Slack-native exec approval routing and approver authorization. Enable this only when Slack should act as an explicit exec-approval client for the selected workspace account.","hasChildren":true}
{"recordType":"path","path":"channels.slack.execApprovals.agentFilter","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Slack Exec Approval Agent Filter","help":"Optional allowlist of agent IDs eligible for Slack exec approvals, for example `[\"main\", \"ops-agent\"]`. Use this to keep approval prompts scoped to the agents you actually operate from Slack.","hasChildren":true}
{"recordType":"path","path":"channels.slack.execApprovals.agentFilter.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.slack.execApprovals.approvers","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Slack Exec Approval Approvers","help":"Slack user IDs allowed to approve exec requests for this workspace account. Use Slack user IDs or user targets such as `U123`, `user:U123`, or `<@U123>`. If you leave this unset, OpenClaw falls back to owner IDs inferred from channels.slack.allowFrom, channels.slack.dm.allowFrom, and defaultTo when possible.","hasChildren":true}
{"recordType":"path","path":"channels.slack.execApprovals.approvers","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Slack Exec Approval Approvers","help":"Slack user IDs allowed to approve exec requests for this workspace account. Use Slack user IDs or user targets such as `U123`, `user:U123`, or `<@U123>`. If you leave this unset, OpenClaw falls back to commands.ownerAllowFrom when possible.","hasChildren":true}
{"recordType":"path","path":"channels.slack.execApprovals.approvers.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.slack.execApprovals.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Slack Exec Approvals Enabled","help":"Enable Slack exec approvals for this account. When false or unset, Slack messages/buttons cannot approve exec requests.","hasChildren":false}
{"recordType":"path","path":"channels.slack.execApprovals.sessionFilter","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network","storage"],"label":"Slack Exec Approval Session Filter","help":"Optional session-key filters matched as substring or regex-style patterns before Slack approval routing is used. Use narrow patterns so Slack approvals only appear for intended sessions.","hasChildren":true}
@@ -2931,6 +2936,8 @@
{"recordType":"path","path":"channels.telegram.accounts.*.direct.*.autoTopicLabel.prompt","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.telegram.accounts.*.direct.*.dmPolicy","kind":"channel","type":"string","required":false,"enumValues":["pairing","allowlist","open","disabled"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.telegram.accounts.*.direct.*.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.telegram.accounts.*.direct.*.errorCooldownMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.telegram.accounts.*.direct.*.errorPolicy","kind":"channel","type":"string","required":false,"enumValues":["always","once","silent"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.telegram.accounts.*.direct.*.requireTopic","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.telegram.accounts.*.direct.*.skills","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
{"recordType":"path","path":"channels.telegram.accounts.*.direct.*.skills.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
@@ -2957,6 +2964,8 @@
{"recordType":"path","path":"channels.telegram.accounts.*.direct.*.topics.*.allowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.telegram.accounts.*.direct.*.topics.*.disableAudioPreflight","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.telegram.accounts.*.direct.*.topics.*.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.telegram.accounts.*.direct.*.topics.*.errorCooldownMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.telegram.accounts.*.direct.*.topics.*.errorPolicy","kind":"channel","type":"string","required":false,"enumValues":["always","once","silent"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.telegram.accounts.*.direct.*.topics.*.groupPolicy","kind":"channel","type":"string","required":false,"enumValues":["open","disabled","allowlist"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.telegram.accounts.*.direct.*.topics.*.requireMention","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.telegram.accounts.*.direct.*.topics.*.skills","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
@@ -2972,6 +2981,8 @@
{"recordType":"path","path":"channels.telegram.accounts.*.draftChunk.maxChars","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.telegram.accounts.*.draftChunk.minChars","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.telegram.accounts.*.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.telegram.accounts.*.errorCooldownMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.telegram.accounts.*.errorPolicy","kind":"channel","type":"string","required":false,"enumValues":["always","once","silent"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.telegram.accounts.*.execApprovals","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
{"recordType":"path","path":"channels.telegram.accounts.*.execApprovals.agentFilter","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
{"recordType":"path","path":"channels.telegram.accounts.*.execApprovals.agentFilter.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
@@ -2990,6 +3001,8 @@
{"recordType":"path","path":"channels.telegram.accounts.*.groups.*.allowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.telegram.accounts.*.groups.*.disableAudioPreflight","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.telegram.accounts.*.groups.*.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.telegram.accounts.*.groups.*.errorCooldownMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.telegram.accounts.*.groups.*.errorPolicy","kind":"channel","type":"string","required":false,"enumValues":["always","once","silent"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.telegram.accounts.*.groups.*.groupPolicy","kind":"channel","type":"string","required":false,"enumValues":["open","disabled","allowlist"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.telegram.accounts.*.groups.*.requireMention","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.telegram.accounts.*.groups.*.skills","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
@@ -3017,6 +3030,8 @@
{"recordType":"path","path":"channels.telegram.accounts.*.groups.*.topics.*.allowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.telegram.accounts.*.groups.*.topics.*.disableAudioPreflight","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.telegram.accounts.*.groups.*.topics.*.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.telegram.accounts.*.groups.*.topics.*.errorCooldownMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.telegram.accounts.*.groups.*.topics.*.errorPolicy","kind":"channel","type":"string","required":false,"enumValues":["always","once","silent"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.telegram.accounts.*.groups.*.topics.*.groupPolicy","kind":"channel","type":"string","required":false,"enumValues":["open","disabled","allowlist"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.telegram.accounts.*.groups.*.topics.*.requireMention","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.telegram.accounts.*.groups.*.topics.*.skills","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
@@ -3116,6 +3131,8 @@
{"recordType":"path","path":"channels.telegram.direct.*.autoTopicLabel.prompt","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.telegram.direct.*.dmPolicy","kind":"channel","type":"string","required":false,"enumValues":["pairing","allowlist","open","disabled"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.telegram.direct.*.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.telegram.direct.*.errorCooldownMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.telegram.direct.*.errorPolicy","kind":"channel","type":"string","required":false,"enumValues":["always","once","silent"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.telegram.direct.*.requireTopic","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.telegram.direct.*.skills","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
{"recordType":"path","path":"channels.telegram.direct.*.skills.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
@@ -3142,6 +3159,8 @@
{"recordType":"path","path":"channels.telegram.direct.*.topics.*.allowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.telegram.direct.*.topics.*.disableAudioPreflight","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.telegram.direct.*.topics.*.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.telegram.direct.*.topics.*.errorCooldownMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.telegram.direct.*.topics.*.errorPolicy","kind":"channel","type":"string","required":false,"enumValues":["always","once","silent"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.telegram.direct.*.topics.*.groupPolicy","kind":"channel","type":"string","required":false,"enumValues":["open","disabled","allowlist"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.telegram.direct.*.topics.*.requireMention","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.telegram.direct.*.topics.*.skills","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
@@ -3157,6 +3176,8 @@
{"recordType":"path","path":"channels.telegram.draftChunk.maxChars","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.telegram.draftChunk.minChars","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.telegram.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.telegram.errorCooldownMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.telegram.errorPolicy","kind":"channel","type":"string","required":false,"enumValues":["always","once","silent"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"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}
@@ -3175,6 +3196,8 @@
{"recordType":"path","path":"channels.telegram.groups.*.allowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.telegram.groups.*.disableAudioPreflight","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.telegram.groups.*.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.telegram.groups.*.errorCooldownMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.telegram.groups.*.errorPolicy","kind":"channel","type":"string","required":false,"enumValues":["always","once","silent"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.telegram.groups.*.groupPolicy","kind":"channel","type":"string","required":false,"enumValues":["open","disabled","allowlist"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.telegram.groups.*.requireMention","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.telegram.groups.*.skills","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
@@ -3202,6 +3225,8 @@
{"recordType":"path","path":"channels.telegram.groups.*.topics.*.allowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.telegram.groups.*.topics.*.disableAudioPreflight","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.telegram.groups.*.topics.*.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.telegram.groups.*.topics.*.errorCooldownMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.telegram.groups.*.topics.*.errorPolicy","kind":"channel","type":"string","required":false,"enumValues":["always","once","silent"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.telegram.groups.*.topics.*.groupPolicy","kind":"channel","type":"string","required":false,"enumValues":["open","disabled","allowlist"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.telegram.groups.*.topics.*.requireMention","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.telegram.groups.*.topics.*.skills","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
@@ -3392,6 +3417,7 @@
{"recordType":"path","path":"channels.whatsapp.accounts.*.mediaMaxMb","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.whatsapp.accounts.*.messagePrefix","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.whatsapp.accounts.*.name","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.whatsapp.accounts.*.reactionLevel","kind":"channel","type":"string","required":false,"enumValues":["off","ack","minimal","extensive"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.whatsapp.accounts.*.responsePrefix","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.whatsapp.accounts.*.selfChatMode","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.whatsapp.accounts.*.sendReadReceipts","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
@@ -3456,6 +3482,7 @@
{"recordType":"path","path":"channels.whatsapp.markdown.tables","kind":"channel","type":"string","required":false,"enumValues":["off","bullets","code","block"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.whatsapp.mediaMaxMb","kind":"channel","type":"integer","required":true,"defaultValue":50,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.whatsapp.messagePrefix","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.whatsapp.reactionLevel","kind":"channel","type":"string","required":false,"enumValues":["off","ack","minimal","extensive"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.whatsapp.responsePrefix","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"channels.whatsapp.selfChatMode","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"WhatsApp Self-Phone Mode","help":"Same-phone setup (bot uses your personal WhatsApp number).","hasChildren":false}
{"recordType":"path","path":"channels.whatsapp.sendReadReceipts","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
@@ -3790,6 +3817,8 @@
{"recordType":"path","path":"gateway.tools.deny.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"gateway.trustedProxies","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["network"],"label":"Gateway Trusted Proxy CIDRs","help":"CIDR/IP allowlist of upstream proxies permitted to provide forwarded client identity headers. Keep this list narrow so untrusted hops cannot impersonate users.","hasChildren":true}
{"recordType":"path","path":"gateway.trustedProxies.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"gateway.webchat","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
{"recordType":"path","path":"gateway.webchat.chatHistoryMaxChars","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["network","performance"],"label":"WebChat History Max Chars","help":"Max characters per text field in chat.history responses before truncation (default: 12000).","hasChildren":false}
{"recordType":"path","path":"hooks","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Hooks","help":"Inbound webhook automation surface for mapping external events into wake or agent actions in OpenClaw. Keep this locked down with explicit token/session/agent controls before exposing it beyond trusted networks.","hasChildren":true}
{"recordType":"path","path":"hooks.allowedAgentIds","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Hooks Allowed Agent IDs","help":"Allowlist of agent IDs that hook mappings are allowed to target when selecting execution agents. Use this to constrain automation events to dedicated service agents and reduce blast radius if a hook token is exposed.","hasChildren":true}
{"recordType":"path","path":"hooks.allowedAgentIds.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
@@ -4145,7 +4174,12 @@
{"recordType":"path","path":"plugins.entries.acpx.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.acpx.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.amazon-bedrock","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/amazon-bedrock-provider","help":"OpenClaw Amazon Bedrock provider plugin (plugin: amazon-bedrock)","hasChildren":true}
{"recordType":"path","path":"plugins.entries.amazon-bedrock.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/amazon-bedrock-provider Config","help":"Plugin-defined config payload for amazon-bedrock.","hasChildren":false}
{"recordType":"path","path":"plugins.entries.amazon-bedrock.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/amazon-bedrock-provider Config","help":"Plugin-defined config payload for amazon-bedrock.","hasChildren":true}
{"recordType":"path","path":"plugins.entries.amazon-bedrock.config.guardrail","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
{"recordType":"path","path":"plugins.entries.amazon-bedrock.config.guardrail.guardrailIdentifier","kind":"plugin","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.amazon-bedrock.config.guardrail.guardrailVersion","kind":"plugin","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.amazon-bedrock.config.guardrail.streamProcessingMode","kind":"plugin","type":"string","required":false,"enumValues":["sync","async"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.amazon-bedrock.config.guardrail.trace","kind":"plugin","type":"string","required":false,"enumValues":["enabled","disabled","enabled_full"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.amazon-bedrock.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/amazon-bedrock-provider","hasChildren":false}
{"recordType":"path","path":"plugins.entries.amazon-bedrock.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.amazon-bedrock.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}
@@ -4778,6 +4812,19 @@
{"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.searxng","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/searxng-plugin","help":"OpenClaw SearXNG plugin (plugin: searxng)","hasChildren":true}
{"recordType":"path","path":"plugins.entries.searxng.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/searxng-plugin Config","help":"Plugin-defined config payload for searxng.","hasChildren":true}
{"recordType":"path","path":"plugins.entries.searxng.config.webSearch","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
{"recordType":"path","path":"plugins.entries.searxng.config.webSearch.baseUrl","kind":"plugin","type":["object","string"],"required":false,"deprecated":false,"sensitive":false,"tags":["advanced","url-secret"],"label":"SearXNG Base URL","help":"Base URL of your SearXNG instance, such as http://localhost:8080 or https://search.example.com/searxng.","hasChildren":false}
{"recordType":"path","path":"plugins.entries.searxng.config.webSearch.categories","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"SearXNG Categories","help":"Optional comma-separated categories such as general, news, or science.","hasChildren":false}
{"recordType":"path","path":"plugins.entries.searxng.config.webSearch.language","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"SearXNG Language","help":"Optional language code for results such as en, de, or fr.","hasChildren":false}
{"recordType":"path","path":"plugins.entries.searxng.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/searxng-plugin","hasChildren":false}
{"recordType":"path","path":"plugins.entries.searxng.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.searxng.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.searxng.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.searxng.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.searxng.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
{"recordType":"path","path":"plugins.entries.searxng.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}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 9.8 KiB

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

After

Width:  |  Height:  |  Size: 3.1 KiB

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

After

Width:  |  Height:  |  Size: 3.5 KiB

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

After

Width:  |  Height:  |  Size: 3.5 KiB

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

After

Width:  |  Height:  |  Size: 1.3 KiB

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

After

Width:  |  Height:  |  Size: 3.6 KiB

+26 -85
View File
@@ -1,109 +1,50 @@
---
summary: "ClawFlow workflow orchestration for background tasks and detached runs"
summary: "Compatibility note for older ClawFlow references in release notes and docs"
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
- You encounter ClawFlow or openclaw flows in older release notes or docs
- You want to understand what ClawFlow terminology maps to in the current CLI
- You want to translate older flow references into the supported task commands
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.
`ClawFlow` appears in some older OpenClaw release notes and documentation as if it were a user-facing runtime with its own `openclaw flows` command 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.
That is not the current operator-facing surface in this repository.
## TL;DR
Today, the supported CLI surface for inspecting and managing detached work is [`openclaw tasks`](/automation/tasks).
- 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.
## What to use today
## Quick start
- `openclaw tasks list` shows tracked detached runs
- `openclaw tasks show <lookup>` shows one task by task id, run id, or session key
- `openclaw tasks cancel <lookup>` cancels a running task
- `openclaw tasks audit` surfaces stale or broken task runs
```bash
openclaw flows list
openclaw flows show <flow-id-or-owner-session>
openclaw flows cancel <flow-id-or-owner-session>
openclaw tasks list
openclaw tasks show <lookup>
openclaw tasks cancel <lookup>
```
## How it relates to tasks
## What this means for older references
Background tasks still do the low-level work:
If you see `ClawFlow` or `openclaw flows` in:
- ACP runs
- subagent runs
- cron executions
- CLI-initiated runs
- old release notes
- issue threads
- stale search results
- outdated local notes
ClawFlow sits above that ledger:
translate those instructions to the current task CLI:
- 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.
- `openclaw flows list` -> `openclaw tasks list`
- `openclaw flows show <lookup>` -> `openclaw tasks show <lookup>`
- `openclaw flows cancel <lookup>` -> `openclaw tasks cancel <lookup>`
## Related
- [Background Tasks](/automation/tasks) — detached work ledger
- [CLI: flows](/cli/flows) — flow inspection and control commands
- [CLI: flows](/cli/flows) — compatibility note for the mistaken command name
- [Cron Jobs](/automation/cron-jobs) — scheduled jobs that may create tasks
+19 -2
View File
@@ -36,7 +36,7 @@ Troubleshooting: [/automation/troubleshooting](/automation/troubleshooting)
- Wakeups are first-class: a job can request “wake now” vs “next heartbeat”.
- Webhook posting is per job via `delivery.mode = "webhook"` + `delivery.to = "<url>"`.
- Legacy fallback remains for stored jobs with `notify: true` when `cron.webhook` is set, migrate those jobs to webhook delivery mode.
- For upgrades, `openclaw doctor --fix` can normalize legacy cron store fields before the scheduler touches them.
- For upgrades, `openclaw doctor --fix` can normalize legacy cron store fields, including old top-level delivery hints such as `threadId`.
## Quick start (actionable)
@@ -198,12 +198,14 @@ Common `agentTurn` fields:
- `model` / `thinking`: optional overrides (see below).
- `timeoutSeconds`: optional timeout override.
- `lightContext`: optional lightweight bootstrap mode for jobs that do not need workspace bootstrap file injection.
- `toolsAllow`: optional array of tool names to restrict which tools the job can use (e.g. `["exec", "read", "write"]`).
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`
@@ -272,6 +274,7 @@ Isolated jobs can deliver output to a channel via the top-level `delivery` confi
- `delivery.mode`: `announce` (channel delivery), `webhook` (HTTP POST), or `none`.
- `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.
@@ -378,7 +381,8 @@ Notes:
- `"current"` is resolved to `"session:<sessionKey>"` at creation time.
- Custom sessions (`session:xxx`) maintain persistent context across runs.
- Optional fields: `agentId`, `description`, `enabled`, `deleteAfterRun` (defaults to true for `at`),
`delivery`.
`delivery`, `toolsAllow`.
- `toolsAllow`: optional array of tool names to restrict which tools the job can use (e.g. `["exec", "read"]`). Omit or set `null` to use all tools.
- `wakeMode` defaults to `"now"` when omitted.
### cron.update params
@@ -665,6 +669,19 @@ openclaw cron edit <jobId> --agent ops
openclaw cron edit <jobId> --clear-agent
```
Tool allowlists (restrict which tools a job can use):
```bash
# Only allow exec and read tools for this job
openclaw cron add --name "Scoped job" --cron "0 8 * * *" --session isolated --message "Run scoped checks" --tools exec,read
# Update an existing job's tool allowlist
openclaw cron edit <jobId> --tools exec,read,write
# Remove a tool allowlist (use all tools)
openclaw cron edit <jobId> --clear-tools
```
Manual run (force is the default, use `--due` to only run when due):
```bash
+4 -5
View File
@@ -51,19 +51,18 @@ 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
## Older ClawFlow references
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.
Older release notes and docs may mention `ClawFlow` or `openclaw flows`, but the current CLI surface in this repo is `openclaw tasks`.
See [ClawFlow](/automation/clawflow) for the flow overview and [CLI: flows](/cli/flows) for the command surface.
See [Background Tasks](/automation/tasks) for the supported task ledger commands, plus [ClawFlow](/automation/clawflow) and [CLI: flows](/cli/flows) for compatibility notes.
## Related
- [Cron vs Heartbeat](/automation/cron-vs-heartbeat) — detailed comparison guide
- [ClawFlow](/automation/clawflow) — flow-level orchestration above tasks
- [ClawFlow](/automation/clawflow) — compatibility note for older docs and release notes
- [Troubleshooting](/automation/troubleshooting) — debugging automation issues
- [Configuration Reference](/gateway/configuration-reference) — all config keys
+19 -5
View File
@@ -172,6 +172,16 @@ Surfaces operational issues. Findings also appear in `openclaw status` when issu
| `missing_cleanup` | warn | Terminal task with no cleanup timestamp |
| `inconsistent_timestamps` | warn | Timeline violation (for example ended before started) |
## Chat task board (`/tasks`)
Use `/tasks` in any chat session to see background tasks linked to that session. The board shows
active and recently completed tasks with runtime, status, timing, and progress or error detail.
When the current session has no visible linked tasks, `/tasks` falls back to agent-local task counts
so you still get an overview without leaking other-session details.
For the full operator ledger, use the CLI: `openclaw tasks list`.
## Status integration (task pressure)
`openclaw status` includes an at-a-glance task summary:
@@ -186,6 +196,10 @@ The summary reports:
- **failures** — count of `failed` + `timed_out` + `lost`
- **byRuntime** — breakdown by `acp`, `subagent`, `cron`, `cli`
Both `/status` and the `session_status` tool use a cleanup-aware task snapshot: active tasks are
preferred, stale completed rows are hidden, and recent failures only surface when no active work
remains. This keeps the status card focused on what matters right now.
## Storage and maintenance
### Where tasks live
@@ -210,11 +224,11 @@ A sweeper runs every **60 seconds** and handles three things:
## How tasks relate to other systems
### Tasks and ClawFlow
### Tasks and older flow references
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.
Some older OpenClaw release notes and docs referred to task management as `ClawFlow` and documented an `openclaw flows` command surface.
See [ClawFlow](/automation/clawflow) for the flow overview and [CLI: flows](/cli/flows) for the command surface.
In the current codebase, the supported operator surface is `openclaw tasks`. See [ClawFlow](/automation/clawflow) and [CLI: flows](/cli/flows) for compatibility notes that map those older references to the current task commands.
### Tasks and cron
@@ -239,9 +253,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
- [ClawFlow](/automation/clawflow) — compatibility note for older docs and release notes
- [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: flows](/cli/flows) — compatibility note for the mistaken command name
- [CLI: Tasks](/cli/index#tasks) — CLI command reference
+32
View File
@@ -148,6 +148,7 @@ In **Event Subscription**:
1. Choose **Use long connection to receive events** (WebSocket)
2. Add the event: `im.message.receive_v1`
3. (Optional) For Drive comment workflows, also add: `drive.notice.comment_add_v1`
⚠️ If the gateway is not running, the long-connection setup may fail to save.
@@ -735,6 +736,36 @@ Key options:
- ✅ Topic-thread replies where Feishu exposes `reply_in_thread`
- ✅ Media replies stay thread-aware when replying to a thread/topic message
## Drive comments
Feishu can trigger the agent when someone adds a comment on a Feishu Drive document (Docs, Sheets,
etc.). The agent receives the comment text, document context, and the comment thread so it can
respond in-thread or make document edits.
Requirements:
- Subscribe to `drive.notice.comment_add_v1` in your Feishu app event subscription settings
(alongside the existing `im.message.receive_v1`)
- The Drive tool is enabled by default; disable with `channels.feishu.tools.drive: false`
The `feishu_drive` tool exposes these comment actions:
| Action | Description |
| ---------------------- | ----------------------------------- |
| `list_comments` | List comments on a document |
| `list_comment_replies` | List replies in a comment thread |
| `add_comment` | Add a new top-level comment |
| `reply_comment` | Reply to an existing comment thread |
When the agent handles a Drive comment event, it receives:
- the comment text and sender
- document metadata (title, type, URL)
- the comment thread context for in-thread replies
After making document edits, the agent is guided to use `feishu_drive.reply_comment` to notify the
commenter and then output `NO_REPLY` to avoid duplicate sends.
## Runtime action surface
Feishu currently exposes these runtime actions:
@@ -750,6 +781,7 @@ Feishu currently exposes these runtime actions:
- `channel-info`
- `channel-list`
- `react` and `reactions` when reactions are enabled in config
- `feishu_drive` comment actions: `list_comments`, `list_comment_replies`, `add_comment`, `reply_comment`
## Related
+21
View File
@@ -481,6 +481,27 @@ Notes:
</Accordion>
</AccordionGroup>
## Exec approvals in Slack
Exec approval prompts can route natively through Slack using interactive buttons and interactions, instead of falling back to the Web UI or terminal. Approver authorization is enforced: only users identified as approvers can approve or deny requests through Slack.
This uses the same shared approval button surface as other channels. When `interactivity` is enabled in your Slack app settings, approval prompts render as Block Kit buttons directly in the conversation.
Configuration uses the shared `approvals.exec` config with Slack targets:
```json5
{
approvals: {
exec: {
enabled: true,
targets: [{ channel: "slack", to: "U12345678" }],
},
},
}
```
Same-chat `/approve` also works in Slack channels and DMs that already support commands. See [Exec approvals](/tools/exec-approvals) for the full approval forwarding model.
## Troubleshooting
<AccordionGroup>
+30
View File
@@ -831,6 +831,33 @@ openclaw message poll --channel telegram --target -1001234567890:topic:42 \
</Accordion>
</AccordionGroup>
## Error reply controls
When the agent encounters a delivery or provider error, Telegram can either reply with the error text or suppress it. Two config keys control this behavior:
| Key | Values | Default | Description |
| ----------------------------------- | ----------------- | ------- | ----------------------------------------------------------------------------------------------- |
| `channels.telegram.errorPolicy` | `reply`, `silent` | `reply` | `reply` sends a friendly error message to the chat. `silent` suppresses error replies entirely. |
| `channels.telegram.errorCooldownMs` | number (ms) | `60000` | Minimum time between error replies to the same chat. Prevents error spam during outages. |
Per-account, per-group, and per-topic overrides are supported (same inheritance as other Telegram config keys).
```json5
{
channels: {
telegram: {
errorPolicy: "reply",
errorCooldownMs: 120000,
groups: {
"-1001234567890": {
errorPolicy: "silent", // suppress errors in this group
},
},
},
},
}
```
## Troubleshooting
<AccordionGroup>
@@ -963,6 +990,8 @@ Primary reference:
- `channels.telegram.actions.sticker`: gate Telegram sticker actions — send and search (default: false).
- `channels.telegram.reactionNotifications`: `off | own | all` — control which reactions trigger system events (default: `own` when not set).
- `channels.telegram.reactionLevel`: `off | ack | minimal | extensive` — control agent's reaction capability (default: `minimal` when not set).
- `channels.telegram.errorPolicy`: `reply | silent` — control error reply behavior (default: `reply`). Per-account/group/topic overrides supported.
- `channels.telegram.errorCooldownMs`: minimum ms between error replies to the same chat (default: `60000`). Prevents error spam during outages.
- [Configuration reference - Telegram](/gateway/configuration-reference#telegram)
@@ -979,6 +1008,7 @@ Telegram-specific high-signal fields:
- webhook: `webhookUrl`, `webhookSecret`, `webhookPath`, `webhookHost`
- actions/capabilities: `capabilities.inlineButtons`, `actions.sendMessage|editMessage|deleteMessage|reactions|sticker`
- reactions: `reactionNotifications`, `reactionLevel`
- errors: `errorPolicy`, `errorCooldownMs`
- writes/history: `configWrites`, `historyLimit`, `dmHistoryLimit`, `dms.*.historyLimit`
## Related
+13
View File
@@ -105,6 +105,19 @@ Full troubleshooting:
Full troubleshooting: [/channels/signal#troubleshooting](/channels/signal#troubleshooting)
## QQ Bot
### QQ Bot failure signatures
| Symptom | Fastest check | Fix |
| ------------------------------- | ------------------------------------------- | --------------------------------------------------------------- |
| Bot replies "gone to Mars" | Verify `appId` and `clientSecret` in config | Set credentials or restart the gateway. |
| No inbound messages | `openclaw channels status --probe` | Verify credentials on the QQ Open Platform. |
| Voice not transcribed | Check STT provider config | Configure `channels.qqbot.stt` or `tools.media.audio`. |
| Proactive messages not arriving | Check QQ platform interaction requirements | QQ may block bot-initiated messages without recent interaction. |
Full troubleshooting: [/channels/qqbot#troubleshooting](/channels/qqbot#troubleshooting)
## Matrix
### Matrix failure signatures
+27 -1
View File
@@ -330,9 +330,35 @@ When the linked self number is also present in `allowFrom`, WhatsApp self-chat s
</Accordion>
</AccordionGroup>
## Reaction level
`channels.whatsapp.reactionLevel` controls how broadly the agent uses emoji reactions on WhatsApp:
| Level | Ack reactions | Agent-initiated reactions | Description |
| ------------- | ------------- | ------------------------- | ------------------------------------------------ |
| `"off"` | No | No | No reactions at all |
| `"ack"` | Yes | No | Ack reactions only (pre-reply receipt) |
| `"minimal"` | Yes | Yes (conservative) | Ack + agent reactions with conservative guidance |
| `"extensive"` | Yes | Yes (encouraged) | Ack + agent reactions with encouraged guidance |
Default: `"minimal"`.
Per-account overrides use `channels.whatsapp.accounts.<id>.reactionLevel`.
```json5
{
channels: {
whatsapp: {
reactionLevel: "ack",
},
},
}
```
## Acknowledgment reactions
WhatsApp supports immediate ack reactions on inbound receipt via `channels.whatsapp.ackReaction`.
Ack reactions are gated by `reactionLevel` — they are suppressed when `reactionLevel` is `"off"`.
```json5
{
@@ -447,7 +473,7 @@ Primary reference:
High-signal WhatsApp fields:
- access: `dmPolicy`, `allowFrom`, `groupPolicy`, `groupAllowFrom`, `groups`
- delivery: `textChunkLimit`, `chunkMode`, `mediaMaxMb`, `sendReadReceipts`, `ackReaction`
- delivery: `textChunkLimit`, `chunkMode`, `mediaMaxMb`, `sendReadReceipts`, `ackReaction`, `reactionLevel`
- multi-account: `accounts.<id>.enabled`, `accounts.<id>.authDir`, account-level overrides
- operations: `configWrites`, `debounceMs`, `web.enabled`, `web.heartbeatSeconds`, `web.reconnect.*`
- session behavior: `session.dmScope`, `historyLimit`, `dmHistoryLimit`, `dms.<id>.historyLimit`
+1 -1
View File
@@ -35,7 +35,7 @@ Note: retention/pruning is controlled in config:
Upgrade note: if you have older cron jobs from before the current delivery/store format, run
`openclaw doctor --fix`. Doctor now normalizes legacy cron fields (`jobId`, `schedule.cron`,
top-level delivery fields, payload `provider` delivery aliases) and migrates simple
top-level delivery fields including legacy `threadId`, payload `provider` delivery aliases) and migrates simple
`notify: true` webhook fallback jobs to explicit webhook delivery when `cron.webhook` is
configured.
+17 -35
View File
@@ -1,54 +1,36 @@
---
summary: "CLI reference for `openclaw flows` (list, inspect, cancel)"
summary: "Compatibility note for the mistakenly documented `openclaw flows` command"
read_when:
- You want to inspect or cancel a flow
- You want to see how background tasks roll up into a higher-level job
- You encounter openclaw flows in older release notes, issue threads, or search results
- You want to know what command replaced openclaw flows
title: "flows"
---
# `openclaw flows`
Inspect and manage [ClawFlow](/automation/clawflow) jobs.
`openclaw flows` is **not** a current OpenClaw CLI command.
Some older release notes and docs mistakenly documented a `flows` command surface. The supported operator surface is [`openclaw tasks`](/automation/tasks).
```bash
openclaw flows list
openclaw flows show <lookup>
openclaw flows cancel <lookup>
openclaw tasks list
openclaw tasks show <lookup>
openclaw tasks cancel <lookup>
```
## Commands
## Use instead
### `flows list`
- `openclaw tasks list` — list tracked background tasks
- `openclaw tasks show <lookup>` — inspect one task by task id, run id, or session key
- `openclaw tasks cancel <lookup>` — cancel a running background task
- `openclaw tasks notify <lookup> <policy>` — change task notification behavior
- `openclaw tasks audit` — surface stale or broken task runs
List tracked flows and their task counts.
## Why this page exists
```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>
```
This page stays in place so existing links from older changelog entries, issue threads, and search results have a clear correction instead of a dead end.
## Related
- [ClawFlow](/automation/clawflow) — job-level orchestration above tasks
- [Background Tasks](/automation/tasks) — detached work ledger
- [CLI reference](/cli/index) — full command tree
+1 -13
View File
@@ -45,7 +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)
- [`tasks`](/cli/index#tasks)
- [`dns`](/cli/dns)
- [`docs`](/cli/docs)
- [`hooks`](/cli/hooks)
@@ -172,10 +172,6 @@ openclaw [--dev] [--profile <name>] <command>
show
notify
cancel
flows
list
show
cancel
gateway
call
health
@@ -814,14 +810,6 @@ 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`
+36 -5
View File
@@ -425,10 +425,11 @@ Launches a local child process and communicates over stdin/stdout.
Connects to a remote MCP server over HTTP Server-Sent Events.
| Field | Description |
| --------- | ---------------------------------------------------------------- |
| `url` | HTTP or HTTPS URL of the remote server (required) |
| `headers` | Optional key-value map of HTTP headers (for example auth tokens) |
| Field | Description |
| ------------------- | ---------------------------------------------------------------- |
| `url` | HTTP or HTTPS URL of the remote server (required) |
| `headers` | Optional key-value map of HTTP headers (for example auth tokens) |
| `connectionTimeout` | Per-server connection timeout in ms (optional) |
Example:
@@ -450,6 +451,36 @@ Example:
Sensitive values in `url` (userinfo) and `headers` are redacted in logs and
status output.
### Streamable HTTP transport
`streamable-http` is an additional transport option alongside `sse` and `stdio`. It uses HTTP streaming for bidirectional communication with remote MCP servers.
| Field | Description |
| ------------------- | ---------------------------------------------------------------- |
| `url` | HTTP or HTTPS URL of the remote server (required) |
| `transport` | Set to `"streamable-http"` to select this transport |
| `headers` | Optional key-value map of HTTP headers (for example auth tokens) |
| `connectionTimeout` | Per-server connection timeout in ms (optional) |
Example:
```json
{
"mcp": {
"servers": {
"streaming-tools": {
"url": "https://mcp.example.com/stream",
"transport": "streamable-http",
"connectionTimeout": 10000,
"headers": {
"Authorization": "Bearer <token>"
}
}
}
}
}
```
These commands manage saved config only. They do not start the channel bridge,
open a live MCP client session, or prove the target server is reachable.
@@ -462,6 +493,6 @@ Current limits:
- conversation discovery depends on existing Gateway session route metadata
- no generic push protocol beyond the Claude-specific adapter
- no message edit or react tools yet
- HTTP/SSE transport connects to a single remote server; no multiplexed upstream yet
- HTTP/SSE/streamable-http transport connects to a single remote server; no multiplexed upstream yet
- `permissions_list_open` only includes approvals observed while the bridge is
connected
+11
View File
@@ -129,6 +129,8 @@ Defaults:
- Billing backoff starts at **5 hours**, doubles per billing failure, and caps at **24 hours**.
- Backoff counters reset if the profile hasnt failed for **24 hours** (configurable).
- Overloaded retries allow **1 same-provider profile rotation** before model fallback.
- Overloaded retries use **0 ms backoff** by default.
## Model fallback
@@ -136,6 +138,13 @@ If all profiles for a provider fail, OpenClaw moves to the next model in
`agents.defaults.model.fallbacks`. This applies to auth failures, rate limits, and
timeouts that exhausted profile rotation (other errors do not advance fallback).
Overloaded and rate-limit errors are handled more aggressively than billing
cooldowns. By default, OpenClaw allows one same-provider auth-profile retry,
then switches to the next configured model fallback without waiting. Tune this
with `auth.cooldowns.overloadedProfileRotations`,
`auth.cooldowns.overloadedBackoffMs`, and
`auth.cooldowns.rateLimitedProfileRotations`.
When a run starts with a model override (hooks or CLI), fallbacks still end at
`agents.defaults.model.primary` after trying any configured fallbacks.
@@ -146,6 +155,8 @@ See [Gateway configuration](/gateway/configuration) for:
- `auth.profiles` / `auth.order`
- `auth.cooldowns.billingBackoffHours` / `auth.cooldowns.billingBackoffHoursByProvider`
- `auth.cooldowns.billingMaxHours` / `auth.cooldowns.failureWindowHours`
- `auth.cooldowns.overloadedProfileRotations` / `auth.cooldowns.overloadedBackoffMs`
- `auth.cooldowns.rateLimitedProfileRotations`
- `agents.defaults.model.primary` / `agents.defaults.model.fallbacks`
- `agents.defaults.imageModel` routing
+1
View File
@@ -108,6 +108,7 @@ Notes:
- `/model` (and `/model list`) is a compact, numbered picker (model family + available providers).
- On Discord, `/model` and `/models` open an interactive picker with provider and model dropdowns plus a Submit step.
- `/model <#>` selects from that picker.
- `/model` updates the session selection immediately. If the agent is idle, the next run uses the new model right away. If the agent is busy, the in-flight run finishes first and queued/future work uses the new model after that.
- `/model status` is the detailed view (auth candidates and, when configured, provider endpoint `baseUrl` + `api` mode).
- Model refs are parsed by splitting on the **first** `/`. Use `provider/model` when typing `/model <ref>`.
- If the model ID itself contains `/` (OpenRouter-style), you must include the provider prefix (example: `/model openrouter/moonshotai/kimi-k2`).
+1
View File
@@ -1156,6 +1156,7 @@
"tools/grok-search",
"tools/kimi-search",
"tools/perplexity-search",
"tools/searxng-search",
"tools/tavily"
]
},
+9 -1
View File
@@ -923,6 +923,7 @@ Time format in system prompt. Default: `auto` (OS preference).
primary: "anthropic/claude-opus-4-6",
fallbacks: ["openai/gpt-5-mini"],
},
params: { cacheRetention: "long" }, // global default provider params
pdfMaxBytesMb: 10,
pdfMaxPages: 20,
thinkingDefault: "low",
@@ -957,7 +958,8 @@ Time format in system prompt. Default: `auto` (OS preference).
- `elevatedDefault`: default elevated-output level for agents. Values: `"off"`, `"on"`, `"ask"`, `"full"`. Default: `"on"`.
- `model.primary`: format `provider/model` (e.g. `anthropic/claude-opus-4-6`). If you omit the provider, OpenClaw assumes `anthropic` (deprecated).
- `models`: the configured model catalog and allowlist for `/model`. Each entry can include `alias` (shortcut) and `params` (provider-specific, for example `temperature`, `maxTokens`, `cacheRetention`, `context1m`).
- `params` merge precedence (config): `agents.defaults.models["provider/model"].params` is the base, then `agents.list[].params` (matching agent id) overrides by key.
- `params`: global default provider parameters applied to all models. Set at `agents.defaults.params` (e.g. `{ cacheRetention: "long" }`).
- `params` merge precedence (config): `agents.defaults.params` (global base) is overridden by `agents.defaults.models["provider/model"].params` (per-model), then `agents.list[].params` (matching agent id) overrides by key. See [Prompt Caching](/reference/prompt-caching) for details.
- Config writers that mutate these fields (for example `/models set`, `/models set-image`, and fallback add/remove commands) save canonical object form and preserve existing fallback lists when possible.
- `maxConcurrent`: max parallel agent runs across sessions (each session still serialized). Default: 4.
@@ -3029,6 +3031,9 @@ Notes:
billingBackoffHoursByProvider: { anthropic: 3, openai: 8 },
billingMaxHours: 24,
failureWindowHours: 24,
overloadedProfileRotations: 1,
overloadedBackoffMs: 0,
rateLimitedProfileRotations: 1,
},
},
}
@@ -3038,6 +3043,9 @@ Notes:
- `billingBackoffHoursByProvider`: optional per-provider overrides for billing backoff hours.
- `billingMaxHours`: cap in hours for billing backoff exponential growth (default: `24`).
- `failureWindowHours`: rolling window in hours used for backoff counters (default: `24`).
- `overloadedProfileRotations`: maximum same-provider auth-profile rotations for overloaded errors before switching to model fallback (default: `1`).
- `overloadedBackoffMs`: fixed delay before retrying an overloaded provider/profile rotation (default: `0`).
- `rateLimitedProfileRotations`: maximum same-provider auth-profile rotations for rate-limit errors before switching to model fallback (default: `1`).
---
+8
View File
@@ -117,6 +117,7 @@ Doctor will:
The Gateway also auto-runs doctor migrations on startup when it detects a
legacy config format, so stale configs are repaired without manual intervention.
Cron job store migrations are handled by `openclaw doctor --fix`.
Current migrations:
@@ -303,6 +304,13 @@ catalog and allowlist and warns when it wont resolve or is disallowed.
When sandboxing is enabled, doctor checks Docker images and offers to build or
switch to legacy names if the current image is missing.
### 7b) Bundled plugin runtime deps
Doctor verifies that bundled plugin runtime dependencies (for example the
Discord plugin runtime packages) are present in the OpenClaw install root.
If any are missing, doctor reports the packages and installs them in
`openclaw doctor --fix` / `openclaw doctor --repair` mode.
### 8) Gateway service migrations and cleanup hints
Doctor detects legacy gateway services (launchd/systemd/schtasks) and
+33
View File
@@ -65,10 +65,43 @@ Notes:
- `node.pair.request` is idempotent per node: repeated calls return the same
pending request.
- Repeated requests for the same pending node also refresh the stored node
metadata and the latest allowlisted declared command snapshot for operator visibility.
- Approval **always** generates a fresh token; no token is ever returned from
`node.pair.request`.
- Requests may include `silent: true` as a hint for auto-approval flows.
Important:
- Node pairing is a trust/identity flow plus token issuance.
- It does **not** pin the live node command surface per node.
- Live node commands come from what the node declares on connect after the
gateway's global node command policy (`gateway.nodes.allowCommands` /
`denyCommands`) is applied.
- Per-node `system.run` allow/ask policy lives on the node in
`exec.approvals.node.*`, not in the pairing record.
## Node command gating (2026.3.31+)
<Warning>
**Breaking change:** Starting with `2026.3.31`, node commands are disabled until node pairing is approved. Device pairing alone is no longer enough to expose declared node commands.
</Warning>
When a node connects for the first time, pairing is requested automatically. Until the pairing request is approved, all pending node commands from that node are filtered and will not execute. Once trust is established through pairing approval, the node's declared commands become available subject to the normal command policy.
This means:
- Nodes that were previously relying on device pairing alone to expose commands must now complete node pairing.
- Commands queued before pairing approval are dropped, not deferred.
## Node event trust boundaries (2026.3.31+)
<Warning>
**Breaking change:** Node-originated runs now stay on a reduced trusted surface.
</Warning>
Node-originated summaries and related session events are restricted to the intended trusted surface. Notification-driven or node-triggered flows that previously relied on broader host or session tool access may need adjustment. This hardening ensures that node events cannot escalate into host-level tool access beyond what the node's trust boundary permits.
## Auto-approval (macOS app)
The macOS app can optionally attempt a **silent approval** when:
+9
View File
@@ -116,6 +116,7 @@ These patterns are commonly reported and are usually closed as no-action unless
- Claims that classify normal operator read-path access (for example `sessions.list`/`sessions.preview`/`chat.history`) as IDOR in a shared-gateway setup.
- Localhost-only deployment findings (for example HSTS on loopback-only gateway).
- Discord inbound webhook signature findings for inbound paths that do not exist in this repo.
- Reports that treat node pairing metadata as a hidden second per-command approval layer for `system.run`, when the real execution boundary is still the gateway's global node command policy plus the node's own exec approvals.
- "Missing per-user authorization" findings that treat `sessionKey` as an auth token.
## Researcher preflight checklist
@@ -370,10 +371,18 @@ stronger isolation between agents, run them under separate OS users or separate
If a macOS node is paired, the Gateway can invoke `system.run` on that node. This is **remote code execution** on the Mac:
- Requires node pairing (approval + token).
- Gateway node pairing is not a per-command approval surface. It establishes node identity/trust and token issuance.
- The Gateway applies a coarse global node command policy via `gateway.nodes.allowCommands` / `denyCommands`.
- Controlled on the Mac via **Settings → Exec approvals** (security + ask + allowlist).
- The per-node `system.run` policy is the node's own exec approvals file (`exec.approvals.node.*`), which can be stricter or looser than the gateway's global command-ID policy.
- Approval mode binds exact request context and, when possible, one concrete local script/file operand. If OpenClaw cannot identify exactly one direct local file for an interpreter/runtime command, approval-backed execution is denied rather than promising full semantic coverage.
- If you dont want remote execution, set security to **deny** and remove node pairing for that Mac.
This distinction matters for triage:
- A reconnecting paired node advertising a different command list is not, by itself, a vulnerability if the Gateway global policy and the node's local exec approvals still enforce the actual execution boundary.
- Reports that treat node pairing metadata as a second hidden per-command approval layer are usually policy/UX confusion, not a security boundary bypass.
## Dynamic skills (watcher / remote nodes)
OpenClaw can refresh the skills list mid-session:
+3 -3
View File
@@ -48,7 +48,7 @@ update **without** changing your persisted channel:
```bash
# Install a specific version
openclaw update --tag 2026.3.31-beta.1
openclaw update --tag 2026.4.1-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.31-beta.1
openclaw update --tag openclaw@2026.4.1-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.31-beta.1 --dry-run
openclaw update --tag 2026.4.1-beta.1 --dry-run
openclaw update --dry-run --json
```
+6 -2
View File
@@ -62,7 +62,8 @@ If you see `NODE_BACKGROUND_UNAVAILABLE`, bring the node app to the foreground a
These are different gates:
1. **Device pairing**: can this node connect to the gateway?
2. **Exec approvals**: can this node run a specific shell command?
2. **Gateway node command policy**: is the RPC command ID allowed by `gateway.nodes.allowCommands` / `denyCommands` and platform defaults?
3. **Exec approvals**: can this node run a specific shell command locally?
Quick checks:
@@ -74,7 +75,10 @@ openclaw approvals allowlist add --node <idOrNameOrIp> "/usr/bin/uname"
```
If pairing is missing, approve the node device first.
If pairing is fine but `system.run` fails, fix exec approvals/allowlist.
If `nodes describe` is missing a command, check the gateway node command policy and whether the node actually declared that command on connect.
If pairing is fine but `system.run` fails, fix exec approvals/allowlist on that node.
Node pairing is an identity/trust gate, not a per-command approval surface. For `system.run`, the per-node policy lives in that node's exec approvals file (`openclaw approvals get --node ...`), not in the gateway pairing record.
## Common node error codes
+35 -1
View File
@@ -159,10 +159,44 @@ See [Camera node](/nodes/camera) for parameters and CLI helpers.
- Voice wake/talk-mode toggles are currently removed from Android UX/runtime.
- Additional Android command families (availability depends on device + permissions):
- `device.status`, `device.info`, `device.permissions`, `device.health`
- `notifications.list`, `notifications.actions`
- `notifications.list`, `notifications.actions` (see [Notification forwarding](#notification-forwarding) below)
- `photos.latest`
- `contacts.search`, `contacts.add`
- `calendar.events`, `calendar.add`
- `callLog.search`
- `sms.search`
- `motion.activity`, `motion.pedometer`
## Notification forwarding
Android can forward device notifications to the gateway as events. Several controls let you scope which notifications are forwarded and when.
| Key | Type | Description |
| -------------------------------- | -------------- | ------------------------------------------------------------------------------------------------- |
| `notifications.allowPackages` | string[] | Only forward notifications from these package names. If set, all other packages are ignored. |
| `notifications.denyPackages` | string[] | Never forward notifications from these package names. Applied after `allowPackages`. |
| `notifications.quietHours.start` | string (HH:mm) | Start of quiet hours window (local device time). Notifications are suppressed during this window. |
| `notifications.quietHours.end` | string (HH:mm) | End of quiet hours window. |
| `notifications.rateLimit` | number | Maximum forwarded notifications per package per minute. Excess notifications are dropped. |
The notification picker also uses safer behavior for forwarded notification events, preventing accidental forwarding of sensitive system notifications.
Example configuration:
```json5
{
notifications: {
allowPackages: ["com.slack", "com.whatsapp"],
denyPackages: ["com.android.systemui"],
quietHours: {
start: "22:00",
end: "07:00",
},
rateLimit: 5,
},
}
```
<Note>
Notification forwarding requires the Android Notification Listener permission. The app prompts for this during setup.
</Note>
+2
View File
@@ -159,6 +159,8 @@ The current boundary is:
bookkeeping, and execution dispatch
- channel plugins own scoped action discovery, capability discovery, and any
channel-specific schema fragments
- channel plugins own provider-specific session conversation grammar, such as
how conversation ids encode thread ids or inherit from parent conversations
- channel plugins execute the final action through their action adapter
For channel plugins, the SDK surface is
+22 -2
View File
@@ -28,11 +28,31 @@ shared `message` tool in core. Your plugin owns:
- **Config** — account resolution and setup wizard
- **Security** — DM policy and allowlists
- **Pairing** — DM approval flow
- **Session grammar** — how provider-specific conversation ids map to base chats, thread ids, and parent fallbacks
- **Outbound** — sending text, media, and polls to the platform
- **Threading** — how replies are threaded
Core owns the shared message tool, prompt wiring, session bookkeeping, and
dispatch.
Core owns the shared message tool, prompt wiring, the outer session-key shape,
generic `:thread:` bookkeeping, and dispatch.
If your platform stores extra scope inside conversation ids, keep that parsing
in the plugin with `messaging.resolveSessionConversation(...)`. That is the
canonical hook for mapping `rawId` to the base conversation id, optional thread
id, explicit `baseConversationId`, and any `parentConversationCandidates`.
When you return `parentConversationCandidates`, keep them ordered from the
narrowest parent to the broadest/base conversation.
Bundled plugins that need the same parsing before the channel registry boots
can also expose a top-level `session-key-api.ts` file with a matching
`resolveSessionConversation(...)` export. Core uses that bootstrap-safe surface
only when the runtime plugin registry is not available yet.
`messaging.resolveParentConversationCandidates(...)` remains available as a
legacy compatibility fallback when a plugin only needs parent fallbacks on top
of the generic/raw id. If both hooks exist, core uses
`resolveSessionConversation(...).parentConversationCandidates` first and only
falls back to `resolveParentConversationCandidates(...)` when the canonical hook
omits them.
## Approvals and channel capabilities
+4 -2
View File
@@ -184,8 +184,10 @@ enabled). Otherwise Anthropic returns:
`HTTP 429: rate_limit_error: Extra usage is required for long context requests`.
Note: Anthropic currently rejects `context-1m-*` beta requests when using
OAuth/subscription tokens (`sk-ant-oat-*`). OpenClaw automatically skips the
context1m beta header for OAuth auth and keeps the required OAuth betas.
subscription setup-tokens (`sk-ant-oat-*`). If you configure `context1m: true`
with subscription auth, OpenClaw logs a warning and falls back to the standard
context window by skipping the context1m beta header while keeping the required
OAuth betas.
## Option B: Claude CLI as the message provider
+41
View File
@@ -174,3 +174,44 @@ openclaw models list
current capabilities.
- If you prefer a managed key flow, you can also place an OpenAIcompatible
proxy in front of Bedrock and configure it as an OpenAI provider instead.
## Guardrails
You can apply [Amazon Bedrock Guardrails](https://docs.aws.amazon.com/bedrock/latest/userguide/guardrails.html)
to all Bedrock model invocations by adding a `guardrail` object to the
`amazon-bedrock` plugin config. Guardrails let you enforce content filtering,
topic denial, word filters, sensitive information filters, and contextual
grounding checks.
```json5
{
plugins: {
entries: {
"amazon-bedrock": {
config: {
guardrail: {
guardrailIdentifier: "abc123", // guardrail ID or full ARN
guardrailVersion: "1", // version number or "DRAFT"
streamProcessingMode: "sync", // optional: "sync" or "async"
trace: "enabled", // optional: "enabled", "disabled", or "enabled_full"
},
},
},
},
},
}
```
- `guardrailIdentifier` (required) accepts a guardrail ID (e.g. `abc123`) or a
full ARN (e.g. `arn:aws:bedrock:us-east-1:123456789012:guardrail/abc123`).
- `guardrailVersion` (required) specifies which published version to use, or
`"DRAFT"` for the working draft.
- `streamProcessingMode` (optional) controls whether guardrail evaluation runs
synchronously (`"sync"`) or asynchronously (`"async"`) during streaming. If
omitted, Bedrock uses its default behavior.
- `trace` (optional) enables guardrail trace output in the API response. Set to
`"enabled"` or `"enabled_full"` for debugging; omit or set `"disabled"` for
production.
The IAM principal used by the gateway must have the `bedrock:ApplyGuardrail`
permission in addition to the standard invoke permissions.
+1 -1
View File
@@ -39,5 +39,5 @@ openclaw onboard --auth-choice zai-cn
## Notes
- GLM versions and availability can change; check Z.AI's docs for the latest.
- Example model IDs include `glm-5`, `glm-4.7`, and `glm-4.6`.
- Example model IDs include `glm-5.1`, `glm-5`, `glm-5v-turbo`, `glm-4.7`, and `glm-4.6`.
- For provider details, see [/providers/zai](/providers/zai).
+6 -2
View File
@@ -37,8 +37,9 @@ OpenClaw has three public release lanes:
## Release preflight
- Run `pnpm build` before `pnpm release:check` so the expected `dist/*` release
artifacts exist for the pack validation step
- Run `pnpm build && pnpm ui:build` before `pnpm release:check` so the expected
`dist/*` release artifacts and Control UI bundle exist for the pack
validation step
- Run `pnpm release:check` before every tagged release
- Run `RELEASE_TAG=vYYYY.M.D node --import tsx scripts/openclaw-npm-release-check.ts`
(or the matching beta/correction tag) before approval
@@ -46,6 +47,9 @@ OpenClaw has three public release lanes:
`node --import tsx scripts/openclaw-npm-postpublish-verify.ts YYYY.M.D`
(or the matching beta/correction version) to verify the published registry
install path in a fresh temp prefix
- Maintainer workflows may reuse a successful preflight run for the real
publish so the publish step promotes prepared release artifacts instead of
rebuilding them again
- For stable correction releases like `YYYY.M.D-N`, the post-publish verifier
also checks the same temp-prefix upgrade path from `YYYY.M.D` to `YYYY.M.D-N`
so release corrections cannot silently leave older global installs on the
+14 -4
View File
@@ -20,9 +20,18 @@ For Anthropic pricing details, see:
## Primary knobs
### `cacheRetention` (model and per-agent)
### `cacheRetention` (global default, model, and per-agent)
Set cache retention on model params:
Set cache retention as a global default for all models:
```yaml
agents:
defaults:
params:
cacheRetention: "long" # none | short | long
```
Override per-model:
```yaml
agents:
@@ -45,8 +54,9 @@ agents:
Config merge order:
1. `agents.defaults.models["provider/model"].params`
2. `agents.list[].params` (matching agent id; overrides by key)
1. `agents.defaults.params` (global default — applies to all models)
2. `agents.defaults.models["provider/model"].params` (per-model override)
3. `agents.list[].params` (matching agent id; overrides by key)
### Legacy `cacheControlTtl`
+2 -1
View File
@@ -415,9 +415,10 @@ resolved approver list for authorization even when native approval delivery is d
### Native approval delivery
Discord and Telegram can also act as native approval-delivery adapters with channel-specific config.
Discord, Slack, and Telegram can also act as native approval-delivery adapters with channel-specific config.
- Discord: `channels.discord.execApprovals.*`
- Slack: uses shared `approvals.exec.targets` with `channel: "slack"` and renders Block Kit approval buttons when interactivity is enabled
- Telegram: `channels.telegram.execApprovals.*`
These native delivery adapters are opt-in. They add DM routing and channel fanout on top of the
+1 -1
View File
@@ -10,7 +10,7 @@ read_when:
Lobster is a workflow shell that lets OpenClaw run multi-step tool sequences as a single, deterministic operation with explicit approval checkpoints.
Lobster is one authoring layer above [ClawFlow](/automation/clawflow). Lobster can decide the step logic, but ClawFlow still owns the job identity, owner context, and how detached work returns to the original conversation.
Lobster is one authoring layer above detached background work. If you run into older `ClawFlow` terminology, treat it as historical naming around the same task-oriented runtime area; the current operator-facing CLI surface is [`openclaw tasks`](/automation/tasks).
## Hook
+9
View File
@@ -58,6 +58,15 @@ tool with the `react` action. Reaction behavior varies by channel.
</Accordion>
</AccordionGroup>
## Reaction level
Per-channel `reactionLevel` config controls how broadly the agent uses reactions. Values are typically `off`, `ack`, `minimal`, or `extensive`.
- [Telegram reactionLevel](/channels/telegram#reaction-notifications) — `channels.telegram.reactionLevel`
- [WhatsApp reactionLevel](/channels/whatsapp#reactions) — `channels.whatsapp.reactionLevel`
Set `reactionLevel` on individual channels to tune how actively the agent reacts to messages on each platform.
## Related
- [Agent Send](/tools/agent-send) — the `message` tool that includes `react`
+124
View File
@@ -0,0 +1,124 @@
---
summary: "SearXNG web search -- self-hosted, key-free meta-search provider"
read_when:
- You want a self-hosted web search provider
- You want to use SearXNG for web_search
- You need a privacy-focused or air-gapped search option
title: "SearXNG Search"
---
# SearXNG Search
OpenClaw supports [SearXNG](https://docs.searxng.org/) as a **self-hosted,
key-free** `web_search` provider. SearXNG is an open-source meta-search engine
that aggregates results from Google, Bing, DuckDuckGo, and other sources.
Advantages:
- **Free and unlimited** -- no API key or commercial subscription required
- **Privacy / air-gap** -- queries never leave your network
- **Works anywhere** -- no region restrictions on commercial search APIs
## Setup
<Steps>
<Step title="Run a SearXNG instance">
```bash
docker run -d -p 8888:8080 searxng/searxng
```
Or use any existing SearXNG deployment you have access to. See the
[SearXNG documentation](https://docs.searxng.org/) for production setup.
</Step>
<Step title="Configure">
```bash
openclaw configure --section web
# Select "searxng" as the provider
```
Or set the env var and let auto-detection find it:
```bash
export SEARXNG_BASE_URL="http://localhost:8888"
```
</Step>
</Steps>
## Config
```json5
{
tools: {
web: {
search: {
provider: "searxng",
},
},
},
}
```
Plugin-level settings for the SearXNG instance:
```json5
{
plugins: {
entries: {
searxng: {
config: {
webSearch: {
baseUrl: "http://localhost:8888",
categories: "general,news", // optional
language: "en", // optional
},
},
},
},
},
}
```
The `baseUrl` field also accepts SecretRef objects.
## Environment variable
Set `SEARXNG_BASE_URL` as an alternative to config:
```bash
export SEARXNG_BASE_URL="http://localhost:8888"
```
When `SEARXNG_BASE_URL` is set and no explicit provider is configured, auto-detection
picks SearXNG automatically (at the lowest priority -- any API-backed provider with a
key wins first).
## Plugin config reference
| Field | Description |
| ------------ | ------------------------------------------------------------------ |
| `baseUrl` | Base URL of your SearXNG instance (required) |
| `categories` | Comma-separated categories such as `general`, `news`, or `science` |
| `language` | Language code for results such as `en`, `de`, or `fr` |
## Notes
- **JSON API** -- uses SearXNG's native `format=json` endpoint, not HTML scraping
- **No API key** -- works with any SearXNG instance out of the box
- **Auto-detection order** -- SearXNG is checked last (order 200) in auto-detection,
so any API-backed provider with a key takes priority over SearXNG, and SearXNG sits
behind DuckDuckGo (order 100) as well
- **Self-hosted** -- you control the instance, queries, and upstream search engines
- **Categories** default to `general` when not configured
<Tip>
For SearXNG JSON API to work, make sure your SearXNG instance has the `json`
format enabled in its `settings.yml` under `search.formats`.
</Tip>
## Related
- [Web Search overview](/tools/web) -- all providers and auto-detection
- [DuckDuckGo Search](/tools/duckduckgo-search) -- another key-free fallback
- [Brave Search](/tools/brave-search) -- structured results with free tier
+2
View File
@@ -78,6 +78,7 @@ Text + native (when enabled):
- `/tools [compact|verbose]` (show what the current agent can use right now; `verbose` adds descriptions)
- `/skill <name> [input]` (run a skill by name)
- `/status` (show current status; includes provider usage/quota for the current model provider when available)
- `/tasks` (list background tasks for the current session; shows active and recent task details with agent-local fallback counts)
- `/allowlist` (list/add/remove allowlist entries)
- `/approve <id> allow-once|allow-always|deny` (resolve exec approval prompts)
- `/context [list|detail|json]` (explain “context”; `detail` shows per-file + per-tool + per-skill + system prompt size)
@@ -146,6 +147,7 @@ Notes:
- `/fast` is provider-specific: OpenAI/OpenAI Codex map it to `service_tier=priority` on native Responses endpoints, while direct public Anthropic requests, including OAuth-authenticated traffic sent to `api.anthropic.com`, map it to `service_tier=auto` or `standard_only`. See [OpenAI](/providers/openai) and [Anthropic](/providers/anthropic).
- Tool failure summaries are still shown when relevant, but detailed failure text is only included when `/verbose` is `on` or `full`.
- `/reasoning` (and `/verbose`) are risky in group settings: they may reveal internal reasoning or tool output you did not intend to expose. Prefer leaving them off, especially in group chats.
- `/model` persists the new session model immediately, but it does not interrupt a busy run. The current turn finishes first, then queued or future work uses the updated model.
- **Fast path:** command-only messages from allowlisted senders are handled immediately (bypass queue + model).
- **Group mention gating:** command-only messages from allowlisted senders bypass mention requirements.
- **Inline shortcuts (allowlisted senders only):** certain commands also work when embedded in a normal message and are stripped before the model sees the remaining text.
+11 -2
View File
@@ -81,6 +81,9 @@ local while `web_search` and `x_search` can use xAI Responses under the hood.
<Card title="Perplexity" icon="search" href="/tools/perplexity-search">
Structured results with content extraction controls and domain filtering.
</Card>
<Card title="SearXNG" icon="server" href="/tools/searxng-search">
Self-hosted meta-search. No API key needed. Aggregates Google, Bing, DuckDuckGo, and more.
</Card>
<Card title="Tavily" icon="globe" href="/tools/tavily">
Structured results with search depth, topic filtering, and `tavily_extract` for URL extraction.
</Card>
@@ -98,6 +101,7 @@ local while `web_search` and `x_search` can use xAI Responses under the hood.
| [Grok](/tools/grok-search) | AI-synthesized + citations | -- | `XAI_API_KEY` |
| [Kimi](/tools/kimi-search) | AI-synthesized + citations | -- | `KIMI_API_KEY` / `MOONSHOT_API_KEY` |
| [Perplexity](/tools/perplexity-search) | Structured snippets | Country, language, time, domains, content limits | `PERPLEXITY_API_KEY` / `OPENROUTER_API_KEY` |
| [SearXNG](/tools/searxng-search) | Structured snippets | Categories, language | None (self-hosted) |
| [Tavily](/tools/tavily) | Structured snippets | Via `tavily_search` tool | `TAVILY_API_KEY` |
## Auto-detection
@@ -153,8 +157,13 @@ the first one found:
6. **Firecrawl** -- `FIRECRAWL_API_KEY` or `plugins.entries.firecrawl.config.webSearch.apiKey`
7. **Tavily** -- `TAVILY_API_KEY` or `plugins.entries.tavily.config.webSearch.apiKey`
If no keys are found, it falls back to Brave (you will get a missing-key error
prompting you to configure one).
Key-free providers are checked after API-backed providers:
8. **DuckDuckGo** -- no key needed (auto-detect order 100)
9. **SearXNG** -- `SEARXNG_BASE_URL` or `plugins.entries.searxng.config.webSearch.baseUrl` (auto-detect order 200)
If no provider is detected, it falls back to Brave (you will get a missing-key
error prompting you to configure one).
<Note>
All provider key fields support SecretRef objects. In auto-detect mode,
+2 -2
View File
@@ -52,9 +52,9 @@ Status: the macOS/iOS SwiftUI chat UI talks directly to the Gateway WebSocket.
Full configuration: [Configuration](/gateway/configuration)
Channel options:
WebChat options:
- No dedicated `webchat.*` block. WebChat uses the gateway endpoint + auth settings below.
- `gateway.webchat.chatHistoryMaxChars`: maximum character count for text fields in `chat.history` responses. When a transcript entry exceeds this limit, Gateway truncates long text fields and may replace oversized messages with a placeholder. Per-request `maxChars` can also be sent by the client to override this default for a single `chat.history` call.
Related global options:
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@openclaw/acpx",
"version": "2026.3.31-beta.1",
"version": "2026.4.1-beta.1",
"description": "OpenClaw ACP runtime backend via acpx",
"type": "module",
"dependencies": {
@@ -116,6 +116,36 @@ describe("resolveSpawnCommand", () => {
});
});
it("falls back to node on PATH when execPath is unavailable for a node shebang wrapper", async () => {
const dir = await createTempDir();
const binDir = path.join(dir, "bin");
const scriptPath = path.join(binDir, "acpx");
const nodePath = path.join(binDir, "node");
await mkdir(binDir, { recursive: true });
await writeFile(scriptPath, "#!/usr/bin/env node\nconsole.log('ok')\n", "utf8");
await writeFile(nodePath, "#!/bin/sh\nexit 0\n", "utf8");
await chmod(scriptPath, 0o755);
await chmod(nodePath, 0o755);
const resolved = resolveSpawnCommand(
{
command: scriptPath,
args: ["--help"],
},
undefined,
{
platform: "darwin",
env: { PATH: binDir },
execPath: "/missing/node",
},
);
expect(resolved).toEqual({
command: nodePath,
args: [scriptPath, "--help"],
});
});
it("routes .js command execution through node on windows", () => {
const resolved = resolveSpawnCommand(
{
@@ -88,6 +88,13 @@ function resolveExecutableFromPath(command: string, runtime: SpawnRuntime): stri
return undefined;
}
function resolveNodeExecPath(runtime: SpawnRuntime): string {
if (runtime.execPath && isExecutableFile(runtime.execPath, runtime.platform)) {
return runtime.execPath;
}
return resolveExecutableFromPath("node", runtime) ?? runtime.execPath;
}
function resolveNodeShebangScriptPath(command: string, runtime: SpawnRuntime): string | undefined {
const commandPath =
path.isAbsolute(command) || command.includes(path.sep)
@@ -122,7 +129,7 @@ export function resolveSpawnCommand(
resolution: "direct",
});
return {
command: runtime.execPath,
command: resolveNodeExecPath(runtime),
args: [nodeShebangScript, ...params.args],
};
}
+161 -6
View File
@@ -47,6 +47,7 @@ async function expectSessionEnsureFallback(params: {
env?: Record<string, string>;
expectNewAfterStatus: boolean;
expectedRecordId?: string;
expectedResumeSessionId?: string | null;
}) {
const previousEnv = new Map<string, string | undefined>();
for (const [key, value] of Object.entries(params.env ?? {})) {
@@ -70,13 +71,26 @@ async function expectSessionEnsureFallback(params: {
const logs = await readMockRuntimeLogEntries(logPath);
const ensureIndex = logs.findIndex((entry) => entry.kind === "ensure");
const statusIndex = logs.findIndex((entry) => entry.kind === "status");
const newIndex = logs.findIndex((entry) => entry.kind === "new");
const newEntries = logs.filter((entry) => entry.kind === "new");
const newEntry = newEntries[0] ?? null;
const newIndex = newEntry ? logs.indexOf(newEntry) : -1;
expect(ensureIndex).toBeGreaterThanOrEqual(0);
expect(statusIndex).toBeGreaterThan(ensureIndex);
if (params.expectNewAfterStatus) {
expect(newEntries).toHaveLength(1);
expect(newIndex).toBeGreaterThan(statusIndex);
} else {
expect(newIndex).toBe(-1);
expect(newEntries).toHaveLength(0);
}
const newArgs = ((newEntry?.args as string[]) ?? []).slice();
const resumeFlagIndex = newArgs.indexOf("--resume-session");
if (params.expectedResumeSessionId === undefined) {
// No assertion requested for resume behavior.
} else if (params.expectedResumeSessionId === null) {
expect(resumeFlagIndex).toBe(-1);
} else {
expect(resumeFlagIndex).toBeGreaterThanOrEqual(0);
expect(newArgs[resumeFlagIndex + 1]).toBe(params.expectedResumeSessionId);
}
} finally {
for (const [key, value] of previousEnv.entries()) {
@@ -241,14 +255,15 @@ describe("AcpxRuntime", () => {
expect(resumeArgs[resumeFlagIndex + 1]).toBe(resumeSessionId);
});
it("retains dead named sessions when status only reports queue owner unavailable", async () => {
it("repairs dead named sessions when status only reports queue owner unavailable", async () => {
await expectSessionEnsureFallback({
sessionKey: "agent:codex:acp:dead-session",
env: {
MOCK_ACPX_STATUS_STATUS: "dead",
MOCK_ACPX_STATUS_SUMMARY: "queue owner unavailable",
},
expectNewAfterStatus: false,
expectNewAfterStatus: true,
expectedResumeSessionId: "sid-agent:codex:acp:dead-session",
});
});
@@ -275,7 +290,7 @@ describe("AcpxRuntime", () => {
});
});
it("retains the named session after ensure failure when status only reports queue owner unavailable", async () => {
it("repairs the named session after ensure failure when status only reports queue owner unavailable", async () => {
await expectSessionEnsureFallback({
sessionKey: "agent:codex:acp:ensure-fallback-dead",
env: {
@@ -283,11 +298,73 @@ describe("AcpxRuntime", () => {
MOCK_ACPX_STATUS_STATUS: "dead",
MOCK_ACPX_STATUS_SUMMARY: "queue owner unavailable",
},
expectNewAfterStatus: false,
expectNewAfterStatus: true,
expectedRecordId: "rec-agent:codex:acp:ensure-fallback-dead",
expectedResumeSessionId: "sid-agent:codex:acp:ensure-fallback-dead",
});
});
it("falls back to a fresh named session when queue owner recovery has no resumable id", async () => {
await expectSessionEnsureFallback({
sessionKey: "agent:codex:acp:dead-session-no-ids",
env: {
MOCK_ACPX_STATUS_STATUS: "dead",
MOCK_ACPX_STATUS_SUMMARY: "queue owner unavailable",
MOCK_ACPX_STATUS_NO_IDS: "1",
},
expectNewAfterStatus: true,
expectedResumeSessionId: null,
});
});
it("falls back to a fresh named session when queue owner resume repair uses a stale session id", async () => {
const previousResumeFailure = process.env.MOCK_ACPX_NEW_FAIL_ON_RESUME;
const previousStatus = process.env.MOCK_ACPX_STATUS_STATUS;
const previousSummary = process.env.MOCK_ACPX_STATUS_SUMMARY;
process.env.MOCK_ACPX_NEW_FAIL_ON_RESUME = "1";
process.env.MOCK_ACPX_STATUS_STATUS = "dead";
process.env.MOCK_ACPX_STATUS_SUMMARY = "queue owner unavailable";
try {
const { runtime, logPath } = await createMockRuntimeFixture();
const handle = await runtime.ensureSession({
sessionKey: "agent:codex:acp:dead-session-stale-resume",
agent: "codex",
mode: "persistent",
});
expect(handle.backend).toBe("acpx");
const logs = await readMockRuntimeLogEntries(logPath);
const newEntries = logs.filter((entry) => entry.kind === "new");
expect(newEntries).toHaveLength(2);
const firstArgs = ((newEntries[0]?.args as string[]) ?? []).slice();
const secondArgs = ((newEntries[1]?.args as string[]) ?? []).slice();
const firstResumeFlagIndex = firstArgs.indexOf("--resume-session");
expect(firstResumeFlagIndex).toBeGreaterThanOrEqual(0);
expect(firstArgs[firstResumeFlagIndex + 1]).toBe(
"sid-agent:codex:acp:dead-session-stale-resume",
);
expect(secondArgs.indexOf("--resume-session")).toBe(-1);
} finally {
if (previousResumeFailure === undefined) {
delete process.env.MOCK_ACPX_NEW_FAIL_ON_RESUME;
} else {
process.env.MOCK_ACPX_NEW_FAIL_ON_RESUME = previousResumeFailure;
}
if (previousStatus === undefined) {
delete process.env.MOCK_ACPX_STATUS_STATUS;
} else {
process.env.MOCK_ACPX_STATUS_STATUS = previousStatus;
}
if (previousSummary === undefined) {
delete process.env.MOCK_ACPX_STATUS_SUMMARY;
} else {
process.env.MOCK_ACPX_STATUS_SUMMARY = previousSummary;
}
}
});
it("creates a fresh named session after ensure failure when status indicates an unrecoverable failure", async () => {
await expectSessionEnsureFallback({
sessionKey: "agent:codex:acp:ensure-fallback-dead-unrecoverable",
@@ -300,6 +377,84 @@ describe("AcpxRuntime", () => {
});
});
it("surfaces structured control-command errors from sessions ensure", async () => {
const previousEnsureExit = process.env.MOCK_ACPX_ENSURE_EXIT_1;
const previousStatusSignal = process.env.MOCK_ACPX_STATUS_SIGNAL;
process.env.MOCK_ACPX_ENSURE_EXIT_1 = "1";
process.env.MOCK_ACPX_STATUS_SIGNAL = "SIGTERM";
try {
const { runtime } = await createMockRuntimeFixture();
await expect(
runtime.ensureSession({
sessionKey: "agent:codex:acp:ensure-structured-error",
agent: "codex",
mode: "persistent",
}),
).rejects.toMatchObject({
code: "ACP_SESSION_INIT_FAILED",
message: "-32603: mock ensure failure",
});
} finally {
if (previousEnsureExit === undefined) {
delete process.env.MOCK_ACPX_ENSURE_EXIT_1;
} else {
process.env.MOCK_ACPX_ENSURE_EXIT_1 = previousEnsureExit;
}
if (previousStatusSignal === undefined) {
delete process.env.MOCK_ACPX_STATUS_SIGNAL;
} else {
process.env.MOCK_ACPX_STATUS_SIGNAL = previousStatusSignal;
}
}
});
it("appends stderr details when control-command errors are generic", async () => {
const previousEnsureExit = process.env.MOCK_ACPX_ENSURE_EXIT_1;
const previousEnsureMessage = process.env.MOCK_ACPX_ENSURE_ERROR_MESSAGE;
const previousEnsureStderr = process.env.MOCK_ACPX_ENSURE_STDERR;
const previousStatusSignal = process.env.MOCK_ACPX_STATUS_SIGNAL;
process.env.MOCK_ACPX_ENSURE_EXIT_1 = "1";
process.env.MOCK_ACPX_ENSURE_ERROR_MESSAGE = "Internal error";
process.env.MOCK_ACPX_ENSURE_STDERR = "usage limit exceeded";
process.env.MOCK_ACPX_STATUS_SIGNAL = "SIGTERM";
try {
const { runtime } = await createMockRuntimeFixture();
await expect(
runtime.ensureSession({
sessionKey: "agent:codex:acp:ensure-generic-error",
agent: "codex",
mode: "persistent",
}),
).rejects.toMatchObject({
code: "ACP_SESSION_INIT_FAILED",
message: "-32603: Internal error | usage limit exceeded",
});
} finally {
if (previousEnsureExit === undefined) {
delete process.env.MOCK_ACPX_ENSURE_EXIT_1;
} else {
process.env.MOCK_ACPX_ENSURE_EXIT_1 = previousEnsureExit;
}
if (previousEnsureMessage === undefined) {
delete process.env.MOCK_ACPX_ENSURE_ERROR_MESSAGE;
} else {
process.env.MOCK_ACPX_ENSURE_ERROR_MESSAGE = previousEnsureMessage;
}
if (previousEnsureStderr === undefined) {
delete process.env.MOCK_ACPX_ENSURE_STDERR;
} else {
process.env.MOCK_ACPX_ENSURE_STDERR = previousEnsureStderr;
}
if (previousStatusSignal === undefined) {
delete process.env.MOCK_ACPX_STATUS_SIGNAL;
} else {
process.env.MOCK_ACPX_STATUS_SIGNAL = previousStatusSignal;
}
}
});
it("serializes text plus image attachments into ACP prompt blocks", async () => {
const { runtime, logPath } = await createMockRuntimeFixture();
+146 -43
View File
@@ -14,6 +14,7 @@ import type {
import { AcpRuntimeError } from "../runtime-api.js";
import { toAcpMcpServers, type ResolvedAcpxPluginConfig } from "./config.js";
import { checkAcpxVersion, type AcpxVersionCheckResult } from "./ensure.js";
import { parseControlJsonError } from "./runtime-internals/control-errors.js";
import {
parseJsonLines,
parsePromptEventLine,
@@ -73,6 +74,11 @@ type AcpxHealthCheckResult =
};
};
type EnsureFailureRecoveryResult = {
events: AcpxJsonObject[];
skipPostEnsureReplacement: boolean;
};
function formatPermissionModeGuidance(): string {
return "Configure plugins.entries.acpx.config.permissionMode to one of: approve-reads, approve-all, deny-all.";
}
@@ -128,6 +134,29 @@ function shouldRetainNamedSessionForDeadStatus(detail: AcpxJsonObject | undefine
return summary?.includes("queue owner unavailable") ?? false;
}
function resolveResumeSessionIdFromDetail(detail: AcpxJsonObject | undefined): string | undefined {
return asOptionalString(detail?.acpxSessionId) ?? asOptionalString(detail?.agentSessionId);
}
function formatAcpxControlErrorMessage(params: {
code?: string;
message: string;
stderr: string;
}): string {
const baseMessage = params.code ? `${params.code}: ${params.message}` : params.message;
const stderrSummary = summarizeLogText(params.stderr);
if (!stderrSummary) {
return baseMessage;
}
if (
/^(?:internal error|acpx reported an error)$/i.test(params.message) &&
!baseMessage.includes(stderrSummary)
) {
return `${baseMessage} | ${stderrSummary}`;
}
return baseMessage;
}
function findSessionIdentifierEvent(events: AcpxJsonObject[]): AcpxJsonObject | undefined {
return events.find(
(event) =>
@@ -330,11 +359,54 @@ export class AcpxRuntime implements AcpRuntime {
});
}
private async replaceDeadNamedSession(params: {
detail: AcpxJsonObject | undefined;
sessionName: string;
agent: string;
cwd: string;
logContext: string;
}): Promise<AcpxJsonObject[]> {
const resumeSessionId = resolveResumeSessionIdFromDetail(params.detail);
if (!resumeSessionId) {
this.logger?.warn?.(
`acpx ensureSession repairing dead named session with fresh session owner: session=${params.sessionName} cwd=${params.cwd} ${params.logContext}`,
);
return await this.createNamedSession({
agent: params.agent,
cwd: params.cwd,
sessionName: params.sessionName,
});
}
this.logger?.warn?.(
`acpx ensureSession repairing dead named session by resuming backend session: session=${params.sessionName} cwd=${params.cwd} resumeSessionId=${resumeSessionId} ${params.logContext}`,
);
try {
return await this.createNamedSession({
agent: params.agent,
cwd: params.cwd,
sessionName: params.sessionName,
resumeSessionId,
});
} catch (error) {
if (!(error instanceof AcpRuntimeError) || error.code !== "ACP_SESSION_INIT_FAILED") {
throw error;
}
this.logger?.warn?.(
`acpx ensureSession dead-session resume repair failed; retrying with fresh session owner: session=${params.sessionName} cwd=${params.cwd} resumeSessionId=${resumeSessionId} error=${summarizeLogText(error.message) || "<empty>"} ${params.logContext}`,
);
return await this.createNamedSession({
agent: params.agent,
cwd: params.cwd,
sessionName: params.sessionName,
});
}
}
private async shouldReplaceEnsuredSession(params: {
sessionName: string;
agent: string;
cwd: string;
}): Promise<boolean> {
}): Promise<{ replace: boolean; replacementEvents?: AcpxJsonObject[] }> {
const args = await this.buildVerbArgs({
agent: params.agent,
cwd: params.cwd,
@@ -352,7 +424,7 @@ export class AcpxRuntime implements AcpRuntime {
this.logger?.warn?.(
`acpx ensureSession status probe failed: session=${params.sessionName} cwd=${params.cwd} error=${summarizeLogText(error instanceof Error ? error.message : String(error)) || "<empty>"}`,
);
return false;
return { replace: false };
}
const noSession = events.some((event) => toAcpxErrorEvent(event)?.code === "NO_SESSION");
@@ -360,7 +432,7 @@ export class AcpxRuntime implements AcpRuntime {
this.logger?.warn?.(
`acpx ensureSession replacing missing named session: session=${params.sessionName} cwd=${params.cwd}`,
);
return true;
return { replace: true };
}
const detail = events.find((event) => !toAcpxErrorEvent(event));
@@ -368,18 +440,24 @@ export class AcpxRuntime implements AcpRuntime {
if (status === "dead") {
const summary = summarizeLogText(asOptionalString(detail?.summary) ?? "");
if (shouldRetainNamedSessionForDeadStatus(detail)) {
this.logger?.warn?.(
`acpx ensureSession retaining dead named session with recoverable status: session=${params.sessionName} cwd=${params.cwd} status=${status} summary=${summary || "<empty>"}`,
);
return false;
return {
replace: true,
replacementEvents: await this.replaceDeadNamedSession({
detail,
sessionName: params.sessionName,
agent: params.agent,
cwd: params.cwd,
logContext: `status=${status} summary=${summary || "<empty>"}`,
}),
};
}
this.logger?.warn?.(
`acpx ensureSession replacing dead named session: session=${params.sessionName} cwd=${params.cwd} status=${status} summary=${summary || "<empty>"}`,
);
return true;
return { replace: true };
}
return false;
return { replace: false };
}
private async recoverEnsureFailure(params: {
@@ -387,7 +465,7 @@ export class AcpxRuntime implements AcpRuntime {
agent: string;
cwd: string;
error: unknown;
}): Promise<AcpxJsonObject[] | null> {
}): Promise<EnsureFailureRecoveryResult | null> {
const errorMessage = summarizeLogText(
params.error instanceof Error ? params.error.message : String(params.error),
);
@@ -419,11 +497,14 @@ export class AcpxRuntime implements AcpRuntime {
this.logger?.warn?.(
`acpx ensureSession creating named session after ensure failure and missing status: session=${params.sessionName} cwd=${params.cwd}`,
);
return await this.createNamedSession({
agent: params.agent,
cwd: params.cwd,
sessionName: params.sessionName,
});
return {
events: await this.createNamedSession({
agent: params.agent,
cwd: params.cwd,
sessionName: params.sessionName,
}),
skipPostEnsureReplacement: true,
};
}
const detail = events.find((event) => !toAcpxErrorEvent(event));
@@ -431,26 +512,38 @@ export class AcpxRuntime implements AcpRuntime {
if (status === "dead") {
const summary = summarizeLogText(asOptionalString(detail?.summary) ?? "");
if (shouldRetainNamedSessionForDeadStatus(detail)) {
this.logger?.warn?.(
`acpx ensureSession retaining dead named session after ensure failure with recoverable status: session=${params.sessionName} cwd=${params.cwd} status=${status} summary=${summary || "<empty>"}`,
);
return events;
return {
events: await this.replaceDeadNamedSession({
detail,
sessionName: params.sessionName,
agent: params.agent,
cwd: params.cwd,
logContext: `status=${status} summary=${summary || "<empty>"}`,
}),
skipPostEnsureReplacement: true,
};
}
this.logger?.warn?.(
`acpx ensureSession replacing dead named session after ensure failure: session=${params.sessionName} cwd=${params.cwd}`,
);
return await this.createNamedSession({
agent: params.agent,
cwd: params.cwd,
sessionName: params.sessionName,
});
return {
events: await this.createNamedSession({
agent: params.agent,
cwd: params.cwd,
sessionName: params.sessionName,
}),
skipPostEnsureReplacement: true,
};
}
if (status === "alive" || findSessionIdentifierEvent(events)) {
this.logger?.warn?.(
`acpx ensureSession reusing live named session after ensure failure: session=${params.sessionName} cwd=${params.cwd} status=${status || "unknown"}`,
);
return events;
return {
events,
skipPostEnsureReplacement: false,
};
}
return null;
@@ -469,6 +562,7 @@ export class AcpxRuntime implements AcpRuntime {
const mode = input.mode;
const resumeSessionId = asTrimmedString(input.resumeSessionId);
let events: AcpxJsonObject[];
let skipPostEnsureReplacement = false;
if (resumeSessionId) {
events = await this.createNamedSession({
agent,
@@ -497,7 +591,8 @@ export class AcpxRuntime implements AcpRuntime {
if (!recovered) {
throw error;
}
events = recovered;
events = recovered.events;
skipPostEnsureReplacement = recovered.skipPostEnsureReplacement;
}
}
if (events.length === 0) {
@@ -507,26 +602,27 @@ export class AcpxRuntime implements AcpRuntime {
}
let ensuredEvent = findSessionIdentifierEvent(events);
if (
ensuredEvent &&
!resumeSessionId &&
(await this.shouldReplaceEnsuredSession({
if (ensuredEvent && !resumeSessionId && !skipPostEnsureReplacement) {
const replacement = await this.shouldReplaceEnsuredSession({
sessionName,
agent,
cwd,
}))
) {
events = await this.createNamedSession({
agent,
cwd,
sessionName,
});
if (events.length === 0) {
this.logger?.warn?.(
`acpx ensureSession returned no events after replacing dead session: session=${sessionName} agent=${agent} cwd=${cwd}`,
);
if (replacement.replace) {
events =
replacement.replacementEvents ??
(await this.createNamedSession({
agent,
cwd,
sessionName,
}));
if (events.length === 0) {
this.logger?.warn?.(
`acpx ensureSession returned no events after replacing dead session: session=${sessionName} agent=${agent} cwd=${cwd}`,
);
}
ensuredEvent = findSessionIdentifierEvent(events);
}
ensuredEvent = findSessionIdentifierEvent(events);
}
if (!ensuredEvent && !resumeSessionId) {
@@ -1037,14 +1133,21 @@ export class AcpxRuntime implements AcpRuntime {
}
const events = parseJsonLines(result.stdout);
const errorEvent = events.map((event) => toAcpxErrorEvent(event)).find(Boolean) ?? null;
const errorEvent =
events
.map((event) => toAcpxErrorEvent(event) ?? parseControlJsonError(event))
.find(Boolean) ?? null;
if (errorEvent) {
if (params.ignoreNoSession && errorEvent.code === "NO_SESSION") {
return events;
}
throw new AcpRuntimeError(
params.fallbackCode,
errorEvent.code ? `${errorEvent.code}: ${errorEvent.message}` : errorEvent.message,
formatAcpxControlErrorMessage({
code: errorEvent.code,
message: errorEvent.message,
stderr: result.stderr,
}),
);
}
+134
View File
@@ -1,3 +1,6 @@
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { AcpRuntimeError } from "openclaw/plugin-sdk/acp-runtime";
import {
__testing,
@@ -20,6 +23,11 @@ vi.mock("./ensure.js", () => ({
type RuntimeStub = AcpRuntime & {
probeAvailability(): Promise<void>;
isHealthy(): boolean;
doctor?(): Promise<{
ok: boolean;
message: string;
details?: string[];
}>;
};
function createRuntimeStub(healthy: boolean): {
@@ -53,6 +61,56 @@ function createRuntimeStub(healthy: boolean): {
};
}
function createRetryingRuntimeStub(
healthSequence: boolean[],
doctorReport: { ok: boolean; message: string; details?: string[] } = {
ok: false,
message: "acpx help check failed",
details: ["stderr=temporary startup race"],
},
): {
runtime: RuntimeStub;
probeAvailabilitySpy: ReturnType<typeof vi.fn>;
isHealthySpy: ReturnType<typeof vi.fn>;
doctorSpy: ReturnType<typeof vi.fn>;
} {
let probeCount = 0;
const probeAvailabilitySpy = vi.fn(async () => {
probeCount += 1;
});
const isHealthySpy = vi.fn(() => {
const index = Math.max(0, probeCount - 1);
return healthSequence[Math.min(index, healthSequence.length - 1)] ?? false;
});
const doctorSpy = vi.fn(async () => doctorReport);
return {
runtime: {
ensureSession: vi.fn(async (input) => ({
sessionKey: input.sessionKey,
backend: "acpx",
runtimeSessionName: input.sessionKey,
})),
runTurn: vi.fn(async function* () {
yield { type: "done" as const };
}),
cancel: vi.fn(async () => {}),
close: vi.fn(async () => {}),
async probeAvailability() {
await probeAvailabilitySpy();
},
isHealthy() {
return isHealthySpy();
},
async doctor() {
return await doctorSpy();
},
},
probeAvailabilitySpy,
isHealthySpy,
doctorSpy,
};
}
function createServiceContext(
overrides: Partial<OpenClawPluginServiceContext> = {},
): OpenClawPluginServiceContext {
@@ -105,6 +163,7 @@ describe("createAcpxRuntimeService", () => {
const { runtime } = createRuntimeStub(false);
const service = createAcpxRuntimeService({
runtimeFactory: () => runtime,
healthProbeRetryDelaysMs: [],
});
const context = createServiceContext();
@@ -177,4 +236,79 @@ describe("createAcpxRuntimeService", () => {
expect(startResult).toBe("started");
expect(getAcpRuntimeBackend("acpx")?.runtime).toBe(runtime);
});
it("creates the workspace dir before probing acpx", async () => {
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "acpx-service-workspace-"));
const workspaceDir = path.join(tempRoot, "workspace");
const { runtime, probeAvailabilitySpy } = createRuntimeStub(true);
const service = createAcpxRuntimeService({
runtimeFactory: ({ pluginConfig }) => {
expect(pluginConfig.cwd).toBe(workspaceDir);
return runtime;
},
});
const context = createServiceContext({ workspaceDir });
try {
await service.start(context);
expect(fs.existsSync(workspaceDir)).toBe(true);
await vi.waitFor(() => {
expect(probeAvailabilitySpy).toHaveBeenCalledOnce();
});
} finally {
await service.stop?.(context);
fs.rmSync(tempRoot, { recursive: true, force: true });
}
});
it("retries health probes until the runtime becomes healthy", async () => {
const { runtime, probeAvailabilitySpy, doctorSpy } = createRetryingRuntimeStub([
false,
false,
true,
]);
const service = createAcpxRuntimeService({
runtimeFactory: () => runtime,
healthProbeRetryDelaysMs: [0, 0],
});
const context = createServiceContext();
await service.start(context);
await vi.waitFor(() => {
expect(probeAvailabilitySpy).toHaveBeenCalledTimes(3);
});
expect(doctorSpy).toHaveBeenCalledTimes(2);
expect(context.logger.warn).toHaveBeenCalledWith(
expect.stringContaining("probe attempt 1 failed"),
);
expect(context.logger.info).toHaveBeenCalledWith(
"acpx runtime backend ready after 3 probe attempts",
);
});
it("does not treat doctor ok as healthy when the runtime still reports unhealthy", async () => {
const { runtime, probeAvailabilitySpy, doctorSpy } = createRetryingRuntimeStub([false], {
ok: true,
message: "acpx help check passed",
});
const service = createAcpxRuntimeService({
runtimeFactory: () => runtime,
healthProbeRetryDelaysMs: [],
});
const context = createServiceContext();
await service.start(context);
await vi.waitFor(() => {
expect(probeAvailabilitySpy).toHaveBeenCalledOnce();
expect(doctorSpy).toHaveBeenCalledOnce();
expect(context.logger.warn).toHaveBeenCalledWith(
"acpx runtime backend probe failed: acpx help check passed",
);
});
expect(context.logger.info).not.toHaveBeenCalledWith("acpx runtime backend ready");
expect(() => requireAcpRuntimeBackend("acpx")).toThrowError(AcpRuntimeError);
});
});
+67 -5
View File
@@ -1,3 +1,4 @@
import fs from "node:fs/promises";
import type {
AcpRuntime,
OpenClawPluginService,
@@ -12,6 +13,11 @@ import { ACPX_BACKEND_ID, AcpxRuntime } from "./runtime.js";
type AcpxRuntimeLike = AcpRuntime & {
probeAvailability(): Promise<void>;
isHealthy(): boolean;
doctor?(): Promise<{
ok: boolean;
message: string;
details?: string[];
}>;
};
type AcpxRuntimeFactoryParams = {
@@ -23,8 +29,25 @@ type AcpxRuntimeFactoryParams = {
type CreateAcpxRuntimeServiceParams = {
pluginConfig?: unknown;
runtimeFactory?: (params: AcpxRuntimeFactoryParams) => AcpxRuntimeLike;
healthProbeRetryDelaysMs?: number[];
};
const DEFAULT_HEALTH_PROBE_RETRY_DELAYS_MS = [250, 1_000, 2_500];
function delay(ms: number): Promise<void> {
if (ms <= 0) {
return Promise.resolve();
}
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
function formatDoctorFailureMessage(report: { message: string; details?: string[] }): string {
const detailText = report.details?.filter(Boolean).join("; ").trim();
return detailText ? `${report.message} (${detailText})` : report.message;
}
function createDefaultRuntime(params: AcpxRuntimeFactoryParams): AcpxRuntimeLike {
return new AcpxRuntime(params.pluginConfig, {
logger: params.logger,
@@ -45,6 +68,11 @@ export function createAcpxRuntimeService(
rawConfig: params.pluginConfig,
workspaceDir: ctx.workspaceDir,
});
if (ctx.workspaceDir?.trim()) {
await fs.mkdir(ctx.workspaceDir, { recursive: true });
}
const healthProbeRetryDelaysMs =
params.healthProbeRetryDelaysMs ?? DEFAULT_HEALTH_PROBE_RETRY_DELAYS_MS;
const runtimeFactory = params.runtimeFactory ?? createDefaultRuntime;
runtime = runtimeFactory({
pluginConfig,
@@ -80,12 +108,46 @@ export function createAcpxRuntimeService(
if (currentRevision !== lifecycleRevision) {
return;
}
await runtime?.probeAvailability();
if (runtime?.isHealthy()) {
ctx.logger.info("acpx runtime backend ready");
} else {
ctx.logger.warn("acpx runtime backend probe failed after local install");
let lastFailureMessage: string | undefined;
for (let attempt = 0; attempt <= healthProbeRetryDelaysMs.length; attempt += 1) {
await runtime?.probeAvailability();
if (currentRevision !== lifecycleRevision) {
return;
}
if (runtime?.isHealthy()) {
ctx.logger.info(
attempt === 0
? "acpx runtime backend ready"
: `acpx runtime backend ready after ${attempt + 1} probe attempts`,
);
return;
}
const doctorReport = await runtime?.doctor?.();
if (currentRevision !== lifecycleRevision) {
return;
}
if (doctorReport) {
lastFailureMessage = formatDoctorFailureMessage(doctorReport);
} else {
lastFailureMessage = "acpx runtime backend remained unhealthy after probe";
}
const retryDelayMs = healthProbeRetryDelaysMs[attempt];
if (retryDelayMs == null) {
break;
}
ctx.logger.warn(
`acpx runtime backend probe attempt ${attempt + 1} failed: ${lastFailureMessage}; retrying in ${retryDelayMs}ms`,
);
await delay(retryDelayMs);
if (currentRevision !== lifecycleRevision) {
return;
}
}
ctx.logger.warn(
`acpx runtime backend probe failed: ${lastFailureMessage ?? "backend remained unhealthy after setup"}`,
);
} catch (err) {
if (currentRevision !== lifecycleRevision) {
return;
@@ -84,13 +84,16 @@ const setValue = command === "set" ? String(args[commandIndex + 2] || "") : "";
if (command === "sessions" && args[commandIndex + 1] === "ensure") {
writeLog({ kind: "ensure", agent, args, sessionName: ensureName });
if (process.env.MOCK_ACPX_ENSURE_STDERR) {
process.stderr.write(String(process.env.MOCK_ACPX_ENSURE_STDERR) + "\n");
}
if (process.env.MOCK_ACPX_ENSURE_EXIT_1 === "1") {
return emitJsonAndExit({
jsonrpc: "2.0",
id: null,
error: {
code: -32603,
message: "mock ensure failure",
message: process.env.MOCK_ACPX_ENSURE_ERROR_MESSAGE || "mock ensure failure",
},
}, 1);
}
@@ -112,6 +115,19 @@ if (command === "sessions" && args[commandIndex + 1] === "ensure") {
if (command === "sessions" && args[commandIndex + 1] === "new") {
writeLog({ kind: "new", agent, args, sessionName: ensureName });
if (process.env.MOCK_ACPX_NEW_FAIL_ON_RESUME === "1" && args.includes("--resume-session")) {
return emitJsonAndExit(
{
jsonrpc: "2.0",
id: null,
error: {
code: -32603,
message: "mock stale resume session",
},
},
1,
);
}
if (process.env.MOCK_ACPX_NEW_EMPTY === "1") {
emitJson({ action: "session_created", name: ensureName });
} else {
@@ -198,10 +214,11 @@ if (command === "status") {
}
const status = process.env.MOCK_ACPX_STATUS_STATUS || (sessionFromOption ? "alive" : "no-session");
const summary = process.env.MOCK_ACPX_STATUS_SUMMARY || "";
const omitStatusIds = process.env.MOCK_ACPX_STATUS_NO_IDS === "1";
emitJson({
acpxRecordId: sessionFromOption ? "rec-" + sessionFromOption : null,
acpxSessionId: sessionFromOption ? "sid-" + sessionFromOption : null,
agentSessionId: sessionFromOption ? "inner-" + sessionFromOption : null,
acpxRecordId: sessionFromOption && !omitStatusIds ? "rec-" + sessionFromOption : null,
acpxSessionId: sessionFromOption && !omitStatusIds ? "sid-" + sessionFromOption : null,
agentSessionId: sessionFromOption && !omitStatusIds ? "inner-" + sessionFromOption : null,
status,
...(summary ? { summary } : {}),
pid: 4242,
@@ -419,8 +436,12 @@ export async function readMockRuntimeLogEntries(
export async function cleanupMockRuntimeFixtures(): Promise<void> {
delete process.env.MOCK_ACPX_LOG;
delete process.env.MOCK_ACPX_CONFIG_SHOW_AGENTS;
delete process.env.MOCK_ACPX_ENSURE_ERROR_MESSAGE;
delete process.env.MOCK_ACPX_ENSURE_EXIT_1;
delete process.env.MOCK_ACPX_ENSURE_STDERR;
delete process.env.MOCK_ACPX_NEW_FAIL_ON_RESUME;
delete process.env.MOCK_ACPX_STATUS_STATUS;
delete process.env.MOCK_ACPX_STATUS_NO_IDS;
delete process.env.MOCK_ACPX_STATUS_SUMMARY;
sharedMockCliScriptPath = null;
logFileSequence = 0;
@@ -1,5 +1,11 @@
import type { BedrockClient } from "@aws-sdk/client-bedrock";
import { beforeEach, describe, expect, it, vi } from "vitest";
import {
discoverBedrockModels,
mergeImplicitBedrockProvider,
resetBedrockDiscoveryCacheForTest,
resolveBedrockConfigApiKey,
} from "./api.js";
const sendMock = vi.fn();
const clientFactory = () => ({ send: sendMock }) as unknown as BedrockClient;
@@ -14,12 +20,6 @@ const baseActiveAnthropicSummary = {
modelLifecycle: { status: "ACTIVE" },
};
async function loadDiscovery() {
const mod = await import("../plugin-sdk/amazon-bedrock.js");
mod.resetBedrockDiscoveryCacheForTest();
return mod;
}
function mockSingleActiveSummary(overrides: Partial<typeof baseActiveAnthropicSummary> = {}): void {
sendMock.mockResolvedValueOnce({
modelSummaries: [{ ...baseActiveAnthropicSummary, ...overrides }],
@@ -29,11 +29,10 @@ function mockSingleActiveSummary(overrides: Partial<typeof baseActiveAnthropicSu
describe("bedrock discovery", () => {
beforeEach(() => {
sendMock.mockClear();
resetBedrockDiscoveryCacheForTest();
});
it("filters to active streaming text models and maps modalities", async () => {
const { discoverBedrockModels } = await loadDiscovery();
sendMock.mockResolvedValueOnce({
modelSummaries: [
{
@@ -88,7 +87,6 @@ describe("bedrock discovery", () => {
});
it("applies provider filter", async () => {
const { discoverBedrockModels } = await loadDiscovery();
mockSingleActiveSummary();
const models = await discoverBedrockModels({
@@ -100,7 +98,6 @@ describe("bedrock discovery", () => {
});
it("uses configured defaults for context and max tokens", async () => {
const { discoverBedrockModels } = await loadDiscovery();
mockSingleActiveSummary();
const models = await discoverBedrockModels({
@@ -112,7 +109,6 @@ describe("bedrock discovery", () => {
});
it("caches results when refreshInterval is enabled", async () => {
const { discoverBedrockModels } = await loadDiscovery();
mockSingleActiveSummary();
await discoverBedrockModels({ region: "us-east-1", clientFactory });
@@ -121,8 +117,6 @@ describe("bedrock discovery", () => {
});
it("skips cache when refreshInterval is 0", async () => {
const { discoverBedrockModels } = await loadDiscovery();
sendMock
.mockResolvedValueOnce({ modelSummaries: [baseActiveAnthropicSummary] })
.mockResolvedValueOnce({ modelSummaries: [baseActiveAnthropicSummary] });
@@ -140,9 +134,7 @@ describe("bedrock discovery", () => {
expect(sendMock).toHaveBeenCalledTimes(2);
});
it("resolves the Bedrock config apiKey from AWS auth env vars", async () => {
const { resolveBedrockConfigApiKey } = await loadDiscovery();
it("resolves the Bedrock config apiKey from AWS auth env vars", () => {
expect(
resolveBedrockConfigApiKey({
AWS_BEARER_TOKEN_BEDROCK: "bearer", // pragma: allowlist secret
@@ -153,9 +145,7 @@ describe("bedrock discovery", () => {
expect(resolveBedrockConfigApiKey({} as NodeJS.ProcessEnv)).toBe("AWS_PROFILE");
});
it("merges implicit Bedrock models into explicit provider overrides", async () => {
const { mergeImplicitBedrockProvider } = await loadDiscovery();
it("merges implicit Bedrock models into explicit provider overrides", () => {
expect(
mergeImplicitBedrockProvider({
existing: {
@@ -182,89 +172,4 @@ describe("bedrock discovery", () => {
}).models?.map((model) => model.id),
).toEqual(["amazon.nova-micro-v1:0"]);
});
it("merges implicit Bedrock discovery into provider catalog config", async () => {
vi.resetModules();
const bedrockApi = await import("../plugin-sdk/amazon-bedrock.js");
vi.spyOn(bedrockApi, "resolveImplicitBedrockProvider").mockResolvedValue({
baseUrl: "https://bedrock-runtime.us-east-1.amazonaws.com",
api: "bedrock-converse-stream",
auth: "aws-sdk",
models: [
{
id: "amazon.nova-micro-v1:0",
name: "Nova Micro",
reasoning: false,
input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 32_000,
maxTokens: 4096,
},
],
});
vi.spyOn(bedrockApi, "mergeImplicitBedrockProvider").mockImplementation(
({ existing, implicit }) => ({
...implicit,
...existing,
models:
Array.isArray(existing?.models) && existing.models.length > 0
? existing.models
: implicit.models,
}),
);
const result = await (async () => {
const implicit = await bedrockApi.resolveImplicitBedrockProvider({
config: {
models: {
bedrockDiscovery: {
enabled: true,
},
},
},
env: {
AWS_PROFILE: "default",
} as NodeJS.ProcessEnv,
});
if (!implicit) {
return null;
}
return {
provider: bedrockApi.mergeImplicitBedrockProvider({
existing: {
baseUrl: "https://bedrock-runtime.us-west-2.amazonaws.com",
headers: { "x-test-header": "1" },
models: [],
},
implicit,
}),
};
})();
expect(bedrockApi.resolveImplicitBedrockProvider).toHaveBeenCalledWith({
config: {
models: {
bedrockDiscovery: {
enabled: true,
},
},
},
env: {
AWS_PROFILE: "default",
} as NodeJS.ProcessEnv,
});
expect(result).toMatchObject({
provider: {
api: "bedrock-converse-stream",
auth: "aws-sdk",
baseUrl: "https://bedrock-runtime.us-west-2.amazonaws.com",
headers: { "x-test-header": "1" },
},
});
expect(
result && "provider" in result
? result.provider.models?.map((model: { id: string }) => model.id)
: [],
).toEqual(["amazon.nova-micro-v1:0"]);
});
});
+208
View File
@@ -1,7 +1,92 @@
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
import { describe, expect, it } from "vitest";
import type { OpenClawConfig } from "../../src/config/config.js";
import { buildPluginApi } from "../../src/plugins/api-builder.js";
import type { PluginRuntime } from "../../src/plugins/runtime/types.js";
import { registerSingleProviderPlugin } from "../../test/helpers/plugins/plugin-registration.js";
import amazonBedrockPlugin from "./index.js";
type RegisteredProviderPlugin = ReturnType<typeof registerSingleProviderPlugin>;
/** Register the amazon-bedrock plugin with an optional pluginConfig override. */
function registerWithConfig(pluginConfig?: Record<string, unknown>): RegisteredProviderPlugin {
const providers: RegisteredProviderPlugin[] = [];
const noopLogger = { info() {}, warn() {}, error() {}, debug() {} };
const api = buildPluginApi({
id: "amazon-bedrock",
name: "Amazon Bedrock Provider",
source: "test",
registrationMode: "full",
config: {} as OpenClawConfig,
pluginConfig,
runtime: {} as PluginRuntime,
logger: noopLogger,
resolvePath: (input) => input,
handlers: {
registerProvider(provider: RegisteredProviderPlugin) {
providers.push(provider);
},
},
});
amazonBedrockPlugin.register(api);
const provider = providers[0];
if (!provider) throw new Error("provider registration missing");
return provider;
}
/** Spy streamFn that returns the options it receives. */
const spyStreamFn = (_model: unknown, _context: unknown, options: Record<string, unknown>) =>
options;
const ANTHROPIC_MODEL = "us.anthropic.claude-sonnet-4-6-v1";
const NON_ANTHROPIC_MODEL = "amazon.nova-micro-v1:0";
const MODEL_DESCRIPTOR = {
api: "openai-completions",
provider: "amazon-bedrock",
id: NON_ANTHROPIC_MODEL,
} as never;
const ANTHROPIC_MODEL_DESCRIPTOR = {
api: "openai-completions",
provider: "amazon-bedrock",
id: ANTHROPIC_MODEL,
} as never;
/**
* Call wrapStreamFn and then invoke the returned stream function, capturing
* the payload via the onPayload hook that streamWithPayloadPatch installs.
*/
function callWrappedStream(
provider: RegisteredProviderPlugin,
modelId: string,
modelDescriptor: never,
): Record<string, unknown> {
const wrapped = provider.wrapStreamFn?.({
provider: "amazon-bedrock",
modelId,
streamFn: spyStreamFn,
} as never);
// The wrapped stream returns the options object (from spyStreamFn).
// For guardrail-wrapped streams, streamWithPayloadPatch intercepts onPayload,
// so we need to invoke onPayload on the returned options to trigger the patch.
const result = wrapped?.(modelDescriptor, { messages: [] } as never, {}) as unknown as Record<
string,
unknown
>;
// If onPayload was installed by streamWithPayloadPatch, call it to apply the patch.
if (typeof result?.onPayload === "function") {
const payload: Record<string, unknown> = {};
(result.onPayload as (p: Record<string, unknown>) => void)(payload);
return { ...result, _capturedPayload: payload };
}
return result;
}
describe("amazon-bedrock provider plugin", () => {
it("marks Claude 4.6 Bedrock models as adaptive by default", () => {
const provider = registerSingleProviderPlugin(amazonBedrockPlugin);
@@ -42,4 +127,127 @@ describe("amazon-bedrock provider plugin", () => {
cacheRetention: "none",
});
});
describe("guardrail config schema", () => {
it("defines guardrail object with correct property types, required fields, and enums", () => {
const pluginJson = JSON.parse(
readFileSync(resolve(import.meta.dirname, "openclaw.plugin.json"), "utf-8"),
);
const guardrail = pluginJson.configSchema?.properties?.guardrail;
expect(guardrail).toBeDefined();
expect(guardrail.type).toBe("object");
expect(guardrail.additionalProperties).toBe(false);
// Required fields
expect(guardrail.required).toEqual(["guardrailIdentifier", "guardrailVersion"]);
// Property types
expect(guardrail.properties.guardrailIdentifier).toEqual({ type: "string" });
expect(guardrail.properties.guardrailVersion).toEqual({ type: "string" });
// Enum constraints
expect(guardrail.properties.streamProcessingMode).toEqual({
type: "string",
enum: ["sync", "async"],
});
expect(guardrail.properties.trace).toEqual({
type: "string",
enum: ["enabled", "disabled", "enabled_full"],
});
});
});
describe("guardrail payload injection", () => {
it("does not inject guardrailConfig when guardrail is absent from plugin config", () => {
const provider = registerWithConfig(undefined);
const result = callWrappedStream(provider, NON_ANTHROPIC_MODEL, MODEL_DESCRIPTOR);
expect(result).not.toHaveProperty("_capturedPayload");
// The onPayload hook should not exist when no guardrail is configured
expect(result).toMatchObject({ cacheRetention: "none" });
});
it("injects all four fields when guardrail config includes optional fields", () => {
const provider = registerWithConfig({
guardrail: {
guardrailIdentifier: "my-guardrail-id",
guardrailVersion: "1",
streamProcessingMode: "sync",
trace: "enabled",
},
});
const result = callWrappedStream(provider, NON_ANTHROPIC_MODEL, MODEL_DESCRIPTOR);
expect(result._capturedPayload).toEqual({
guardrailConfig: {
guardrailIdentifier: "my-guardrail-id",
guardrailVersion: "1",
streamProcessingMode: "sync",
trace: "enabled",
},
});
});
it("injects only required fields when optional fields are omitted", () => {
const provider = registerWithConfig({
guardrail: {
guardrailIdentifier: "abc123",
guardrailVersion: "DRAFT",
},
});
const result = callWrappedStream(provider, NON_ANTHROPIC_MODEL, MODEL_DESCRIPTOR);
expect(result._capturedPayload).toEqual({
guardrailConfig: {
guardrailIdentifier: "abc123",
guardrailVersion: "DRAFT",
},
});
});
it("injects guardrailConfig for Anthropic models without cacheRetention: none", () => {
const provider = registerWithConfig({
guardrail: {
guardrailIdentifier: "guardrail-anthropic",
guardrailVersion: "2",
streamProcessingMode: "async",
trace: "disabled",
},
});
const result = callWrappedStream(provider, ANTHROPIC_MODEL, ANTHROPIC_MODEL_DESCRIPTOR);
// Anthropic models should get guardrailConfig
expect(result._capturedPayload).toEqual({
guardrailConfig: {
guardrailIdentifier: "guardrail-anthropic",
guardrailVersion: "2",
streamProcessingMode: "async",
trace: "disabled",
},
});
// Anthropic models should NOT get cacheRetention: "none"
expect(result).not.toHaveProperty("cacheRetention", "none");
});
it("injects guardrailConfig for non-Anthropic models with cacheRetention: none", () => {
const provider = registerWithConfig({
guardrail: {
guardrailIdentifier: "guardrail-nova",
guardrailVersion: "3",
},
});
const result = callWrappedStream(provider, NON_ANTHROPIC_MODEL, MODEL_DESCRIPTOR);
// Non-Anthropic models should get guardrailConfig
expect(result._capturedPayload).toEqual({
guardrailConfig: {
guardrailIdentifier: "guardrail-nova",
guardrailVersion: "3",
},
});
// Non-Anthropic models should also get cacheRetention: "none"
expect(result).toMatchObject({ cacheRetention: "none" });
});
});
});
+47 -2
View File
@@ -1,7 +1,9 @@
import type { StreamFn } from "@mariozechner/pi-agent-core";
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
import {
createBedrockNoCacheWrapper,
isAnthropicBedrockModel,
streamWithPayloadPatch,
} from "openclaw/plugin-sdk/provider-stream";
import {
mergeImplicitBedrockProvider,
@@ -9,6 +11,38 @@ import {
resolveImplicitBedrockProvider,
} from "./api.js";
type GuardrailConfig = {
guardrailIdentifier: string;
guardrailVersion: string;
streamProcessingMode?: "sync" | "async";
trace?: "enabled" | "disabled" | "enabled_full";
};
function createGuardrailWrapStreamFn(
innerWrapStreamFn: (ctx: { modelId: string; streamFn?: StreamFn }) => StreamFn | null | undefined,
guardrailConfig: GuardrailConfig,
): (ctx: { modelId: string; streamFn?: StreamFn }) => StreamFn | null | undefined {
return (ctx) => {
const inner = innerWrapStreamFn(ctx);
if (!inner) return inner;
return (model, context, options) => {
return streamWithPayloadPatch(inner, model, context, options, (payload) => {
const gc: Record<string, unknown> = {
guardrailIdentifier: guardrailConfig.guardrailIdentifier,
guardrailVersion: guardrailConfig.guardrailVersion,
};
if (guardrailConfig.streamProcessingMode) {
gc.streamProcessingMode = guardrailConfig.streamProcessingMode;
}
if (guardrailConfig.trace) {
gc.trace = guardrailConfig.trace;
}
payload.guardrailConfig = gc;
});
};
};
}
const PROVIDER_ID = "amazon-bedrock";
const CLAUDE_46_MODEL_RE = /claude-(?:opus|sonnet)-4(?:\.|-)6(?:$|[-.])/i;
@@ -17,6 +51,18 @@ export default definePluginEntry({
name: "Amazon Bedrock Provider",
description: "Bundled Amazon Bedrock provider policy plugin",
register(api) {
const guardrail = (api.pluginConfig as Record<string, unknown> | undefined)?.guardrail as
| GuardrailConfig
| undefined;
const baseWrapStreamFn = ({ modelId, streamFn }: { modelId: string; streamFn?: StreamFn }) =>
isAnthropicBedrockModel(modelId) ? streamFn : createBedrockNoCacheWrapper(streamFn);
const wrapStreamFn =
guardrail?.guardrailIdentifier && guardrail?.guardrailVersion
? createGuardrailWrapStreamFn(baseWrapStreamFn, guardrail)
: baseWrapStreamFn;
api.registerProvider({
id: PROVIDER_ID,
label: "Amazon Bedrock",
@@ -45,8 +91,7 @@ export default definePluginEntry({
providerFamily: "anthropic",
dropThinkingBlockModelHints: ["claude"],
},
wrapStreamFn: ({ modelId, streamFn }) =>
isAnthropicBedrockModel(modelId) ? streamFn : createBedrockNoCacheWrapper(streamFn),
wrapStreamFn,
resolveDefaultThinkingLevel: ({ modelId }) =>
CLAUDE_46_MODEL_RE.test(modelId.trim()) ? "adaptive" : undefined,
});
+13 -1
View File
@@ -5,6 +5,18 @@
"configSchema": {
"type": "object",
"additionalProperties": false,
"properties": {}
"properties": {
"guardrail": {
"type": "object",
"additionalProperties": false,
"properties": {
"guardrailIdentifier": { "type": "string" },
"guardrailVersion": { "type": "string" },
"streamProcessingMode": { "type": "string", "enum": ["sync", "async"] },
"trace": { "type": "string", "enum": ["enabled", "disabled", "enabled_full"] }
},
"required": ["guardrailIdentifier", "guardrailVersion"]
}
}
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@openclaw/amazon-bedrock-provider",
"version": "2026.3.31-beta.1",
"version": "2026.4.1-beta.1",
"private": true,
"description": "OpenClaw Amazon Bedrock provider plugin",
"type": "module",
@@ -1,3 +0,0 @@
import { describeProviderContracts } from "../../test/helpers/plugins/provider-contract.js";
describeProviderContracts("amazon-bedrock");
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@openclaw/anthropic-vertex-provider",
"version": "2026.3.31-beta.1",
"version": "2026.4.1-beta.1",
"private": true,
"description": "OpenClaw Anthropic Vertex provider plugin",
"type": "module",
@@ -1,3 +0,0 @@
import { describeProviderContracts } from "../../test/helpers/plugins/provider-contract.js";
describeProviderContracts("anthropic-vertex");
@@ -0,0 +1,32 @@
import { describe, expect, it } from "vitest";
import { resolveAnthropicVertexRegion, resolveAnthropicVertexRegionFromBaseUrl } from "./api.js";
describe("anthropic vertex region helpers", () => {
it("accepts well-formed regional env values", () => {
expect(
resolveAnthropicVertexRegion({
GOOGLE_CLOUD_LOCATION: "us-east1",
} as NodeJS.ProcessEnv),
).toBe("us-east1");
});
it("falls back to the default region for malformed env values", () => {
expect(
resolveAnthropicVertexRegion({
GOOGLE_CLOUD_LOCATION: "us-central1.attacker.example",
} as NodeJS.ProcessEnv),
).toBe("global");
});
it("parses regional Vertex endpoints", () => {
expect(
resolveAnthropicVertexRegionFromBaseUrl("https://europe-west4-aiplatform.googleapis.com"),
).toBe("europe-west4");
});
it("treats the global Vertex endpoint as global", () => {
expect(resolveAnthropicVertexRegionFromBaseUrl("https://aiplatform.googleapis.com")).toBe(
"global",
);
});
});
+8
View File
@@ -1 +1,9 @@
export { CLAUDE_CLI_BACKEND_ID, isClaudeCliProvider } from "./cli-shared.js";
export {
createAnthropicBetaHeadersWrapper,
createAnthropicFastModeWrapper,
createAnthropicServiceTierWrapper,
resolveAnthropicBetas,
resolveAnthropicFastMode,
resolveAnthropicServiceTier,
} from "./stream-wrappers.js";
+24
View File
@@ -30,6 +30,14 @@ import { fetchClaudeUsage } from "openclaw/plugin-sdk/provider-usage";
import { buildAnthropicCliBackend } from "./cli-backend.js";
import { buildAnthropicCliMigrationResult, hasClaudeCliAuth } from "./cli-migration.js";
import { anthropicMediaUnderstandingProvider } from "./media-understanding-provider.js";
import {
createAnthropicBetaHeadersWrapper,
createAnthropicFastModeWrapper,
createAnthropicServiceTierWrapper,
resolveAnthropicBetas,
resolveAnthropicFastMode,
resolveAnthropicServiceTier,
} from "./stream-wrappers.js";
const PROVIDER_ID = "anthropic";
const DEFAULT_ANTHROPIC_MODEL = "anthropic/claude-sonnet-4-6";
@@ -443,6 +451,22 @@ export default definePluginEntry({
dropThinkingBlockModelHints: ["claude"],
},
isModernModelRef: ({ modelId }) => matchesAnthropicModernModel(modelId),
wrapStreamFn: (ctx) => {
let streamFn = ctx.streamFn;
const anthropicBetas = resolveAnthropicBetas(ctx.extraParams, ctx.modelId);
if (anthropicBetas?.length) {
streamFn = createAnthropicBetaHeadersWrapper(streamFn, anthropicBetas);
}
const serviceTier = resolveAnthropicServiceTier(ctx.extraParams);
if (serviceTier) {
streamFn = createAnthropicServiceTierWrapper(streamFn, serviceTier);
}
const fastMode = resolveAnthropicFastMode(ctx.extraParams);
if (fastMode !== undefined) {
streamFn = createAnthropicFastModeWrapper(streamFn, fastMode);
}
return streamFn;
},
resolveDefaultThinkingLevel: ({ modelId }) =>
matchesAnthropicModernModel(modelId) &&
(modelId.toLowerCase().startsWith(ANTHROPIC_OPUS_46_MODEL_ID) ||
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@openclaw/anthropic-provider",
"version": "2026.3.31-beta.1",
"version": "2026.4.1-beta.1",
"private": true,
"description": "OpenClaw Anthropic provider plugin",
"type": "module",
@@ -1,9 +0,0 @@
import { describePluginRegistrationContract } from "../../test/helpers/plugins/plugin-registration-contract.js";
describePluginRegistrationContract({
pluginId: "anthropic",
providerIds: ["anthropic"],
mediaUnderstandingProviderIds: ["anthropic"],
cliBackendIds: ["claude-cli"],
requireDescribeImages: true,
});
@@ -1,3 +0,0 @@
import { describeAnthropicProviderRuntimeContract } from "../../test/helpers/plugins/provider-runtime-contract.js";
describeAnthropicProviderRuntimeContract();
@@ -1,3 +0,0 @@
import { describeProviderContracts } from "../../test/helpers/plugins/provider-contract.js";
describeProviderContracts("anthropic");
@@ -0,0 +1,44 @@
import type { StreamFn } from "@mariozechner/pi-agent-core";
import { afterEach, describe, expect, it, vi } from "vitest";
import { __testing, createAnthropicBetaHeadersWrapper } from "./stream-wrappers.js";
const CONTEXT_1M_BETA = "context-1m-2025-08-07";
const OAUTH_BETA = "oauth-2025-04-20";
function runWrapper(apiKey: string | undefined): Record<string, string> | undefined {
const captured: { headers?: Record<string, string> } = {};
const base: StreamFn = (_model, _context, options) => {
captured.headers = options?.headers;
return {} as never;
};
const wrapper = createAnthropicBetaHeadersWrapper(base, [CONTEXT_1M_BETA]);
wrapper(
{ provider: "anthropic", id: "claude-opus-4-6" } as never,
{} as never,
{ apiKey } as never,
);
return captured.headers;
}
describe("anthropic stream wrappers", () => {
afterEach(() => {
vi.restoreAllMocks();
});
it("strips context-1m for subscription setup-token auth and warns", () => {
const warn = vi.spyOn(__testing.log, "warn").mockImplementation(() => undefined);
const headers = runWrapper("sk-ant-oat01-123");
expect(headers?.["anthropic-beta"]).toBeDefined();
expect(headers?.["anthropic-beta"]).toContain(OAUTH_BETA);
expect(headers?.["anthropic-beta"]).not.toContain(CONTEXT_1M_BETA);
expect(warn).toHaveBeenCalledOnce();
});
it("keeps context-1m for API key auth", () => {
const warn = vi.spyOn(__testing.log, "warn").mockImplementation(() => undefined);
const headers = runWrapper("sk-ant-api-123");
expect(headers?.["anthropic-beta"]).toBeDefined();
expect(headers?.["anthropic-beta"]).toContain(CONTEXT_1M_BETA);
expect(warn).not.toHaveBeenCalled();
});
});
+222
View File
@@ -0,0 +1,222 @@
import type { StreamFn } from "@mariozechner/pi-agent-core";
import { streamSimple } from "@mariozechner/pi-ai";
import { streamWithPayloadPatch } from "openclaw/plugin-sdk/provider-stream";
import { createSubsystemLogger } from "openclaw/plugin-sdk/runtime-env";
const log = createSubsystemLogger("anthropic-stream");
const ANTHROPIC_CONTEXT_1M_BETA = "context-1m-2025-08-07";
const ANTHROPIC_1M_MODEL_PREFIXES = ["claude-opus-4", "claude-sonnet-4"] as const;
const PI_AI_DEFAULT_ANTHROPIC_BETAS = [
"fine-grained-tool-streaming-2025-05-14",
"interleaved-thinking-2025-05-14",
] as const;
const PI_AI_OAUTH_ANTHROPIC_BETAS = [
"claude-code-20250219",
"oauth-2025-04-20",
...PI_AI_DEFAULT_ANTHROPIC_BETAS,
] as const;
type AnthropicServiceTier = "auto" | "standard_only";
function isAnthropic1MModel(modelId: string): boolean {
const normalized = modelId.trim().toLowerCase();
return ANTHROPIC_1M_MODEL_PREFIXES.some((prefix) => normalized.startsWith(prefix));
}
function parseHeaderList(value: unknown): string[] {
if (typeof value !== "string") {
return [];
}
return value
.split(",")
.map((item) => item.trim())
.filter(Boolean);
}
function mergeAnthropicBetaHeader(
headers: Record<string, string> | undefined,
betas: string[],
): Record<string, string> {
const merged = { ...headers };
const existingKey = Object.keys(merged).find((key) => key.toLowerCase() === "anthropic-beta");
const existing = existingKey ? parseHeaderList(merged[existingKey]) : [];
const values = Array.from(new Set([...existing, ...betas]));
const key = existingKey ?? "anthropic-beta";
merged[key] = values.join(",");
return merged;
}
function isAnthropicOAuthApiKey(apiKey: unknown): boolean {
return typeof apiKey === "string" && apiKey.includes("sk-ant-oat");
}
function isAnthropicPublicApiBaseUrl(baseUrl: unknown): boolean {
if (baseUrl == null) {
return true;
}
if (typeof baseUrl !== "string" || !baseUrl.trim()) {
return true;
}
try {
return new URL(baseUrl).hostname.toLowerCase() === "api.anthropic.com";
} catch {
return baseUrl.toLowerCase().includes("api.anthropic.com");
}
}
function resolveAnthropicFastServiceTier(enabled: boolean): AnthropicServiceTier {
return enabled ? "auto" : "standard_only";
}
function normalizeFastMode(raw?: string | boolean | null): boolean | undefined {
if (typeof raw === "boolean") {
return raw;
}
if (!raw) {
return undefined;
}
const key = raw.toLowerCase();
if (["off", "false", "no", "0", "disable", "disabled", "normal"].includes(key)) {
return false;
}
if (["on", "true", "yes", "1", "enable", "enabled", "fast"].includes(key)) {
return true;
}
return undefined;
}
function normalizeAnthropicServiceTier(value: unknown): AnthropicServiceTier | undefined {
if (typeof value !== "string") {
return undefined;
}
const normalized = value.trim().toLowerCase();
if (normalized === "auto" || normalized === "standard_only") {
return normalized;
}
return undefined;
}
export function resolveAnthropicBetas(
extraParams: Record<string, unknown> | undefined,
modelId: string,
): string[] | undefined {
const betas = new Set<string>();
const configured = extraParams?.anthropicBeta;
if (typeof configured === "string" && configured.trim()) {
betas.add(configured.trim());
} else if (Array.isArray(configured)) {
for (const beta of configured) {
if (typeof beta === "string" && beta.trim()) {
betas.add(beta.trim());
}
}
}
if (extraParams?.context1m === true) {
if (isAnthropic1MModel(modelId)) {
betas.add(ANTHROPIC_CONTEXT_1M_BETA);
} else {
log.warn(`ignoring context1m for non-opus/sonnet model: anthropic/${modelId}`);
}
}
return betas.size > 0 ? [...betas] : undefined;
}
export function createAnthropicBetaHeadersWrapper(
baseStreamFn: StreamFn | undefined,
betas: string[],
): StreamFn {
const underlying = baseStreamFn ?? streamSimple;
return (model, context, options) => {
const isOauth = isAnthropicOAuthApiKey(options?.apiKey);
const requestedContext1m = betas.includes(ANTHROPIC_CONTEXT_1M_BETA);
const effectiveBetas =
isOauth && requestedContext1m
? betas.filter((beta) => beta !== ANTHROPIC_CONTEXT_1M_BETA)
: betas;
if (isOauth && requestedContext1m) {
log.warn(
`ignoring context1m for Anthropic subscription (OAuth setup-token) auth on ${model.provider}/${model.id}; falling back to the standard context window because Anthropic rejects context-1m beta with OAuth auth`,
);
}
const piAiBetas = isOauth
? (PI_AI_OAUTH_ANTHROPIC_BETAS as readonly string[])
: (PI_AI_DEFAULT_ANTHROPIC_BETAS as readonly string[]);
const allBetas = [...new Set([...piAiBetas, ...effectiveBetas])];
return underlying(model, context, {
...options,
headers: mergeAnthropicBetaHeader(options?.headers, allBetas),
});
};
}
export function createAnthropicFastModeWrapper(
baseStreamFn: StreamFn | undefined,
enabled: boolean,
): StreamFn {
const underlying = baseStreamFn ?? streamSimple;
const serviceTier = resolveAnthropicFastServiceTier(enabled);
return (model, context, options) => {
if (
model.api !== "anthropic-messages" ||
model.provider !== "anthropic" ||
!isAnthropicPublicApiBaseUrl(model.baseUrl)
) {
return underlying(model, context, options);
}
return streamWithPayloadPatch(underlying, model, context, options, (payloadObj) => {
if (payloadObj.service_tier === undefined) {
payloadObj.service_tier = serviceTier;
}
});
};
}
export function createAnthropicServiceTierWrapper(
baseStreamFn: StreamFn | undefined,
serviceTier: AnthropicServiceTier,
): StreamFn {
const underlying = baseStreamFn ?? streamSimple;
return (model, context, options) => {
if (
model.api !== "anthropic-messages" ||
model.provider !== "anthropic" ||
!isAnthropicPublicApiBaseUrl(model.baseUrl)
) {
return underlying(model, context, options);
}
return streamWithPayloadPatch(underlying, model, context, options, (payloadObj) => {
if (payloadObj.service_tier === undefined) {
payloadObj.service_tier = serviceTier;
}
});
};
}
export function resolveAnthropicFastMode(
extraParams: Record<string, unknown> | undefined,
): boolean | undefined {
return normalizeFastMode(
(extraParams?.fastMode ?? extraParams?.fast_mode) as string | boolean | null | undefined,
);
}
export function resolveAnthropicServiceTier(
extraParams: Record<string, unknown> | undefined,
): AnthropicServiceTier | undefined {
const raw = extraParams?.serviceTier ?? extraParams?.service_tier;
const normalized = normalizeAnthropicServiceTier(raw);
if (raw !== undefined && normalized === undefined) {
const rawSummary = typeof raw === "string" ? raw : typeof raw;
log.warn(`ignoring invalid Anthropic service tier param: ${rawSummary}`);
}
return normalized;
}
export const __testing = { log };
@@ -1,6 +0,0 @@
import { describePackageManifestContract } from "../../test/helpers/plugins/package-manifest-contract.js";
describePackageManifestContract({
pluginId: "bluebubbles",
minHostVersionBaseline: "2026.3.22",
});
+3 -3
View File
@@ -1,13 +1,13 @@
{
"name": "@openclaw/bluebubbles",
"version": "2026.3.31-beta.1",
"version": "2026.4.1-beta.1",
"description": "OpenClaw BlueBubbles channel plugin",
"type": "module",
"devDependencies": {
"openclaw": "workspace:*"
},
"peerDependencies": {
"openclaw": ">=2026.3.31-beta.1"
"openclaw": ">=2026.4.1-beta.1"
},
"peerDependenciesMeta": {
"openclaw": {
@@ -39,7 +39,7 @@
"install": {
"npmSpec": "@openclaw/bluebubbles",
"defaultChoice": "npm",
"minHostVersion": ">=2026.3.31"
"minHostVersion": ">=2026.4.1"
},
"release": {
"publishToNpm": true
@@ -1,6 +0,0 @@
import { describe } from "vitest";
import { installDmPolicyContractSuite } from "../../../test/helpers/channels/dm-policy-contract.js";
describe("bluebubbles dm policy contract", () => {
installDmPolicyContractSuite("bluebubbles");
});

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