Compare commits

..

255 Commits

Author SHA1 Message Date
Vincent Koc
58e75bbff2 test(plugins): cover install ledger reload indexing 2026-04-25 00:21:36 -07:00
Vincent Koc
2ebe4e7675 test(plugins): lock installed index source contract 2026-04-25 00:08:31 -07:00
Vincent Koc
c6f1d27733 fix(plugins): index install ledger source facts 2026-04-25 00:03:49 -07:00
Vincent Koc
9ecdb94e9d feat(channels): read setup discovery from installed index 2026-04-25 00:03:49 -07:00
Vincent Koc
ea9824d5d2 feat(models): read provider owners from installed index 2026-04-25 00:03:49 -07:00
Vincent Koc
5399058c7f feat(plugins): split cold provider contributions 2026-04-25 00:03:48 -07:00
Vincent Koc
3a4078cc40 feat(plugins): add installed plugin index 2026-04-25 00:03:48 -07:00
Vincent Koc
dfa52aaab0 docs(changelog): clean up top Unreleased section formatting and dedupe
- Remove duplicate #66884 alexlomt entry from top Unreleased > Fixes;
  the canonical entry already lives under 2026.4.24 (Unreleased) per
  Mason Huang's earlier 'move #66884 entry to 2026.4.24' commit.
- Reflow the wrapped 3-line Tool Access bullet (#71405) onto a single
  line so it matches every other bullet in the section.
2026-04-25 00:00:02 -07:00
Vincent Koc
d8a70a7e49 docs(changelog): add missing entries for 3 external-contributor PRs
Three external-contributor commits from the last day landed without
CHANGELOG entries:

- Alex Fries (#68286, @ajfonthemove): hybrid memory search component
  scores. Added under Unreleased > Changes (feat).
- Charles Dusek (#51267, @cgdusek): malformed tool-result text-block
  guard. Added under Unreleased > Fixes.
- Jerome Benoit (#59935, @jerome-benoit): Nix Home Manager daemon PATH
  support. Added under Unreleased > Fixes.

Also drop a duplicate raw-subject changelog line for #66884 that
restated alexlomt's already-formatted entry one line above.
2026-04-24 23:56:46 -07:00
Ayaan Zaidi
8e40bdba90 fix(cli): scope dev reset to active profile 2026-04-25 12:25:45 +05:30
Vincent Koc
56eb1ffabf fix(diagnostics-otel): support preloaded sdk mode (#71450) 2026-04-24 23:55:34 -07:00
Peter Steinberger
417b1c5507 feat(google-meet): export artifacts reports 2026-04-25 07:53:31 +01:00
Peter Steinberger
bb5e278f63 fix(feishu): stabilize topic group session keys 2026-04-25 07:53:05 +01:00
Peter Steinberger
d068cb960d fix: stabilize qa lab memory and thinking scenarios 2026-04-25 07:50:04 +01:00
Peter Steinberger
b34ece705f fix: retire idle bundled MCP runtimes 2026-04-25 07:49:12 +01:00
Alex Fries
66e66f19c6 feat(memory-core): expose hybrid search component scores
Expose raw `vectorScore` and `textScore` alongside the combined hybrid memory search `score`.

- Preserve vector/text component scores from `mergeHybridResults` output.
- Add optional component-score fields to both memory host SDK type surfaces.
- Extend hybrid merge tests for vector-only, text-only, and overlapping result cases.
- Document that component scores remain raw retrieval diagnostics while temporal decay/MMR only adjust or reorder the combined ranking `score`.

Closes #68166.

Maintainer verification:
- `pnpm test extensions/memory-core/src/memory/hybrid.test.ts`
- `pnpm check:changed`
- Fresh GitHub checks passed.

Co-authored-by: Alex Fries <alex@engramlabs.io>
2026-04-25 01:46:03 -05:00
Peter Steinberger
a983ea61ac feat(google-meet): include transcript entries in artifacts 2026-04-25 07:43:49 +01:00
Ayaan Zaidi
356530598a docs(changelog): note restart continuation recovery (#70780) (thanks @fuller-stack-dev) 2026-04-25 12:12:52 +05:30
Ayaan Zaidi
a903df02f5 fix(gateway): bound restart continuation recovery 2026-04-25 12:12:52 +05:30
FullerStackDev
03addfe9ba fix(gateway): tighten session delivery recovery 2026-04-25 12:12:52 +05:30
FullerStackDev
2261550633 fix(gateway): address restart continuation review comments 2026-04-25 12:12:52 +05:30
FullerStackDev
0ac81d41b6 fix(gateway): durably hand off restart continuations 2026-04-25 12:12:52 +05:30
Vincent Koc
4df0e10623 fix(feishu): back off failed streaming card starts 2026-04-24 23:41:01 -07:00
Peter Steinberger
d37f165bee feat(google-meet): add oauth doctor 2026-04-25 07:40:25 +01:00
Peter Steinberger
2ff7eb36cf fix(models): expose codex runtime context caps 2026-04-25 07:38:31 +01:00
Vincent Koc
3d554aefdf fix(logging): keep log transport internals private (#71322)
* fix(logging): share transports across module instances

* fix(logging): share transports across module instances

* fix(logging): share transports across module instances

* fix(logging): remove global log transport hooks

* test(agents): capture diagnostic logs after module reset
2026-04-24 23:36:57 -07:00
Charles Dusek
6b38714cb9 fix(agents): guard malformed tool result text blocks
Harden context pruning and tool-result character estimation against malformed `{ type: "text" }` blocks created by void/undefined tool handler results.

- Require text blocks to carry a string before using `.length` in the tool-result estimator.
- Guard context-pruning text/image loops against malformed and null content entries.
- Serialize malformed non-string text blocks for pruning size accounting so they cannot bypass trimming as zero-sized.
- Add regression coverage for malformed text blocks, null entries, and non-string text payloads.

Closes #34979.

Maintainer verification:
- `pnpm test src/agents/pi-embedded-runner/tool-result-char-estimator.test.ts src/agents/pi-hooks/context-pruning/pruner.test.ts`
- `pnpm check:changed`
- GitHub checks passed, including the OpenAI / Opus 4.6 parity gate.

Based on prior work by #39331 and #34980.

Co-authored-by: Charles Dusek <cgdusek@gmail.com>
Co-authored-by: alvinttang <alvinttang@users.noreply.github.com>
Co-authored-by: coffeexcoin <coffeexcoin@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-25 01:36:40 -05:00
Peter Steinberger
1752b15a21 feat(google-meet): add artifacts and attendance commands 2026-04-25 07:36:10 +01:00
Peter Steinberger
209d50b52c feat(browser): add coordinate click action
Co-authored-by: Daniel Lutts <daniellutts@10-19-94-204.dynapool.wireless.nyu.edu>
2026-04-25 07:31:33 +01:00
Val Alexander
982230f460 Refine tool access controls (#71405)
* feat(ui): refine tool access controls

* fix(ui): tighten tool access scanning

* fix(ui): keep tool access toggles visible (#71405)

* test(daemon): cover launchd restart fallback plist reads (#71405)

* test(daemon): drop duplicate launchd read mock (#71405)

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-04-25 07:22:53 +01:00
Vincent Koc
d7fae7a5e7 docs(changelog): note Feishu streaming dedupe fix (#71431) 2026-04-24 23:21:40 -07:00
Vincent Koc
b59ba1dc8e docs: cover new contextInjection 'never' mode and Nix daemon PATH support
Two recent code changes lacked or had only partial doc coverage:

- contextInjection 'never' (#65006, xDarkicex): the new mode is now
  documented under agents.defaults.contextInjection, alongside the
  existing 'continuation-skip' mode, with guidance on when to use it
  (custom context engines, native runtimes that own their prompt).
- Nix Home Manager daemon PATH (#44402, jerome.benoit): document the
  service PATH auto-discovery (NIX_PROFILES right-to-left precedence
  and ~/.nix-profile/bin fallback) under the Nix install page.

Also sentence-case three Title-Cased headings on the Nix page ('What
You Get', 'Quick Start', 'Nix Mode Runtime Behavior') and drop a
duplicate body H1 that restated the frontmatter title.
2026-04-24 23:19:06 -07:00
ToToKr
2aa313cd90 fix(feishu): prevent duplicate message after streaming card close (#67791) (#68491)
* fix(feishu): prevent duplicate message after streaming card close (#67791)

When onIdle closed the streaming card before the final delivery arrived, the streamed text was not tracked in deliveredFinalTexts. The subsequent final payload bypassed the streaming?.isActive() guard (already closed) and fell through to the non-streaming path, sending the same content as a redundant text/card message. Track raw streamText in deliveredFinalTexts when closeStreaming finalizes the card so the duplicate-final check catches it.

* test(feishu): cover idle streaming final dedupe

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-04-24 23:18:10 -07:00
Peter Steinberger
36eae5a2c7 fix: tighten silent cron exec notifications 2026-04-25 07:15:08 +01:00
Mark Goldenstein
bd60df3e53 fix: silence cron exec completion noise 2026-04-25 07:15:08 +01:00
Peter Steinberger
017252e4f8 test(daemon): remove duplicate launchd read mock 2026-04-25 07:12:17 +01:00
Jérôme Benoit
b8b270d5b8 fix(daemon): add Nix Home Manager PATH support
Add Nix Home Manager profile bin directories to generated gateway service PATHs on macOS and Linux.

Includes ~/.nix-profile/bin fallback when NIX_PROFILES is absent, honors NIX_PROFILES right-to-left precedence when present, and covers the service PATH resolver with focused unit tests.

Closes #44402.
2026-04-25 01:12:10 -05:00
Peter Steinberger
33d5ebbff7 test(daemon): read launchd fixture files 2026-04-25 07:11:33 +01:00
Peter Steinberger
2a96ea4d72 test(agents): cover committed delivery fallback 2026-04-25 07:11:33 +01:00
Peter Steinberger
07cf1dd65c test: remove duplicate launchd read mock 2026-04-25 07:10:32 +01:00
Peter Steinberger
85cab8b516 test: fix launchd restart mock state 2026-04-25 07:09:47 +01:00
Peter Steinberger
22aa402b64 test(daemon): mock launchd plist reads 2026-04-25 07:08:36 +01:00
Peter Steinberger
d957401c7e test(daemon): type launchd kickstart code fake 2026-04-25 07:08:36 +01:00
Peter Steinberger
a7c8a1ba0d fix(browser): finish existing-session attach port (#57245) 2026-04-25 07:08:36 +01:00
Peter Steinberger
998e09ee00 docs(changelog): note browser existing-session fixes (#57245) 2026-04-25 07:08:36 +01:00
Peter Steinberger
ad8737af2c fix(browser): tighten WS3 status probes
# Conflicts:
#	extensions/browser/src/browser/chrome-mcp.test.ts
#	extensions/browser/src/browser/chrome-mcp.ts
#	extensions/browser/src/browser/routes/basic.ts
2026-04-25 07:08:35 +01:00
Peter Steinberger
3c31facfa2 Fix sticky Chrome MCP status probes
# Conflicts:
#	extensions/browser/src/browser/chrome-mcp.ts
#	extensions/browser/src/browser/server-context.ts
2026-04-25 07:08:35 +01:00
Peter Steinberger
8e18b3cc20 fix(browser): report attach-only profile transport truthfully
# Conflicts:
#	extensions/browser/src/browser/routes/basic.ts
2026-04-25 07:08:35 +01:00
Peter Steinberger
0ff7aa5c3d fix(browser): retry stale cached playwright attach once
(cherry picked from commit ec252ebd7b45d54e431e0d7599532e5a0c1a9b73)

# Conflicts:
#	extensions/browser/src/browser/pw-session.ts
2026-04-25 07:08:35 +01:00
Peter Steinberger
d6a9165b9e test: stabilize gateway and TTS tests 2026-04-25 07:07:58 +01:00
Peter Steinberger
996ec2dd76 fix(agents): key fallback on committed delivery 2026-04-25 07:06:46 +01:00
Valentinws
4a68fa3962 fix(ui): keep tmp-dir resolver browser-import safe
Defers the Node fs.constants lookup until tmp-dir resolution actually runs, adds browser-shim import regression coverage, and records the fix in the changelog.\n\nLocal verification:\n- pnpm test src/infra/tmp-openclaw-dir.browser-import.test.ts src/infra/tmp-openclaw-dir.test.ts src/logging/logger.browser-import.test.ts\n- pnpm test src/infra/run-node.test.ts -t "serializes runtime postbuild restaging|forwards wrapper SIGTERM"\n- pnpm build\n\nCo-authored-by: Valentinws <Valentinws@users.noreply.github.com>
2026-04-25 07:02:10 +01:00
Vincent Koc
f5868ad1f8 fix(daemon): refresh launchd plist before restart bootstrap (#71421) 2026-04-24 22:59:21 -07:00
xDarkicex
cc0992564b fix: add contextInjection never mode (#65006) (thanks @xDarkicex) 2026-04-25 11:26:21 +05:30
Peter Steinberger
18ffa81564 fix(agents): suppress delivered messaging fallback 2026-04-25 06:55:47 +01:00
Vincent Koc
47a4124dc3 fix(daemon): tolerate loaded launchctl bootstrap (#71413) 2026-04-24 22:54:12 -07:00
Peter Steinberger
cfb551c709 test(openrouter): cover DeepSeek live cache hits 2026-04-25 06:49:08 +01:00
Peter Steinberger
2cd2732ab6 docs: document trusted CIDR node auto-approval 2026-04-25 06:46:26 +01:00
Peter Steinberger
537a8e25ed fix(signal): classify filename-only voice notes 2026-04-25 06:45:54 +01:00
Peter Steinberger
f44759073b feat(gateway): auto-approve trusted CIDR node pairing (#61004) (thanks @sahilsatralkar) 2026-04-25 06:40:25 +01:00
Sukhdeep
6c1d4414d9 fix(browser): dedupe concurrent lazy start (#61772) (#61772)
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-04-25 06:39:14 +01:00
Peter Steinberger
d79b9e0af4 fix(openrouter): allow DeepSeek cache-ttl eligibility 2026-04-25 06:38:34 +01:00
Peter Steinberger
29f7a2f441 docs(cron): clarify isolated session context 2026-04-25 06:37:18 +01:00
Peter Steinberger
f2745aa03a refactor(cron): clarify ambient session context rollover 2026-04-25 06:37:18 +01:00
Ziy
eaf6d3c146 fix(dashboard): keep bearer token out of runtime logs
Avoid logging tokenized Control UI URLs or SSH hints while preserving clipboard/browser token handoff.\n\nThanks @Ziy1-Tan!
2026-04-25 06:36:11 +01:00
Peter Steinberger
c2a2a481b2 fix(whatsapp): preserve audio-as-voice payload intent 2026-04-25 06:35:57 +01:00
monsonego
80b6da72f5 test(ui): cover nested qualified chat model refs (#65340)
Adds regression coverage for provider-qualified nested model ids such as nvidia/deepseek-ai/deepseek-v3.2.

Validated:
- pnpm test ui/src/ui/chat-model-ref.test.ts ui/src/ui/chat-model-select-state.test.ts

Thanks @monsonego.
2026-04-25 06:32:47 +01:00
Peter Steinberger
0970fc5da7 ci: relax bundled channel fast smoke timeout 2026-04-25 06:31:39 +01:00
Mason Huang
d4ed19dafc chore(changelog): move #66884 entry to 2026.4.24 (#71410) 2026-04-25 13:31:33 +08:00
Peter Steinberger
5b59079fd4 fix(tts): preserve audio-only hook transcript 2026-04-25 06:28:54 +01:00
Vincent Koc
88ea3d839b docs: sentence-case Title Case table headers in Codex/runtime docs
Three table headers introduced in recent agent-runtime / Codex-harness
doc commits used Title Case despite the surrounding house style:

- agent-runtimes.md L17: 'What It Means' -> 'What it means'
- agent-runtimes.md L100: 'Why It Matters' -> 'Why it matters'
- codex-harness.md L615: 'V1 Boundary' / 'Future Path' ->
  'V1 boundary' / 'Future path' (V1 stays as the recognized acronym)
2026-04-24 22:27:10 -07:00
Vincent Koc
57f5b3b201 fix(daemon): harden launchd restart handoff (#71409) 2026-04-24 22:27:05 -07:00
Vincent Koc
44ad970e48 docs: replace generic 'this page covers' intros with direct openings
Four pages started with weak meta-descriptions ('This page covers...')
that restate the frontmatter summary. Replace with direct content-first
openings, and sentence-case a stray 'Slash Commands' link in
configuration-reference.
2026-04-24 22:25:03 -07:00
Vincent Koc
93346b00fb docs: drop redundant body H1s that duplicated frontmatter title
- concepts/streaming.md: remove '# Streaming + chunking'.
- reference/session-management-compaction.md: remove Title Case H1
  '# Session Management & Compaction (Deep Dive)'.
- plugins/voice-call.md: remove '# Voice Call (plugin)'.

CLI pages keep their command-formatted body H1s since that is the repo
convention and the formatting is not expressible in frontmatter.
2026-04-24 22:23:45 -07:00
Peter Steinberger
f9c268cf56 ci: keep fast fixture edits on narrow path 2026-04-25 06:22:57 +01:00
Peter Steinberger
576c6c240f fix(discord): collapse cron announce text 2026-04-25 06:22:30 +01:00
Vincent Koc
ee3c32c103 docs(plugins): clarify inspector package boundary 2026-04-24 22:21:53 -07:00
Sean Coley
a35333abe1 fix(browser): recover stale Chromium profile locks (#62935) (#62935)
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-04-25 06:21:49 +01:00
Peter Steinberger
f00d65a304 test: align status scan compatibility fixture 2026-04-25 06:18:32 +01:00
Peter Steinberger
2f097c47f8 ci: route narrow ci changes through fast path 2026-04-25 06:18:32 +01:00
Vincent Koc
c948c63bbd docs: unify casing and replace path-as-text links across recent doc surfaces
Sweep recent (last ~5h) doc edits for two readability/uniformity issues:

- Replace 42 path-as-text links of the form '[/foo/bar](/foo/bar)' with
  descriptive labels derived from each target page's frontmatter title
  (e.g. '[Anthropic]', '[Token use and costs]', '[OpenAI-compatible
  endpoints]'). Affected files include gateway/troubleshooting,
  concepts/oauth, reference/session-management-compaction, and
  reference/transcript-hygiene.
- Sentence-case Title-Cased headings and link text in Related sections
  across codex-harness, model-providers, tools/plugin, sdk-runtime,
  sdk-setup, prompt-caching, ci, cli/config, google-meet, browser,
  rich-output-protocol, subagents, web/control-ui, while preserving
  brand and proper-noun capitalization (OpenAI, Codex, Chrome, Parallels,
  Z.AI, etc.).
2026-04-24 22:18:22 -07:00
Peter Steinberger
88ca1859ed fix(discord): use undici multipart form data (#71383) 2026-04-25 06:18:10 +01:00
Peter Steinberger
439f353cf6 fix(discord): bridge undici multipart types 2026-04-25 06:18:10 +01:00
TC500
52ebdabcfd fix(discord): use undici form data for multipart uploads 2026-04-25 06:18:10 +01:00
Peter Steinberger
1afbfdf451 fix(control-ui): preserve optimistic chat tail 2026-04-25 06:15:55 +01:00
Vincent Koc
86dc820560 feat(plugins): add compatibility registry 2026-04-24 22:15:41 -07:00
Vincent Koc
f0ceb4b68f fix(cron): isolate fresh cron session state
* fix(cron): isolate fresh cron session state

* fix(cron): deep-copy isolated session state

* fix(cron): reset isolated session context

* test(providers): avoid shared mock races

* test(providers): type injected stream fakes

* ci: refresh package boundary on reply runtime changes

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-04-24 22:15:19 -07:00
alexlomt
7a9584f0f9 fix(ci): harden release checks workflow inputs (#66884)
Merged via squash.

Prepared head SHA: d4e0097301
Co-authored-by: alexlomt <181166594+alexlomt@users.noreply.github.com>
Co-authored-by: hxy91819 <8814856+hxy91819@users.noreply.github.com>
Reviewed-by: @hxy91819
2026-04-25 13:13:30 +08:00
Peter Steinberger
8acc92c881 feat(google): support Gemini TTS style profile 2026-04-25 06:11:23 +01:00
Peter Steinberger
3f63ba8fd8 fix(webchat): hide heartbeat history artifacts 2026-04-25 06:10:57 +01:00
Peter Steinberger
a2a49b430c test(plugins): route tts contract helper changes narrowly 2026-04-25 06:05:00 +01:00
Mason Huang
b79272baad CI: increase CodeQL JavaScript runner size (#71402)
* CI: increase CodeQL JavaScript runner size

* CI: trim CodeQL JavaScript scope further

* ci: keep CodeQL extension coverage

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-04-25 13:04:48 +08:00
Peter Steinberger
104a8f3f52 test(browser): handle oom wrapper in chrome launch assertion 2026-04-25 06:02:03 +01:00
Peter Steinberger
fa22ca8883 test(qa): cover stale subagent child links 2026-04-25 05:59:42 +01:00
Peter Steinberger
7f6452897e fix(agents): repair strict provider tool replay 2026-04-25 05:56:13 +01:00
Peter Steinberger
e31aef7e19 fix(tts): migrate legacy edge config in doctor 2026-04-25 05:55:54 +01:00
Peter Steinberger
b0e834b2d9 fix(browser): support per-profile executable paths
Co-authored-by: nobrainer-tech <nobrainer-tech@users.noreply.github.com>
2026-04-25 05:50:20 +01:00
Peter Steinberger
759fe0bf95 docs: cover reply media and voice-call fixes 2026-04-25 05:48:29 +01:00
Peter Steinberger
938b53698e test(voice-call): cover tunnel startup 2026-04-25 05:43:42 +01:00
Peter Steinberger
2b87d9f3ec fix(voice-call): reject local webhook fallback 2026-04-25 05:41:29 +01:00
Peter Steinberger
2f39e6df59 fix(tts): prefer active speech provider lookup 2026-04-25 05:39:10 +01:00
Peter Steinberger
2033075570 fix(sessions): hide stale subagent child links 2026-04-25 05:39:10 +01:00
Peter Steinberger
c11730fd09 fix(tts): keep speech fallback discovery scoped 2026-04-25 05:38:45 +01:00
Peter Steinberger
b8239be46b fix(voice-call): settle cleared tts queue 2026-04-25 05:38:36 +01:00
Jamil Zakirov
52267a6b75 fix(auto-reply): run message_sending before inbound delivery
Run inbound auto-reply delivery through message_sending hooks before sending replies.

Co-authored-by: Jamil Zakirov <15848838+jzakirov@users.noreply.github.com>
2026-04-25 10:07:35 +05:30
Peter Steinberger
04c5bbf33d fix(reply): dedupe block-streamed media 2026-04-25 05:35:31 +01:00
Peter Steinberger
98a99765af fix(gateway): invoke plugin-backed catalog tools
Co-authored-by: chat2way <chat2way@users.noreply.github.com>
2026-04-25 05:28:03 +01:00
Peter Steinberger
6602092a40 fix(browser): require admin scope for browser request
Co-authored-by: RichardCao <RichardCao@users.noreply.github.com>
2026-04-25 05:27:20 +01:00
Peter Steinberger
b84e57fca3 fix(tts): use resolved config for tool synthesis 2026-04-25 05:18:49 +01:00
Peter Steinberger
c2139635ff fix: format acpx doctor details safely 2026-04-25 05:15:00 +01:00
Peter Steinberger
757aee4cdd fix(subagents): retire stale unended runs
Co-authored-by: HCL <chenglunhu@gmail.com>
2026-04-25 05:14:41 +01:00
Peter Steinberger
97fd45a8c1 fix: honor acp allowed agents for acpx probe 2026-04-25 05:10:49 +01:00
Peter Steinberger
14eab13ab4 fix(agents): keep voice media with no-reply sentinel 2026-04-25 05:09:58 +01:00
Peter Steinberger
85db7af8d9 test: cover opencode-go image default pairing 2026-04-25 05:06:17 +01:00
Peter Steinberger
fa976e5b93 feat: update opencode-go default model
Co-authored-by: masrlinu <5259918+masrlinu@users.noreply.github.com>
2026-04-25 05:03:32 +01:00
Ted Li
8cc38c1b86 fix: stop session lock failover (#68700) (thanks @MonkeyLeeT)
* fix(agents): stop treating session lock waits as timeout

* fix(agents): ignore abort-wrapped session lock waits

* fix(agents): keep explicit failover metadata authoritative

* fix(agents): respect inferred failover metadata

* fix(agents): ignore generic abort codes for lock waits

* fix(agents): suppress cause-based lock wait fallback

* fix(agents): type session lock timeout errors

* fix: stop session lock failover (#68700) (thanks @MonkeyLeeT)

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-04-25 09:33:19 +05:30
Peter Steinberger
c03e5b3c3a docs(tts): clarify legacy provider migration 2026-04-25 05:01:09 +01:00
Michiel van den Donker
2c716f5677 fix: enforce memory search session visibility (#70761) (thanks @nefainl)
* [EV-001] memory-core: filter memory_search session hits by visibility

- Move session visibility + listSpawnedSessionKeys to plugin-sdk; sync test
  hook with sessions-resolution __testing.setDepsForTest
- Extract loadCombinedSessionStoreForGateway to config/sessions; re-export
  from gateway session-utils
- Add session-transcript-hit stem resolver for builtin + QMD paths
- Post-filter memory_search results before citations/recall; fail closed when
  requester session key missing; optional corpus=sessions
- Tests: stem extraction, visibility filter smoke, existing suites green

* chore: sync plugin-sdk exports for session-transcript-hit and session-visibility

Run pnpm plugin-sdk:sync-exports so package.json exports match
scripts/lib/plugin-sdk-entrypoints.json. Fixes contract tests and
lint:plugins:plugin-sdk-subpaths-exported for memory-core imports.

* fix(EV-001): cross-agent session memory hits + hoist combined store load

- resolveTranscriptStemToSessionKeys: stop filtering by requester agentId so
  keys from other agents reach createSessionVisibilityGuard (a2a + visibility=all).
- Re-export loadCombinedSessionStoreForGateway from session-transcript-hit;
  filterMemorySearchHitsBySessionVisibility loads the combined store once per pass.
- Drop unused agentId from filter params; extend tests (Greptile/Codex review).

* fix(memory_search): honor corpus=sessions before maxResults cap

Pass sources into MemoryIndexManager.search so FTS/vector queries add
source IN (...) before ranking and top-N slice (Codex: non-session hits
could fill the window).

QMD path: oversample fetch limit for single-source recall, filter by
source, then diversify/clamp to the requested maxResults.

Wire corpus=sessions from tools; extend MemorySearchManager opts and
wrappers.

* fix(memory_search): apply corpus=memory source filter like sessions

Pass sources: ["memory"] into manager.search so maxResults applies only
within the memory index; post-filter for defense in depth. Document
corpus=memory in the tool description.

* fix: scope qmd session memory search

* fix: enforce memory search session visibility (#70761) (thanks @nefainl)

---------

Co-authored-by: NefAI <info@nefai.nl>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-04-25 09:30:21 +05:30
Peter Steinberger
978a50a3c5 fix(minimax): normalize tts pitch for api 2026-04-25 04:58:20 +01:00
Peter Steinberger
5d724863bb test: show live Docker profile mount 2026-04-25 04:57:20 +01:00
Peter Steinberger
b9da7cbf01 test: strip live gateway reasoning wrappers 2026-04-25 04:57:20 +01:00
Peter Steinberger
0f0c855a8b fix(google-meet): surface browser create manual actions 2026-04-25 04:57:06 +01:00
Peter Steinberger
70fd1c91aa fix(channels): harden tool progress previews 2026-04-25 04:54:29 +01:00
Peter Steinberger
da89108b82 docs(minimax): document tts env vars 2026-04-25 04:53:33 +01:00
Peter Steinberger
225ff9a866 fix(minimax): transcode voice-note tts to opus 2026-04-25 04:52:25 +01:00
Ayaan Zaidi
f3cc74ec5d fix: include exec completion payloads (#71213) (thanks @GodsBoy) 2026-04-25 09:21:29 +05:30
Ayaan Zaidi
a8f9e29e1a fix(heartbeat): classify untrusted exec completions 2026-04-25 09:21:29 +05:30
GodsBoy
724692bb8c chore(ci): refresh raw fetch allowlist 2026-04-25 09:21:29 +05:30
GodsBoy
349749f73d fix(heartbeat): include exec completion payloads 2026-04-25 09:21:29 +05:30
Peter Steinberger
7e52223d32 fix: register opencode image understanding 2026-04-25 04:50:01 +01:00
Ayaan Zaidi
398496c45b test(config): cover agent context token validation 2026-04-25 09:17:47 +05:30
statxc
ed03d91ae0 fix(config): allow per-agent contextTokens override in agents.list 2026-04-25 09:17:47 +05:30
Peter Steinberger
391289564c fix(media): validate managed reply media aliases 2026-04-25 04:47:25 +01:00
Peter Steinberger
a31374f097 fix: allow managed media from sandbox replies 2026-04-25 04:39:11 +01:00
Peter Steinberger
7875092f4d feat(openrouter): add tts provider 2026-04-25 04:36:49 +01:00
Peter Steinberger
c7f18a6b9d test(agents): cover Gemini incomplete-turn guard 2026-04-25 04:35:53 +01:00
Peter Steinberger
6f72b74cec fix: strip opencode image reasoning none 2026-04-25 04:35:12 +01:00
Peter Steinberger
96515891a2 fix: keep agent json stdout clean 2026-04-25 04:32:18 +01:00
Peter Steinberger
b0709a894d test(deepseek): cover V4 replay live 2026-04-25 04:30:38 +01:00
pashpashpash
6cfe810402 Refresh the Codex runtime docs
Refresh the Codex runtime docs and cross-link the Codex harness, OpenAI provider, agent runtime, plugin hook, ACP agent, and status pages.
2026-04-24 20:30:33 -07:00
Peter Steinberger
05fbdd4b28 fix: handle missing tailscale binary 2026-04-25 04:28:32 +01:00
Peter Steinberger
678ed5d512 fix(deepseek): normalize V4 tool-call replay 2026-04-25 04:25:44 +01:00
Peter Steinberger
c81b3ab6b9 fix(minimax): mark tts output voice-compatible 2026-04-25 04:25:12 +01:00
Ayaan Zaidi
de07739e40 docs: update changelog for gateway status perf 2026-04-25 08:52:52 +05:30
Ayaan Zaidi
2bf2fd6c3d fix(cli): preserve gateway status rpc probe semantics 2026-04-25 08:52:52 +05:30
Andy Lin
43beceaee7 perf(cli): trim gateway status startup work 2026-04-25 08:52:52 +05:30
Peter Steinberger
cc0f3e0e40 docs: add changelog for Gemini incomplete-turn recovery (#71362) 2026-04-25 04:21:35 +01:00
Neerav Makwana
0ec3b79c07 refactor: reuse model-id prefix helper for Gemini guard 2026-04-25 04:21:35 +01:00
Neerav Makwana
0ce93c9f1a fix: enable incomplete-turn recovery for Gemini 2026-04-25 04:21:35 +01:00
Peter Steinberger
73a6a2a6ab fix(tts): merge allowlisted speech providers 2026-04-25 04:20:04 +01:00
Peter Steinberger
cf07f01d0d docs(plugin-sdk): document browser tab cleanup config type 2026-04-25 04:19:50 +01:00
Peter Steinberger
5699209d00 fix: match bare exec allowlist commands
Co-authored-by: Kengwei Lu <kengwei@kvvlu.com>
Co-authored-by: ZC <chenzhangcode@163.com>
Co-authored-by: dengluozhang <275862143+dengluozhang@users.noreply.github.com>
2026-04-25 04:18:24 +01:00
Peter Steinberger
cb9c927ca6 docs: document config recovery policy 2026-04-25 04:18:24 +01:00
Peter Steinberger
0764f86e18 test(memory): keep embedding provider selection isolated 2026-04-25 04:17:27 +01:00
Peter Steinberger
8b31ba93b5 fix(plugin-sdk): align browser profile facade exports 2026-04-25 04:17:27 +01:00
Peter Steinberger
f1154fc5ed docs: note codex elicitation approval sanitization (#71343) (thanks @Lucenx9) 2026-04-25 04:17:27 +01:00
Lucenx9
e098a439c4 fix(codex): sanitize elicitation approval text 2026-04-25 04:17:27 +01:00
Peter Steinberger
972d8fc1cf fix(agents): keep reply tool snapshots aligned 2026-04-25 04:15:47 +01:00
Peter Steinberger
b13545355d fix(tts): parse bare tags and ignore code examples 2026-04-25 04:13:12 +01:00
Peter Steinberger
a126a9013d feat(plugins): expose nodes runtime to cli commands 2026-04-25 04:12:50 +01:00
Peter Steinberger
3731a7c8f2 fix(macos): retry talk tts via gateway 2026-04-25 04:09:43 +01:00
Peter Steinberger
9a0b26cafc test: cover config recovery policy edges 2026-04-25 04:06:35 +01:00
Josh Lehman
f369939fed fix: avoid plugin-local config recovery rollback (#71289) 2026-04-25 04:06:35 +01:00
Shakker
306c0f73bf feat: add manifest model catalog planner (#71368) (thanks @shakkernerd) 2026-04-25 04:05:30 +01:00
Shakker
b6c24e5322 fix: report model catalog manifest conflicts 2026-04-25 04:05:30 +01:00
Shakker
9e190f1f6a test: cover manifest model catalog planner 2026-04-25 04:05:30 +01:00
Shakker
dabdc779be feat: add manifest model catalog planner 2026-04-25 04:05:30 +01:00
Peter Steinberger
7920f8d4fd fix(compaction): honor manual keepRecentTokens 2026-04-25 04:03:09 +01:00
Peter Steinberger
92b17af817 fix(tts): honor legacy edge voice config 2026-04-25 04:02:17 +01:00
Peter Steinberger
5569d6d9d3 fix: accept singular tool_call finish reason 2026-04-25 04:00:03 +01:00
Shakker
8a14328c69 fix: normalize manifest catalog provider ids 2026-04-25 03:58:44 +01:00
Peter Steinberger
455e84f776 fix(voice-call): start listening after telnyx greetings 2026-04-25 03:57:47 +01:00
Peter Steinberger
344ee3782d fix(google-meet): guide timeout recovery 2026-04-25 03:57:26 +01:00
Peter Steinberger
37c2450124 fix: support draft 2020 mcp tool schemas 2026-04-25 03:56:35 +01:00
Peter Steinberger
9fbfedf12a fix: skip invalid completions stream chunks 2026-04-25 03:55:21 +01:00
Peter Steinberger
5381625f45 fix(voice-call): terminate expired restored calls 2026-04-25 03:55:01 +01:00
Peter Steinberger
fe930b987e fix(plugin-sdk): align browser tab cleanup type exports 2026-04-25 03:53:21 +01:00
Peter Steinberger
250acdd2a2 test(voice-call): cover cloudflare twilio signatures 2026-04-25 03:52:31 +01:00
Peter Steinberger
355c92d69b fix(sessions): honor load-time maintenance config 2026-04-25 03:52:25 +01:00
Peter Steinberger
26f06afb90 fix: forward completions prompt cache keys 2026-04-25 03:51:51 +01:00
Peter Steinberger
24fdfdba6e test: disable Docker CLI image probe by default 2026-04-25 03:51:41 +01:00
Peter Steinberger
a669ba7df1 fix(voice-call): reap stale pre-answer calls 2026-04-25 03:50:27 +01:00
Peter Steinberger
a98a0b94d1 fix: isolate browser proxy routing
Co-authored-by: Sanjays2402 <Sanjays2402@users.noreply.github.com>
2026-04-25 03:49:06 +01:00
Peter Steinberger
9e5d09c962 fix(config): reject legacy secretref env markers 2026-04-25 03:48:11 +01:00
Peter Steinberger
7dc005fab6 fix(voice-call): honor telephony tts timeout 2026-04-25 03:47:56 +01:00
Peter Steinberger
e5babbb5e7 test: cover openai-compatible usage chunks 2026-04-25 03:45:16 +01:00
Ada
413e407fb8 fix(whatsapp): deliver tool replies that include media (#60968)
Merged via squash.

Prepared head SHA: 26704020a4
Co-authored-by: adaclaw <266167987+adaclaw@users.noreply.github.com>
Co-authored-by: mcaxtr <7562095+mcaxtr@users.noreply.github.com>
Reviewed-by: @mcaxtr
2026-04-24 23:44:59 -03:00
Peter Steinberger
8f11e5ad18 fix(voice-call): scope sandbox session to agent 2026-04-25 03:43:01 +01:00
Peter Steinberger
460720d0a1 perf(feishu): avoid unconditional lifecycle waits 2026-04-25 03:41:18 +01:00
Peter Steinberger
a6d16a2153 perf(plugins): skip registry expansion for explicit document extractor allowlists 2026-04-25 03:41:18 +01:00
Peter Steinberger
28de3e1e5c perf(test): isolate core test hotspots 2026-04-25 03:41:17 +01:00
Peter Steinberger
c150110e02 test(voice-call): cover inbound transcript response handoff 2026-04-25 03:39:07 +01:00
Peter Steinberger
893c1d61ee fix: honor sandbox browser SSRF policy 2026-04-25 03:37:08 +01:00
Peter Steinberger
1906dc01bf fix(elevenlabs): omit mp3 accept for pcm tts 2026-04-25 03:36:54 +01:00
Peter Steinberger
7d5a7c4343 test(openai): preserve zero realtime transcription vad config 2026-04-25 03:34:41 +01:00
Shakker
55d3e7bc47 refactor: centralize model catalog normalization (#71360) (thanks @shakkernerd) 2026-04-25 03:34:36 +01:00
Shakker
c0bda91641 refactor: use shared model catalog normalization in manifests 2026-04-25 03:34:36 +01:00
Shakker
31f4991205 test: cover model catalog normalization 2026-04-25 03:34:36 +01:00
Shakker
b9da940813 feat: add model catalog normalizer 2026-04-25 03:34:36 +01:00
Shakker
efcd96a8eb feat: add model catalog shared types 2026-04-25 03:34:36 +01:00
Peter Steinberger
9a3dece879 fix(voice-call): allow dedicated response agent 2026-04-25 03:32:09 +01:00
Peter Steinberger
e442065970 feat(google-meet): add browser recovery diagnostics 2026-04-25 03:31:11 +01:00
Peter Steinberger
996e9226e5 fix(skills): restore legacy skill metadata fallback (#71346) 2026-04-25 03:30:51 +01:00
chen-zhang-cs-code
bb5f523068 fix(skills): honor legacy clawdbot metadata 2026-04-25 03:30:51 +01:00
Peter Steinberger
49f72b332f fix: harden openai-compatible completions payloads 2026-04-25 03:29:26 +01:00
Peter Steinberger
4302f6ea20 docs: credit codex bound-turn fix (#71317) 2026-04-25 03:29:14 +01:00
Lucenx9
cc87c9b120 fix(codex): reject unscoped bound turn events 2026-04-25 03:29:14 +01:00
Peter Steinberger
f7caf83da4 fix(voice-call): coalesce webhook server starts 2026-04-25 03:27:33 +01:00
Gustavo Madeira Santana
53618cca0d docs: clarify plugin discovery loading 2026-04-24 22:26:52 -04:00
Peter Steinberger
1bdf5307d9 refactor(discord): split outbound payload helpers 2026-04-25 03:24:42 +01:00
Peter Steinberger
30aa7e0d4d fix: harden Windows browser open 2026-04-25 03:24:00 +01:00
Peter Steinberger
31d8fdb525 fix(voice-call): answer telnyx inbound calls 2026-04-25 03:23:45 +01:00
Gustavo Madeira Santana
282c32db7c fix(cli): sanitize plugin command descriptors 2026-04-24 22:23:28 -04:00
Peter Steinberger
4da25d0125 fix: keep session store live during rotation 2026-04-25 03:21:49 +01:00
Peter Steinberger
f29e15c05d fix(telegram): bound tool progress preview formatting
Co-authored-by: Neerav Makwana <261249544+neeravmakwana@users.noreply.github.com>
2026-04-25 03:19:01 +01:00
Neerav Makwana
e54a37a91e fix(telegram): harden progress preview rendering 2026-04-25 03:19:01 +01:00
Neerav Makwana
c4a8b80dfa fix(telegram): hide tool progress by default 2026-04-25 03:19:01 +01:00
Peter Steinberger
95a2c9bcdc fix: expand browser executable home paths 2026-04-25 03:16:14 +01:00
Peter Steinberger
4a7ddd7ff5 test(agents): slim embedded runner hotspot coverage 2026-04-25 03:15:23 +01:00
Vincent Koc
f9ac92d1cc fix(deps): keep plugin ownership records live (#71331) 2026-04-24 19:15:12 -07:00
Peter Steinberger
f550aa7622 refactor(outbound): plan text and media sends 2026-04-25 03:12:59 +01:00
Peter Steinberger
a3862ffdf1 feat(voice-call): add setup smoke checks 2026-04-25 03:12:21 +01:00
Peter Steinberger
f9f7d6ffb5 fix(google-meet): reuse meet tabs across retries 2026-04-25 03:11:58 +01:00
Peter Steinberger
ba4cd90dbc fix(voice-call): share webhook runtime across contexts 2026-04-25 03:11:21 +01:00
Peter Steinberger
250d13de53 fix(agents): trust-gate tts transcript suppression
Co-authored-by: Neerav Makwana <261249544+neeravmakwana@users.noreply.github.com>
2026-04-25 03:11:17 +01:00
Neerav Makwana
628f0e8055 fix: gate tts output suppression on deliverable media 2026-04-25 03:11:17 +01:00
Neerav Makwana
db8f7478b4 fix: suppress tts transcript tool output 2026-04-25 03:11:17 +01:00
Gustavo Madeira Santana
6abab7555e test(plugins): align loader channel entry fixture 2026-04-24 22:10:58 -04:00
Peter Steinberger
713807b55d fix: harden openai auth and reasoning replay 2026-04-25 03:10:24 +01:00
Peter Steinberger
ae5c657367 fix: clean up idle browser tabs 2026-04-25 03:08:24 +01:00
Peter Steinberger
d99d9eda37 fix: honor media SSRF policy for PDF inputs (#71321) 2026-04-25 03:08:19 +01:00
Neerav Makwana
f9cb942aa9 fix(music): bound reference image fetches 2026-04-25 03:08:19 +01:00
Neerav Makwana
1bb5a96577 fix(media): cover generation reference media ssrf policy 2026-04-25 03:08:19 +01:00
Neerav Makwana
86556fcd47 fix(media): apply web fetch ssrf policy to media 2026-04-25 03:08:19 +01:00
Gustavo Madeira Santana
a5db42862d fix(plugins): expose channel CLI metadata in discovery (#71309)
Merged via squash.

Prepared head SHA: ce6bb35812
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
2026-04-24 22:06:57 -04:00
Peter Steinberger
7ef4ecf499 refactor(outbound): share reply fanout policy 2026-04-25 03:05:22 +01:00
Peter Steinberger
2f23511ffa test: accept staged bundled runtime deps proof 2026-04-25 03:05:04 +01:00
Peter Steinberger
a5ab488691 fix(voice-call): pin response model sessions 2026-04-25 03:02:30 +01:00
Mason Huang
5d4931cc3f CI: trim CodeQL JavaScript scope (#71347) 2026-04-25 09:57:12 +08:00
Peter Steinberger
5e640b93da fix(discord): preserve outbound reply threading 2026-04-25 02:54:37 +01:00
Shakker
6d271762ab feat: add modelCatalog manifest contract (#71342) (thanks @shakkernerd) 2026-04-25 02:54:32 +01:00
Shakker
1f4dab2c37 fix: tighten model catalog manifest normalization 2026-04-25 02:54:32 +01:00
Shakker
a5d46c4567 fix: require complete model catalog pricing tiers 2026-04-25 02:54:32 +01:00
Shakker
8fa1052838 fix: tighten model catalog manifest validation 2026-04-25 02:54:32 +01:00
Shakker
d39e89e6b0 docs: document model catalog manifest metadata 2026-04-25 02:54:32 +01:00
Shakker
5e715de6c5 test: preserve model catalog manifest metadata 2026-04-25 02:54:32 +01:00
Shakker
61fcbe7dce feat: add model catalog manifest contract 2026-04-25 02:54:32 +01:00
Gustavo Madeira Santana
fd65caf4b0 chore: tighten changelog unreleased detection 2026-04-24 21:52:00 -04:00
Peter Steinberger
2a0a76f876 fix(browser): extend existing-session manage timeouts 2026-04-25 02:50:36 +01:00
Peter Steinberger
2ec70e6770 fix(browser): recover stale chrome mcp sessions 2026-04-25 02:50:11 +01:00
Luka Dolenc
325e5e921f fix: preserve thread-bound subagent completion fallback
Preserve the requester-agent announce path for thread-bound subagent completions, while falling back to direct thread delivery only when the announce fails or produces no visible output.\n\nThanks @DolencLuka.
2026-04-25 02:49:50 +01:00
Peter Steinberger
5865197ec1 test: relax bundled channel Docker readiness 2026-04-25 02:48:02 +01:00
Peter Steinberger
1eef6df5f5 test(auto-reply): reset inbound dedupe for acp abort 2026-04-25 02:47:52 +01:00
Peter Steinberger
69c258f9dc test(auto-reply): keep hook runner mock aligned 2026-04-25 02:47:52 +01:00
Peter Steinberger
f70e439699 fix(amazon-bedrock): skip auto memory embeddings without credentials (#71245)
Co-authored-by: bitloi <raphaelaloi.eth@gmail.com>
2026-04-25 02:47:52 +01:00
723 changed files with 32995 additions and 4673 deletions

View File

@@ -40,6 +40,7 @@ jobs:
has_changed_extensions: ${{ steps.manifest.outputs.has_changed_extensions }}
changed_extensions_matrix: ${{ steps.manifest.outputs.changed_extensions_matrix }}
run_build_artifacts: ${{ steps.manifest.outputs.run_build_artifacts }}
run_checks_fast_core: ${{ steps.manifest.outputs.run_checks_fast_core }}
run_checks_fast: ${{ steps.manifest.outputs.run_checks_fast }}
checks_fast_core_matrix: ${{ steps.manifest.outputs.checks_fast_core_matrix }}
channel_contracts_matrix: ${{ steps.manifest.outputs.channel_contracts_matrix }}
@@ -130,6 +131,9 @@ jobs:
OPENCLAW_CI_RUN_MACOS: ${{ steps.changed_scope.outputs.run_macos || 'false' }}
OPENCLAW_CI_RUN_ANDROID: ${{ steps.changed_scope.outputs.run_android || 'false' }}
OPENCLAW_CI_RUN_WINDOWS: ${{ steps.changed_scope.outputs.run_windows || 'false' }}
OPENCLAW_CI_RUN_NODE_FAST_ONLY: ${{ steps.changed_scope.outputs.run_node_fast_only || 'false' }}
OPENCLAW_CI_RUN_NODE_FAST_PLUGIN_CONTRACTS: ${{ steps.changed_scope.outputs.run_node_fast_plugin_contracts || 'false' }}
OPENCLAW_CI_RUN_NODE_FAST_CI_ROUTING: ${{ steps.changed_scope.outputs.run_node_fast_ci_routing || 'false' }}
OPENCLAW_CI_RUN_SKILLS_PYTHON: ${{ steps.changed_scope.outputs.run_skills_python || 'false' }}
OPENCLAW_CI_RUN_CONTROL_UI_I18N: ${{ steps.changed_scope.outputs.run_control_ui_i18n || 'false' }}
OPENCLAW_CI_HAS_CHANGED_EXTENSIONS: ${{ steps.changed_extensions.outputs.has_changed_extensions || 'false' }}
@@ -173,12 +177,23 @@ jobs:
const docsOnly = parseBoolean(process.env.OPENCLAW_CI_DOCS_ONLY);
const docsChanged = parseBoolean(process.env.OPENCLAW_CI_DOCS_CHANGED);
const runNode = parseBoolean(process.env.OPENCLAW_CI_RUN_NODE) && !docsOnly;
const runNodeFastOnly =
runNode && parseBoolean(process.env.OPENCLAW_CI_RUN_NODE_FAST_ONLY);
const runNodeFull = runNode && !runNodeFastOnly;
const runNodeFastPluginContracts =
runNode && parseBoolean(process.env.OPENCLAW_CI_RUN_NODE_FAST_PLUGIN_CONTRACTS);
const runNodeFastCiRouting =
runNode && parseBoolean(process.env.OPENCLAW_CI_RUN_NODE_FAST_CI_ROUTING);
const runChecksFastCore = runNodeFull || runNodeFastPluginContracts || runNodeFastCiRouting;
const runMacos =
parseBoolean(process.env.OPENCLAW_CI_RUN_MACOS) && !docsOnly && isCanonicalRepository;
const runAndroid =
parseBoolean(process.env.OPENCLAW_CI_RUN_ANDROID) && !docsOnly && isCanonicalRepository;
const runWindows =
parseBoolean(process.env.OPENCLAW_CI_RUN_WINDOWS) && !docsOnly && isCanonicalRepository;
parseBoolean(process.env.OPENCLAW_CI_RUN_WINDOWS) &&
!docsOnly &&
!runNodeFastOnly &&
isCanonicalRepository;
const runSkillsPython = parseBoolean(process.env.OPENCLAW_CI_RUN_SKILLS_PYTHON) && !docsOnly;
const runControlUiI18n =
parseBoolean(process.env.OPENCLAW_CI_RUN_CONTROL_UI_I18N) && !docsOnly;
@@ -191,7 +206,7 @@ jobs:
? DEFAULT_EXTENSION_TEST_SHARD_COUNT
: Math.max(DEFAULT_EXTENSION_TEST_SHARD_COUNT, 36);
const extensionShardMatrix = createMatrix(
runNode
runNodeFull
? createExtensionTestShards({
shardCount: extensionTestShardCount,
}).map((shard) => ({
@@ -207,7 +222,33 @@ jobs:
}))
: [],
);
const nodeTestShards = runNode
const checksFastCoreTasks = [];
if (runNodeFull) {
checksFastCoreTasks.push(
{ check_name: "checks-fast-bundled", runtime: "node", task: "bundled" },
{
check_name: "checks-fast-contracts-plugins",
runtime: "node",
task: "contracts-plugins",
},
);
} else {
if (runNodeFastPluginContracts) {
checksFastCoreTasks.push({
check_name: "checks-fast-contracts-plugins",
runtime: "node",
task: runNodeFastCiRouting ? "contracts-plugins-ci-routing" : "contracts-plugins",
});
} else if (runNodeFastCiRouting) {
checksFastCoreTasks.push({
check_name: "checks-fast-ci-routing",
runtime: "node",
task: "ci-routing",
});
}
}
const nodeTestShards = runNodeFull
? createNodeTestShards().map((shard) => ({
check_name: shard.checkName,
runtime: "node",
@@ -232,25 +273,17 @@ jobs:
run_windows: runWindows,
has_changed_extensions: hasChangedExtensions,
changed_extensions_matrix: changedExtensionsMatrix,
run_build_artifacts: runNode,
run_checks_fast: runNode,
checks_fast_core_matrix: createMatrix(
runNode
? [
{ check_name: "checks-fast-bundled", runtime: "node", task: "bundled" },
{
check_name: "checks-fast-contracts-plugins",
runtime: "node",
task: "contracts-plugins",
},
]
: [],
run_build_artifacts: runNodeFull,
run_checks_fast_core: runChecksFastCore,
run_checks_fast: runNodeFull,
checks_fast_core_matrix: createMatrix(checksFastCoreTasks),
channel_contracts_matrix: createMatrix(
runNodeFull ? createChannelContractTestShards() : [],
),
channel_contracts_matrix: createMatrix(runNode ? createChannelContractTestShards() : []),
checks_node_extensions_matrix: extensionShardMatrix,
run_checks: runNode,
run_checks: runNodeFull,
checks_matrix: createMatrix(
runNode
runNodeFull
? [
{ check_name: "checks-node-channels", runtime: "node", task: "channels" },
]
@@ -269,9 +302,9 @@ jobs:
}))
: [],
),
run_check: runNode,
run_check_additional: runNode,
run_build_smoke: runNode,
run_check: runNodeFull,
run_check_additional: runNodeFull,
run_build_smoke: runNodeFull,
run_check_docs: docsChanged,
run_control_ui_i18n: runControlUiI18n,
run_skills_python_job: runSkillsPython,
@@ -662,7 +695,7 @@ jobs:
contents: read
name: ${{ matrix.check_name }}
needs: [preflight]
if: needs.preflight.outputs.run_checks_fast == 'true'
if: needs.preflight.outputs.run_checks_fast_core == 'true'
runs-on: ${{ github.repository == 'openclaw/openclaw' && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-24.04' }}
timeout-minutes: 60
strategy:
@@ -739,6 +772,13 @@ jobs:
contracts-plugins)
pnpm test:contracts:plugins
;;
contracts-plugins-ci-routing)
pnpm test:contracts:plugins
pnpm test src/commands/status.scan-result.test.ts src/scripts/ci-changed-scope.test.ts test/scripts/test-projects.test.ts
;;
ci-routing)
pnpm test src/commands/status.scan-result.test.ts src/scripts/ci-changed-scope.test.ts test/scripts/test-projects.test.ts
;;
*)
echo "Unsupported checks-fast task: $TASK" >&2
exit 1
@@ -1044,7 +1084,7 @@ jobs:
contents: read
name: checks-node-compat-node22
needs: [preflight]
if: needs.preflight.outputs.run_node == 'true' && github.event_name == 'push'
if: needs.preflight.outputs.run_build_artifacts == 'true' && github.event_name == 'push'
runs-on: ${{ github.repository == 'openclaw/openclaw' && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-24.04' }}
timeout-minutes: 60
steps:
@@ -1587,7 +1627,7 @@ jobs:
packages/plugin-sdk/dist
extensions/*/dist/.boundary-tsc.tsbuildinfo
extensions/*/dist/.boundary-tsc.stamp
key: ${{ runner.os }}-extension-package-boundary-v1-${{ hashFiles('tsconfig.json', 'tsconfig.plugin-sdk.dts.json', 'packages/plugin-sdk/tsconfig.json', 'scripts/check-extension-package-tsc-boundary.mjs', 'scripts/prepare-extension-package-boundary-artifacts.mjs', 'scripts/write-plugin-sdk-entry-dts.ts', 'scripts/lib/plugin-sdk-entrypoints.json', 'scripts/lib/plugin-sdk-entries.mjs', 'src/plugin-sdk/**', 'src/video-generation/dashscope-compatible.ts', 'src/video-generation/types.ts', 'src/types/**', 'extensions/**', 'extensions/tsconfig.package-boundary*.json', 'package.json', 'pnpm-lock.yaml') }}
key: ${{ runner.os }}-extension-package-boundary-v1-${{ hashFiles('tsconfig.json', 'tsconfig.plugin-sdk.dts.json', 'packages/plugin-sdk/tsconfig.json', 'scripts/check-extension-package-tsc-boundary.mjs', 'scripts/prepare-extension-package-boundary-artifacts.mjs', 'scripts/write-plugin-sdk-entry-dts.ts', 'scripts/lib/plugin-sdk-entrypoints.json', 'scripts/lib/plugin-sdk-entries.mjs', 'src/plugin-sdk/**', 'src/auto-reply/**', 'src/video-generation/dashscope-compatible.ts', 'src/video-generation/types.ts', 'src/types/**', 'extensions/**', 'extensions/tsconfig.package-boundary*.json', 'package.json', 'pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-extension-package-boundary-v1-

View File

@@ -349,4 +349,5 @@ jobs:
- name: Run fast bundled plugin Docker E2E
env:
OPENCLAW_BUNDLED_CHANNEL_DEPS_E2E_IMAGE: openclaw-bundled-channel-fast:local
run: timeout 120s pnpm test:docker:bundled-channel-deps:fast
OPENCLAW_BUNDLED_CHANNEL_DOCKER_RUN_TIMEOUT: 90s
run: timeout 240s pnpm test:docker:bundled-channel-deps:fast

View File

@@ -432,24 +432,35 @@ jobs:
OPENCLAW_DISCORD_SMOKE_CHANNEL_ID: ${{ secrets.OPENCLAW_DISCORD_SMOKE_CHANNEL_ID }}
OPENCLAW_RELEASE_CHECK_OS: ${{ matrix.os_id }}
OPENCLAW_RELEASE_CHECK_RUNNER: ${{ matrix.runner }}
CANDIDATE_TGZ: ${{ runner.temp }}/openclaw-cross-os-release-checks/candidate/${{ needs.prepare.outputs.candidate_file_name }}
CANDIDATE_VERSION: ${{ needs.prepare.outputs.candidate_version }}
SOURCE_SHA: ${{ needs.prepare.outputs.source_sha }}
BASELINE_SPEC: ${{ needs.prepare.outputs.baseline_spec }}
PREVIOUS_VERSION: ${{ inputs.previous_version }}
BASELINE_TGZ: ${{ runner.temp }}/openclaw-cross-os-release-checks/baseline/${{ needs.prepare.outputs.baseline_file_name }}
PROVIDER: ${{ inputs.provider }}
MODE: ${{ matrix.lane }}
SUITE: ${{ matrix.suite }}
REF: ${{ inputs.ref }}
OUTPUT_DIR: ${{ runner.temp }}/openclaw-cross-os-release-checks/${{ matrix.artifact_name }}-${{ matrix.suite }}
run: |
DISCORD_ARGS=()
if [[ -n "${OPENCLAW_DISCORD_SMOKE_BOT_TOKEN}" ]] && [[ -n "${OPENCLAW_DISCORD_SMOKE_GUILD_ID}" ]] && [[ -n "${OPENCLAW_DISCORD_SMOKE_CHANNEL_ID}" ]]; then
DISCORD_ARGS+=(--run-discord-roundtrip true)
fi
pnpm dlx "tsx@${TSX_VERSION}" workflow/scripts/openclaw-cross-os-release-checks.ts \
--candidate-tgz "$RUNNER_TEMP/openclaw-cross-os-release-checks/candidate/${{ needs.prepare.outputs.candidate_file_name }}" \
--candidate-version "${{ needs.prepare.outputs.candidate_version }}" \
--source-sha "${{ needs.prepare.outputs.source_sha }}" \
--baseline-spec "${{ needs.prepare.outputs.baseline_spec }}" \
--previous-version "${{ inputs.previous_version }}" \
--baseline-tgz "$RUNNER_TEMP/openclaw-cross-os-release-checks/baseline/${{ needs.prepare.outputs.baseline_file_name }}" \
--provider "${{ inputs.provider }}" \
--mode "${{ matrix.lane }}" \
--suite "${{ matrix.suite }}" \
--ref "${{ inputs.ref }}" \
--candidate-tgz "${CANDIDATE_TGZ}" \
--candidate-version "${CANDIDATE_VERSION}" \
--source-sha "${SOURCE_SHA}" \
--baseline-spec "${BASELINE_SPEC}" \
--previous-version "${PREVIOUS_VERSION}" \
--baseline-tgz "${BASELINE_TGZ}" \
--provider "${PROVIDER}" \
--mode "${MODE}" \
--suite "${SUITE}" \
--ref "${REF}" \
"${DISCORD_ARGS[@]}" \
--output-dir "$RUNNER_TEMP/openclaw-cross-os-release-checks/${{ matrix.artifact_name }}-${{ matrix.suite }}"
--output-dir "${OUTPUT_DIR}"
- name: Summarize release checks
if: always()

View File

@@ -2,6 +2,21 @@
Docs: https://docs.openclaw.ai
## Unreleased
### Changes
- Diagnostics/OTEL: support `OPENCLAW_OTEL_PRELOADED=1` so the plugin can reuse an already-registered OpenTelemetry SDK while keeping OpenClaw diagnostic listeners wired. (#70424) Thanks @jlapenna.
- Control UI: refine the agent Tool Access panel with compact live-tool chips, collapsible tool groups, direct per-tool toggles, and clearer runtime/source provenance. (#71405) Thanks @BunsDev.
- Memory-core/hybrid search: expose raw `vectorScore` and `textScore` alongside the combined `score` on hybrid memory search results, so callers can inspect vector-versus-text retrieval contribution before temporal decay or MMR reordering. Fixes #68166. (#68286) Thanks @ajfonthemove.
### Fixes
- MCP: retire one-shot embedded bundled MCP runtimes at run end, skip bundle-MCP startup when a runtime tool allowlist cannot reach bundle-MCP tools, and add `mcp.sessionIdleTtlMs` idle eviction for leaked session runtimes. Fixes #71106 and #71110.
- Gateway/restart continuation: durably hand restart continuations to a session-delivery queue before deleting the restart sentinel, recover queued continuation work after crashy restarts, and fall back to a session-only wake when no channel route survives reboot. (#70780) Thanks @fuller-stack-dev.
- Agents/tool-result pruning: harden the tool-result character estimator and context-pruning loops against malformed `{ type: "text" }` blocks created by void or undefined tool handler results, serializing non-string text payloads for size accounting so they cannot bypass trimming as zero-sized. Fixes #34979. (#51267) Thanks @cgdusek.
- Daemon/service-env: add Nix Home Manager profile bin directories to generated gateway service PATHs on macOS and Linux, honoring `NIX_PROFILES` right-to-left precedence and falling back to `~/.nix-profile/bin` when unset. Fixes #44402. (#59935) Thanks @jerome-benoit.
## 2026.4.24 (Unreleased)
### Breaking
@@ -10,6 +25,8 @@ Docs: https://docs.openclaw.ai
### Changes
- Gateway/nodes: add disabled-by-default `gateway.nodes.pairing.autoApproveCidrs` for first-time node pairing from explicit trusted CIDRs, while keeping operator/browser pairing and all upgrade flows manual. Fixes #60800. Thanks @sahilsatralkar.
- Browser: add viewport coordinate clicks for managed and existing-session automation, plus `openclaw browser click-coords` for CLI use. (#54452) Thanks @dluttz.
- Browser/config: support per-profile `browser.profiles.<name>.headless` overrides for locally launched browser profiles, so one profile can run headless without forcing all browser profiles headless. Thanks @nakamotoliu.
- Plugins/PDF: move local PDF extraction into a bundled `document-extract` plugin so core no longer owns `pdfjs-dist` or PDF image-rendering dependencies. Thanks @vincentkoc.
- Matrix: require full cross-signing identity trust for self-device verification and add `openclaw matrix verify self` so operators can establish that trust from the CLI. (#70401) Thanks @gumadeiras.
@@ -21,6 +38,9 @@ Docs: https://docs.openclaw.ai
- Plugins/setup: include `setup.providers[].envVars` in generic provider auth/env lookups and warn non-bundled plugins that still rely on deprecated `providerAuthEnvVars` compatibility metadata. Thanks @vincentkoc.
- Plugins/setup: derive generic provider setup choices from descriptor-safe `setup.providers[].authMethods` before falling back to setup runtime. Thanks @vincentkoc.
- Plugins/setup: surface manifest provider auth choices directly in provider setup flow before falling back to setup runtime or install-catalog choices. Thanks @vincentkoc.
- Models/catalog: add a manifest catalog planner that produces stable manifest-sourced model rows and reports duplicate provider/model conflicts without loading provider runtime. (#71368) Thanks @shakkernerd.
- Models/catalog: centralize manifest model catalog normalization behind a shared `src/model-catalog` contract so future provider index, cache, onboarding, and listing consumers reuse the same validation and row refs. (#71360) Thanks @shakkernerd.
- Plugins/manifest: add a `modelCatalog` contract for provider-owned model rows, aliases, suppression rules, and discovery mode metadata without loading plugin runtime. (#71342) Thanks @shakkernerd.
- Plugins/setup: warn when descriptor-only setup plugins still ship ignored setup runtime entries, keeping `setup.requiresRuntime: false` semantics explicit without breaking existing metadata. Thanks @vincentkoc.
- Plugins/channels: use manifest `channelConfigs` for read-only external channel discovery when no setup entry is available or setup descriptors declare runtime unnecessary. Thanks @vincentkoc.
- TUI/dependencies: remove direct `cli-highlight` usage from the OpenClaw TUI code-block renderer, keeping themed code coloring without the extra root dependency. Thanks @vincentkoc.
@@ -54,17 +74,116 @@ Docs: https://docs.openclaw.ai
- Plugins/Google Meet: add a bundled participant plugin with personal Google auth, explicit meeting URL joins, Chrome and Twilio transports, and realtime voice support. (#70765) Thanks @steipete.
- Plugins/Google Meet: default Chrome realtime sessions to OpenAI plus SoX `rec`/`play` audio bridge commands, so the usual setup only needs the plugin enabled and `OPENAI_API_KEY`. Thanks @steipete.
- Plugins/Google Meet: add a `chrome-node` transport so a paired macOS node, such as a Parallels VM, can own Chrome, BlackHole, and SoX while the Gateway machine keeps the agent and model key. Thanks @steipete.
- Plugins/Google Meet: add `googlemeet artifacts` and `googlemeet attendance` commands plus matching tool/gateway actions for conference records, recordings, transcripts and transcript entries, smart notes, and participant sessions. Thanks @steipete.
- Plugins/Google Meet: add markdown and file output for `googlemeet artifacts` and `googlemeet attendance` reports. Thanks @steipete.
- Plugins/Google Meet: add `googlemeet doctor --oauth` so operators can verify OAuth token refresh, Meet space reads, and side-effecting space creation without printing secrets. Thanks @steipete.
- Plugins/Voice Call: expose the shared `openclaw_agent_consult` realtime tool so live phone calls can ask the full OpenClaw agent for deeper/tool-backed answers. Thanks @steipete.
- Plugins/Voice Call: add `voicecall setup` and a dry-run-by-default `voicecall smoke` command so Twilio/provider readiness can be checked before placing a live test call. Thanks @steipete.
- Plugins/Google Meet: add `googlemeet doctor` and a `recover_current_tab`/`recover-tab` flow so agents can inspect an already-open Meet tab and report the blocker without opening another window. Thanks @steipete.
- Plugins/Bonjour: move LAN Gateway discovery advertising into a default-enabled bundled plugin with its own `@homebridge/ciao` dependency, so users can disable Bonjour without cutting wide-area discovery. Thanks @vincentkoc.
- Providers/Google: add a Gemini Live realtime voice provider for backend Voice Call and Google Meet audio bridges, with bidirectional audio and function-call support. Thanks @steipete.
- Providers/Google: let Gemini TTS prepend configured `audioProfile` and `speakerName` prompt text for reusable speech style control. Thanks @tdack.
- Plugins/Google Meet: let realtime Meet sessions consult the full OpenClaw agent for deeper answers while staying in the live voice loop. Thanks @steipete.
- Gateway/VoiceClaw: add a realtime brain WebSocket endpoint backed by Gemini Live, with owner-auth gating and async OpenClaw tool handoff. (#70938) Thanks @yagudaev.
- Providers/DeepSeek: add DeepSeek V4 Flash and V4 Pro to the bundled catalog and make V4 Flash the onboarding default. Thanks @lsdsjy.
- CLI/Gateway: make `gateway status` start faster by skipping plugin loading on the read-only status path. (#71364) Thanks @andyylin.
- Plugins/compatibility: add a central plugin compatibility registry and docs for SDK/config/setup/runtime deprecation records, including dated migration metadata for legacy harness naming and other plugin-facing aliases. Thanks @vincentkoc.
- Agents/bootstrap: add `agents.defaults.contextInjection: "never"` to disable workspace bootstrap file injection for agents that fully own their prompt lifecycle. (#65006) Thanks @xDarkicex.
### Fixes
- Feishu: back off streaming-card creation after HTTP 400 startup failures, so unsupported card setups fall back without delaying every message. Fixes #56981. Thanks @JinnanDuan.
- Feishu/topic groups: key native Feishu/Lark topic-group sessions by `thread_id` so starter messages and replies with different `root_id` formats stay in the same `group_topic` conversation. Fixes #71438. Thanks @1335848090.
- Feishu: suppress duplicate final card delivery when idle closes a streaming card before the final payload arrives. (#68491) Thanks @MoerAI.
- Signal: preserve sender attachment filenames and resolve missing MIME types from those filenames, so Linux `signal-cli` voice notes without `contentType` still enter audio transcription. Fixes #48614. Thanks @mindfury.
- Telegram/agents: suppress the phantom "Agent couldn't generate a response" fallback after a reply was already committed through the messaging tool. (#70623) Thanks @chinar-amrutkar.
- Models/CLI: show provider runtime `contextTokens` beside native `contextWindow` in `openclaw models list`, and align `openai-codex/gpt-5.5` with Codex's 272K runtime cap plus 400K native window. Fixes #71403.
- Dashboard/security: avoid writing tokenized Control UI URLs or SSH hints to runtime logs, keeping gateway bearer fragments out of console-captured logs readable through `logs.tail`. (#70029) Thanks @Ziy1-Tan.
- Providers/OpenRouter: treat DeepSeek refs as cache-TTL eligible without injecting Anthropic cache-control markers, aligning context pruning with OpenRouter-managed prompt caching. (#51983) Thanks @QuinnH496.
- Control UI/browser: defer temp-dir access-mode constants until Node-only temp-dir resolution runs, preventing browser bundles from crashing when `node:fs` constants are stubbed. (#48930) Thanks @Valentinws.
- Discord/cron: deliver text-only isolated cron and heartbeat announce output from the canonical final assistant text once, avoiding duplicate Discord posts when streamed block payloads and the final answer contain the same content. Fixes #71406. Thanks @alexgross21.
- macOS Gateway: wait for launchd to reload the exited Gateway LaunchAgent before bootstrapping repair fallback, preventing config-triggered restarts from leaving the service not loaded. Fixes #45178. Thanks @vincentkoc.
- macOS Gateway: tolerate launchctl bootstrap's already-loaded exit during restart fallback and use non-killing kickstart after bootstrap, avoiding a second race that can unload the LaunchAgent. Fixes #41934. Thanks @zerone0x.
- macOS Gateway: rewrite stale LaunchAgent plists before restart fallback bootstrap, matching install repair behavior when `gateway restart` has to re-register launchd. Thanks @maybegeeker.
- TTS/hooks: preserve audio-only TTS transcripts for `message_sending` and `message_sent` hooks without rendering the transcript as a media caption. Thanks @zqchris.
- WhatsApp/TTS: preserve `audioAsVoice` through shared media payload sends and the WhatsApp outbound adapter, so `[[audio_as_voice]]` reply payloads keep their voice-note intent when routed through `sendPayload`. Fixes #66053. Thanks @masatohoshino.
- Control UI/WebChat: hide heartbeat prompts, `HEARTBEAT_OK` acknowledgments, and internal-only runtime context turns from visible chat history while leaving the underlying transcript intact. Fixes #71381. Thanks @gerald1950ggg-ai.
- Control UI/chat: keep optimistic user and assistant tail messages visible when a final history refresh briefly returns an older snapshot, preventing message cards from flash-disappearing until the next refresh. Fixes #71371. Thanks @WolvenRA.
- Talk/TTS: resolve configured extension speech providers from the active runtime registry before provider-list discovery, so Talk mode no longer rejects valid plugin speech providers as unsupported.
- Sessions/subagents: stop stale ended runs and old store-only child reverse links from reappearing in `childSessions`, while keeping live descendants and recently-ended children visible. Fixes #57920.
- Subagents: stop stale unended runs from counting as active or pending forever, while preserving restart-aborted recovery for recoverable child sessions. Fixes #71252. Thanks @hclsys.
- Gateway/tools: allow `POST /tools/invoke` to reach plugin-backed catalog tools such as `browser` when no core implementation exists, while still preferring built-in tools for real core names. Thanks @chat2way.
- Browser/security: require `operator.admin` for the `browser.request` gateway method, matching the host/browser-node control authority exposed by that route. Thanks @RichardCao.
- Browser/profiles: allow local managed profiles to override `browser.executablePath`, so different profiles can launch different Chromium-based browsers. Thanks @nobrainer-tech.
- Agents/replay: repair displaced or missing tool results before strict provider replay, use Codex-compatible `aborted` outputs for OpenAI Responses history, and drop partial aborted/error transport turns before retries.
- Browser/startup: deduplicate concurrent lazy-start calls per profile so simultaneous browser tool requests no longer race into duplicate Chrome launches and `PortInUseError`. (#61772) Thanks @sukhdeepjohar.
- Browser/profiles: recover from stale Chromium `Singleton*` profile locks after crashes or host moves by clearing dead/foreign locks and retrying launch once. Thanks @seanc-dev.
- Browser/existing-session: keep Chrome MCP status probes transport-only and ephemeral, and retry stale cached Playwright attaches once so idle profile checks no longer poison the next real attach. (#57245) Thanks @josephbergvinson.
- Cron/exec: suppress automatic background exec completion wakes only for silent cron jobs with `delivery.mode="none"` while keeping webhook and announce runs observable. (#71391) Thanks @goldmar.
- Reply media: allow sandboxed replies to deliver OpenClaw-managed `media/outbound` and `media/tool-*` attachments without treating them as sandbox escapes, while keeping alias-escape checks on the managed media root. Fixes #71138. Thanks @mayor686, @truffle-dev, and @neeravmakwana.
- CLI/agent: keep `openclaw agent --json` stdout reserved for the JSON response by routing gateway, plugin, and embedded-fallback diagnostics to stderr before execution starts. Fixes #71319.
- Agents/Gemini: retry reasoning-only, empty, and planning-only Gemini turns instead of letting sessions silently stall. Fixes #71074. (#71362) Thanks @neeravmakwana.
- Providers/DeepSeek: add missing `reasoning_content` placeholders for replayed assistant tool-call turns when DeepSeek V4 thinking is enabled, so switching an existing session to `deepseek-v4-flash` or `deepseek-v4-pro` no longer trips the provider's 400 replay check. Fixes #71372. Thanks @yangyang1719.
- Exec approvals: allow bare command-name allowlist patterns to match PATH-resolved executable basenames without trusting `./tool` or absolute path-selected binaries. Fixes #71315. Thanks @chen-zhang-cs-code and @dengluozhang.
- Config/recovery: skip whole-file last-known-good rollback when invalidity is scoped to `plugins.entries.*`, preserving unrelated user settings during plugin schema or host-version skew. Fixes #71289. Thanks @jalehman.
- Agents/tools: keep resolved reply-run configs from being overwritten by stale runtime snapshots, and let empty web runtime metadata fall back to configured provider auto-detection so standard and queued turns expose the same tool set. Fixes #71355. Thanks @c-g14.
- Agents/TTS: pass the resolved shared config into the `tts` tool, so tool-triggered speech uses configured providers and voices instead of falling back to a fresh config load.
- Reply media: strip `MEDIA:` attachments from final replies when the same media already went out through block streaming, preventing duplicate Telegram voice notes and files. Fixes #65468. Thanks @aurora-openclaw.
- Agents/TTS: preserve voice media when a tool-generated reply is paired with an exact `NO_REPLY` sentinel, stripping the sentinel text instead of dropping the audio payload. Fixes #66092.
- Compaction: honor explicit `agents.defaults.compaction.keepRecentTokens` for manual `/compact`, re-distill safeguard summaries instead of snowballing previous summaries, and enable safeguard summary quality checks by default. Fixes #71357. Thanks @WhiteGiverMa.
- Sessions: honor configured `session.maintenance` settings during load-time maintenance instead of falling back to default entry caps. Fixes #71356. Thanks @comolago.
- Browser/sandbox: pass the resolved `browser.ssrfPolicy` into sandbox browser bridges and refresh cached bridges when the effective policy changes, so sandboxed browser navigation honors private-network opt-ins. Fixes #45153 and #57055. Thanks @jzakirov, @zuoanCo, and @kybrcore.
- Browser/proxy: keep Gateway/provider proxy environment variables from proxying the OpenClaw-managed browser, so `HTTP_PROXY` and `HTTPS_PROXY` no longer block ordinary browser navigation. Fixes #71358. Thanks @Sanjays2402.
- Agents/MCP: validate draft-2020-12 MCP tool output schemas with a draft-aware bundle-MCP client validator, so external MCP servers no longer fail catalog/tool execution with missing schema refs. Fixes #68772 and #70196. Thanks @mwiesen.
- Dashboard/Windows: open Control UI and OAuth URLs through the system URL handler without `cmd.exe` parsing or PATH-based `rundll32` lookup, and reject non-HTTP browser-open inputs. Fixes #71098. Thanks @Sanjays2402.
- Config/doctor: reject legacy `secretref-env:<ENV_VAR>` marker strings on SecretRef credential paths and migrate valid markers to structured env SecretRefs with `openclaw doctor --fix`. Fixes #51794. Thanks @halointellicore.
- Plugin SDK/browser: export the resolved browser tab-cleanup config type through the browser profile facade, keeping SDK subpath contracts aligned.
- Providers/OpenAI: separate API-key and Codex sign-in onboarding groups, and avoid replaying stale OpenAI Responses reasoning blocks after a model route switch.
- Providers/OpenAI-compatible: forward `prompt_cache_key` on Completions requests only for providers that opt in with `compat.supportsPromptCacheKey`, keeping default proxy payloads unchanged. Fixes #69272.
- Providers/OpenAI-compatible: skip null or non-object streaming chunks from custom providers instead of failing the turn after partial output. Fixes #51112.
- Providers/OpenAI-compatible: treat singular MLX-style `finish_reason: "tool_call"` as tool use instead of a provider error. Fixes #61499.
- Docs/TTS: clarify that legacy flat TTS provider config blocks are repaired by `openclaw doctor --fix`, not accepted by strict runtime schema on load. Fixes #56220.
- Plugins/OpenCode: strip unsupported disabled Responses reasoning payloads for OpenCode image understanding. Fixes #70252.
- Plugins/OpenCode/OpenCode Go: register image understanding metadata so the image tool is available for OpenCode catalog models with vision support. Fixes #70482 and #61789.
- Plugins/OpenCode Go: update the default Go catalog model to `opencode-go/kimi-k2.6`. Thanks @masrlinu.
- Providers/ElevenLabs: omit the MP3-only `Accept` header for PCM telephony synthesis, so Voice Call requests for `pcm_22050` no longer receive MP3 audio. Fixes #67340. Thanks @marcchabot.
- Providers/MiniMax TTS: truncate fractional pitch overrides before sending T2A requests, matching MiniMax's integer pitch contract while preserving fractional speed and volume. Fixes #62144.
- Providers/MiniMax TTS: transcode voice-note targets to Opus so Feishu/Telegram receive native voice messages instead of MP3 file attachments. Fixes #63540, #64134, and #70445.
- Providers/Microsoft TTS: keep allowlisted bundled speech providers discoverable even when another speech plugin has already registered, so Edge/Microsoft TTS is available alongside OpenAI. Fixes #62117 and #66850.
- Providers/Microsoft TTS: honor legacy `messages.tts.providers.edge` voice settings after normalizing Edge TTS to the Microsoft provider. Fixes #64153.
- Providers/OpenRouter: add an OpenRouter TTS provider using the OpenAI-compatible `/audio/speech` endpoint and `OPENROUTER_API_KEY`. Fixes #71268.
- macOS Talk Mode: retry failed local ElevenLabs stream playback through gateway `talk.speak` before falling back to the system voice, so configured ElevenLabs voices still play when streaming playback fails. Fixes #65662.
- Plugins/Voice Call: reap stale pre-answer calls by default, honor configured TTS timeouts for Twilio media-stream playback, and fail empty telephony audio instead of completing as silence. Fixes #42071; supersedes #60957. Thanks @Ryce and @sliekens.
- Plugins/Voice Call: fail fast when Twilio, Telnyx, or Plivo would fall back to a loopback/private webhook URL, so calls do not start with an unreachable callback endpoint. Thanks @artemgetmann.
- Plugins/Voice Call: resolve queued-but-not-yet-playing Twilio TTS entries when barge-in or stream teardown clears the playback queue, so callers awaiting `queueTts()` do not hang. Thanks @kevinWangSheng.
- Plugins/Voice Call: terminate expired restored call sessions with the provider and restart restored max-duration timers with only the remaining duration, preventing stale outbound retry loops after Gateway restarts. Fixes #48739. Thanks @mira-solari.
- Plugins/Voice Call: start provider STT after Telnyx outbound conversation greetings and pass configured Telnyx voice IDs through to the speak action. Fixes #56091. Thanks @Roshan.
- Skills: honor legacy `metadata.clawdbot` requirements and installer hints when `metadata.openclaw` is absent, so older skills no longer appear ready when required binaries are missing. Fixes #71323. Thanks @chen-zhang-cs-code.
- Browser/config: expand `~` in `browser.executablePath` before Chromium launch, so home-relative custom browser paths no longer fail with `ENOENT`. Fixes #67264. Thanks @Quratulain-bilal.
- Channels/streaming: keep Telegram tool-progress preview updates enabled by default to match released behavior, document `streaming.preview.toolProgress: false` for disabling only those status lines, and prevent preview progress text from triggering Telegram Markdown links, Discord mentions, or Slack mrkdwn mentions. Fixes #71320. Thanks @neeravmakwana.
- Gateway/sessions: copy the oversized `sessions.json` to a rotation backup before the atomic rewrite instead of renaming the live store away, so a crash during rotation keeps the existing session-to-transcript mapping authoritative. Fixes #68229. Thanks @jjjojoj.
- Providers/OpenAI-compatible: strip OpenAI-only Completions `store` from proxy payloads and allow `extra_body`/`extraBody` passthrough params for provider-specific request fields. Fixes #61826 and #69717.
- Discord/subagents: preserve thread-bound completion delivery by keeping the requester-agent announce path primary and falling back to direct thread sends only when the announce produces no visible output. (#71064) Thanks @DolencLuka.
- Discord/proxy: serialize proxied multipart attachment uploads with undici `FormData`, so Discord media sends work through configured REST proxies. (#71383) Thanks @TC500.
- Browser/tool: give Chrome MCP existing-session manage calls a longer default timeout, pass explicit tool timeouts through tab management, and recover stale selected-page MCP sessions instead of forcing a manual reset. Thanks @steipete.
- Browser/sandbox: clean up idle tracked tabs opened by primary-agent browser sessions, while preserving active tab reuse and lifecycle cleanup for subagents, cron, and ACP sessions. Fixes #71165. Thanks @dwbutler.
- Plugins/Voice Call: reuse the webhook runtime across in-process plugin contexts, avoiding `EADDRINUSE` when agent tools or CLI commands run while the Gateway already owns the voice webhook port. Fixes #58115. Thanks @sfbrian.
- Plugins/Voice Call: answer accepted Telnyx inbound Call Control legs on `call.initiated`, so webhooks that reach OpenClaw no longer leave the caller ringing until hangup. Fixes #58231 and #40131. Thanks @KonsultDigital.
- Plugins/Voice Call: coalesce concurrent webhook server starts on the same runtime instance, avoiding a second `listen()` bind when overlapping startup paths race. Thanks @education-01.
- Plugins/Voice Call: pin voice response sessions to `responseModel` before embedded agent runs, avoiding live-session model switch failures when the global default model differs. Fixes #60118. Thanks @xinbenlv.
- Plugins/Voice Call: add `agentId` for voice response generation, so phone calls can use a dedicated agent workspace instead of always routing through `main`. Fixes #42155. Thanks @TheOpie.
- Plugins/Voice Call: scope embedded voice response sandbox resolution to the selected voice agent, so implicit `main` voice sessions respect `agents.defaults.sandbox.mode: "off"` even when other agents define sandboxed Docker binds. Fixes #56367. Thanks @crpol.
- Media tools: honor the configured web-fetch SSRF policy for media understanding, image/music/video generation references, and PDF inputs, so explicit RFC2544 opt-ins cover WebChat OSS uploads without weakening defaults. Fixes #71300. (#71321) Thanks @neeravmakwana.
- Agents/TTS: suppress successful spoken transcripts from verbose chat tool output when structured voice media is already queued, while preserving text output for non-builtin tool-name collisions. Fixes #71282. Thanks @neeravmakwana.
- Plugins/Google Meet: reuse existing Meet tabs and active sessions across harmless URL query differences, avoiding duplicate Chrome windows when agents retry a join. Thanks @steipete.
- Plugins/Google Meet: tell agents to recover already-open Meet tabs after browser timeouts, and make the dev CLI release its build lock if compiler spawning fails. Thanks @steipete.
- Plugins/Google Meet: return structured manual-action details when browser-based meeting creation needs login or permissions, so agents can guide the operator without opening duplicate Meet tabs. Thanks @steipete.
- Plugins/CLI: provide Gateway-backed node inspection to plugin commands, so `googlemeet recover-tab` can inspect paired browser nodes from the terminal. Thanks @steipete.
- Cron/isolated sessions: clear stale runtime, lifecycle, auth, model, exec, heartbeat, usage, privilege, routing, and delivery artifacts when creating a fresh isolated run, and persist per-run session rows as snapshots so old base-session state no longer leaks into new cron executions. Thanks @vincentkoc.
- Gateway/sessions: recover main-agent turns interrupted by a gateway restart from stale transcript-lock evidence, avoiding stuck `status: "running"` sessions without broad post-boot transcript scans. Fixes #70555. Thanks @bitloi.
- Codex approvals: sanitize MCP elicitation approval titles, descriptions, and display parameters before forwarding them to OpenClaw approval prompts. (#71343) Thanks @Lucenx9.
- Codex approvals: keep command approval responses within Codex app-server `availableDecisions`, including deny/cancel fallbacks for prompts that do not offer `decline`. (#71338) Thanks @Lucenx9.
- Codex harness: reject same-thread app-server notifications without `turnId` or `turn.id` after a bound turn starts, preventing unscoped events from mutating or completing the active reply. (#71317) Thanks @Lucenx9.
- Plugins/Google Meet: include live Chrome-node readiness in `googlemeet setup` and document the Parallels recovery checks, so stale node tokens or disconnected VM browsers are visible before an agent opens a meeting. Thanks @steipete.
- Context engine: keep safeguard compaction checks active after context-engine windowing and for `ownsCompaction` engines, so large transcripts can compact before prompt submission instead of waiting for provider overflow. Fixes #71325. Thanks @steipete.
- Approvals: compact structured home-directory paths to `~` across Codex permission prompts and exec approval metadata without repeating them as a separate high-risk warning, while preserving filesystem root and wildcard host warnings. Thanks @steipete.
@@ -74,6 +193,7 @@ Docs: https://docs.openclaw.ai
- Telegram/model picker: show configured model display names when browsing models through provider buttons, matching typed `/models <provider>` output. Fixes #70560. (#71016) Thanks @iskim77.
- Plugins/runtime deps: stage bundled plugin runtime dependencies for packaged/global installs in an external runtime root and retain already staged deps across repairs, avoiding package-tree update races and npm pruning after upgrades. Thanks @steipete.
- Plugins/runtime deps: log bundled plugin runtime-dependency staging before synchronous npm installs start and include elapsed timing afterward, so first boot after upgrades no longer looks hung while dependencies are being repaired. Thanks @steipete.
- Memory/Bedrock: skip Bedrock during automatic memory embedding selection when AWS credentials are unavailable, so `memory_search` can fall back to lexical search instead of failing on the first embed call. Fixes #71143 via #71245. Thanks @bitloi.
- Agents/failover: forward embedded run abort signals into provider-owned model streams, cap implicit LLM idle watchdogs below long run timeouts, and mark 429 responses without usable retry timing as non-retryable so GitHub Copilot rate limits fail over or surface promptly instead of hanging until run timeout. Fixes #71120. Thanks @steipete.
- Plugins/Google Meet: make meeting creation join by default, with an explicit URL-only opt-out, so agents that create a Meet also enter it. Thanks @steipete.
- Telegram/polling: persist accepted update offsets before long-running handlers complete so poller restarts do not replay already-ingested updates, while keeping same-process retries for handler failures. Thanks @steipete.
@@ -94,6 +214,7 @@ Docs: https://docs.openclaw.ai
- Providers/Google: map `/think adaptive` to Gemini dynamic thinking instead of a fixed medium/high budget, using Gemini 3's provider default and Gemini 2.5's `thinkingBudget: -1`. Fixes #71316. Thanks @steipete.
- Providers/MiniMax: keep M2.7 chat model metadata text-only so image tool requests route through `MiniMax-VL-01` instead of the Anthropic-compatible chat endpoint. Fixes #71296. Thanks @ilker-cevikkaya.
- Discord/replies: run `message_sending` plugin hooks for Discord reply delivery, including DM targets, so plugins can transform or cancel outbound Discord replies consistently with other channels. Fixes #59350. (#71094) Thanks @wei840222.
- Discord/replies: preserve single-use native reply semantics across shared payload fallback, component, voice, and queued delivery paths, so explicit reply tags no longer consume implicit reply slots and chunked fallback sends reply only once. Thanks @steipete.
- Control UI/commands: carry provider-owned thinking option ids/labels in session rows and defaults so fresh sessions show and accept dynamic modes such as `adaptive`, `xhigh`, and `max`. Fixes #71269. Thanks @Young-Khalil.
- Image generation: make explicit `model=` overrides exact-only so failed `openai/gpt-image-2` requests no longer fall through to Gemini or other configured providers, and update `image_generate list` to mention OpenAI Codex OAuth as valid auth for `openai/gpt-image-2`. Fixes #71290 and #71231. Thanks @Young-Khalil and @steipete.
- Providers/GitHub Copilot: keep the plugin stream wrapper from claiming transport selection before OpenClaw picks a boundary-aware stream path, avoiding Pi's stale fallback Copilot headers on normal model turns. Thanks @steipete.
@@ -164,6 +285,14 @@ Docs: https://docs.openclaw.ai
- Plugins/Google Meet: report required manual actions for Chrome joins, use browser automation for Meet entry, and persist the private-WS node opt-in so paired-node realtime sessions keep their intended network policy. Thanks @steipete.
- Slack: route native stream fallback replies through the normal chunked sender so long buffered Slack Connect responses are not dropped or duplicated. (#71124) Thanks @martingarramon.
- WhatsApp: transcribe accepted voice notes before agent dispatch while keeping spoken transcripts out of command authorization. (#64120) Thanks @rogerdigital.
- Plugins/CLI: expose channel plugin CLI descriptors during discovery-mode plugin loads so snapshot registries keep channel commands visible without activating full runtimes. (#71309) Thanks @gumadeiras.
- WhatsApp: deliver media generated by tool-result replies while still suppressing text-only tool chatter. (#60968) Thanks @adaclaw.
- Config/agents: accept `agents.list[].contextTokens` in strict config validation so per-agent overrides survive hot reload, letting `/status` reflect the configured model window instead of the 200k fallback. Fixes #70692. (#71247) Thanks @statxc.
- Heartbeat: include async exec completion details in heartbeat prompts so command-finished notifications relay the actual output. (#71213) Thanks @GodsBoy.
- Memory search: apply session visibility and agent-to-agent policy to session transcript hits, and keep `corpus=sessions` ranking scoped to session collections before result limiting. (#70761) Thanks @nefainl.
- Agents/sessions: stop session write-lock timeouts from entering model failover, so local lock contention surfaces directly instead of cascading across providers. (#68700) Thanks @MonkeyLeeT.
- Auto-reply: run inbound reply delivery through `message_sending` hooks so plugins can transform or cancel generated replies before they are sent. (#70118) Thanks @jzakirov.
- CI/release-checks: pass workflow inputs and matrix values through step environment variables instead of embedding them directly into `run:` shell commands, reducing template-injection surface in the cross-OS release-check workflow. (#66884) Thanks @alexlomt.
## 2026.4.23

View File

@@ -9,8 +9,14 @@ enum ExecAllowlistMatcher {
for entry in entries {
switch ExecApprovalHelpers.validateAllowlistPattern(entry.pattern) {
case let .valid(pattern):
let target = resolvedPath ?? rawExecutable
if self.matches(pattern: pattern, target: target) { return entry }
if ExecApprovalHelpers.patternHasPathSelector(pattern) {
let target = resolvedPath ?? rawExecutable
if self.matches(pattern: pattern, target: target) { return entry }
} else if pattern != "*",
!ExecApprovalHelpers.patternHasPathSelector(rawExecutable),
self.matchesExecutableBasename(pattern: pattern, resolution: resolution) {
return entry
}
case .invalid:
continue
}
@@ -34,6 +40,20 @@ enum ExecAllowlistMatcher {
return matches
}
private static func matchesExecutableBasename(
pattern: String,
resolution: ExecCommandResolution) -> Bool
{
var candidates = Set<String>()
if !resolution.executableName.isEmpty {
candidates.insert(resolution.executableName)
}
if let resolvedPath = resolution.resolvedPath, !resolvedPath.isEmpty {
candidates.insert(URL(fileURLWithPath: resolvedPath).lastPathComponent)
}
return candidates.contains { self.matches(pattern: pattern, target: $0) }
}
private static func matches(pattern: String, target: String) -> Bool {
let trimmed = pattern.trimmingCharacters(in: .whitespacesAndNewlines)
guard !trimmed.isEmpty else { return false }

View File

@@ -616,6 +616,17 @@ enum ExecApprovalsStore {
let trimmedResolved = entry.lastResolvedPath?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
let normalizedResolved = trimmedResolved.isEmpty ? nil : trimmedResolved
if !ExecApprovalHelpers.patternHasPathSelector(trimmedPattern),
!trimmedResolved.isEmpty,
case let .valid(migratedPattern) = ExecApprovalHelpers.validateAllowlistPattern(trimmedResolved) {
return ExecAllowlistEntry(
id: entry.id,
pattern: migratedPattern,
lastUsedAt: entry.lastUsedAt,
lastUsedCommand: entry.lastUsedCommand,
lastResolvedPath: normalizedResolved)
}
switch ExecApprovalHelpers.validateAllowlistPattern(trimmedPattern) {
case let .valid(pattern):
return ExecAllowlistEntry(
@@ -724,11 +735,10 @@ enum ExecApprovalHelpers {
static func validateAllowlistPattern(_ pattern: String?) -> ExecAllowlistPatternValidation {
let trimmed = pattern?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
guard !trimmed.isEmpty else { return .invalid(.empty) }
guard self.containsPathComponent(trimmed) else { return .invalid(.missingPathComponent) }
return .valid(trimmed)
}
static func isPathPattern(_ pattern: String?) -> Bool {
static func isValidAllowlistPattern(_ pattern: String?) -> Bool {
switch self.validateAllowlistPattern(pattern) {
case .valid:
true
@@ -737,6 +747,11 @@ enum ExecApprovalHelpers {
}
}
static func isPathPattern(_ pattern: String?) -> Bool {
let trimmed = pattern?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
return self.patternHasPathSelector(trimmed)
}
static func parseDecision(_ raw: String?) -> ExecApprovalDecision? {
let trimmed = raw?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
guard !trimmed.isEmpty else { return nil }
@@ -759,7 +774,7 @@ enum ExecApprovalHelpers {
return pattern.isEmpty ? nil : pattern
}
private static func containsPathComponent(_ pattern: String) -> Bool {
static func patternHasPathSelector(_ pattern: String) -> Bool {
pattern.contains("/") || pattern.contains("~") || pattern.contains("\\")
}
}

View File

@@ -70,6 +70,7 @@ actor GatewayConnection {
case wizardStatus = "wizard.status"
case talkConfig = "talk.config"
case talkMode = "talk.mode"
case talkSpeak = "talk.speak"
case webLoginStart = "web.login.start"
case webLoginWait = "web.login.wait"
case channelsLogout = "channels.logout"

View File

@@ -105,7 +105,7 @@ struct SystemRunSettingsView: View {
.foregroundStyle(.secondary)
} else {
HStack(spacing: 8) {
TextField("Add allowlist path pattern (case-insensitive globs)", text: self.$newPattern)
TextField("Add command name or path glob", text: self.$newPattern)
.textFieldStyle(.roundedBorder)
Button("Add") {
if self.model.addEntry(self.newPattern) == nil {
@@ -113,10 +113,10 @@ struct SystemRunSettingsView: View {
}
}
.buttonStyle(.bordered)
.disabled(!self.model.isPathPattern(self.newPattern))
.disabled(!self.model.isValidPattern(self.newPattern))
}
Text("Path patterns only. Basename entries like \"echo\" are ignored.")
Text("Bare names match PATH-resolved commands. Use a path glob for a specific binary.")
.font(.footnote)
.foregroundStyle(.secondary)
if let validationMessage = self.model.allowlistValidationMessage {
@@ -424,8 +424,8 @@ final class ExecApprovalsSettingsModel {
self.entries.first(where: { $0.id == id })
}
func isPathPattern(_ pattern: String) -> Bool {
ExecApprovalHelpers.isPathPattern(pattern)
func isValidPattern(_ pattern: String) -> Bool {
ExecApprovalHelpers.isValidAllowlistPattern(pattern)
}
func refreshSkillBins(force: Bool = false) async {

View File

@@ -2,6 +2,7 @@ import AVFoundation
import Foundation
import OpenClawChatUI
import OpenClawKit
import OpenClawProtocol
import OSLog
import Speech
@@ -475,7 +476,16 @@ actor TalkModeRuntime {
self.ttsLogger
.error(
"talk TTS failed: \(error.localizedDescription, privacy: .public); " +
"falling back to system voice")
"retrying gateway talk.speak")
do {
try await self.playGatewayTalkSpeak(input: input)
return
} catch {
self.ttsLogger
.error(
"talk gateway TTS failed: \(error.localizedDescription, privacy: .public); " +
"falling back to system voice")
}
do {
try await self.playSystemVoice(input: input)
} catch {
@@ -720,6 +730,42 @@ actor TalkModeRuntime {
return await self.playMP3(stream: stream)
}
private func playGatewayTalkSpeak(input: TalkPlaybackInput) async throws {
let params = Self.makeTalkSpeakParams(
text: input.cleanedText,
voiceId: input.voiceId,
modelId: self.currentModelId ?? self.defaultModelId,
outputFormat: self.defaultOutputFormat,
directive: input.directive)
let result: TalkSpeakResult = try await GatewayConnection.shared.requestDecoded(
method: .talkSpeak,
params: params,
timeoutMs: max(30000, input.synthTimeoutSeconds * 1000 + 5000))
guard let audioData = Data(base64Encoded: result.audiobase64), !audioData.isEmpty else {
throw NSError(domain: "TalkSpeak", code: 1, userInfo: [
NSLocalizedDescriptionKey: "gateway talk.speak returned empty audio",
])
}
_ = await self.stopPCM()
_ = await self.stopMP3()
if self.interruptOnSpeech {
guard await self.prepareForPlayback(generation: input.generation) else { return }
}
await MainActor.run { TalkModeController.shared.updatePhase(.speaking) }
self.phase = .speaking
let playback = await self.playTalkAudio(data: audioData)
self.ttsLogger
.info(
"talk gateway audio provider=\(result.provider, privacy: .public) " +
"format=\(result.outputformat ?? "unknown", privacy: .public) " +
"finished=\(playback.finished, privacy: .public)")
if !playback.finished, playback.interruptedAt == nil {
throw NSError(domain: "TalkSpeak", code: 2, userInfo: [
NSLocalizedDescriptionKey: "gateway talk.speak audio playback failed",
])
}
}
private func playSystemVoice(input: TalkPlaybackInput) async throws {
self.ttsLogger.info("talk system voice start chars=\(input.cleanedText.count, privacy: .public)")
if self.interruptOnSpeech {
@@ -847,6 +893,54 @@ actor TalkModeRuntime {
}
extension TalkModeRuntime {
static func makeTalkSpeakParams(
text: String,
voiceId: String?,
modelId: String?,
outputFormat: String?,
directive: TalkDirective?) -> [String: AnyCodable]
{
var params: [String: AnyCodable] = ["text": AnyCodable(text)]
func addString(_ key: String, _ value: String?) {
let trimmed = value?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
guard !trimmed.isEmpty else { return }
params[key] = AnyCodable(trimmed)
}
addString("voiceId", voiceId)
addString("modelId", directive?.modelId ?? modelId)
addString("outputFormat", directive?.outputFormat ?? outputFormat)
if let speed = directive?.speed {
params["speed"] = AnyCodable(speed)
}
if let rateWPM = directive?.rateWPM {
params["rateWpm"] = AnyCodable(rateWPM)
}
if let stability = directive?.stability {
params["stability"] = AnyCodable(stability)
}
if let similarity = directive?.similarity {
params["similarity"] = AnyCodable(similarity)
}
if let style = directive?.style {
params["style"] = AnyCodable(style)
}
if let speakerBoost = directive?.speakerBoost {
params["speakerBoost"] = AnyCodable(speakerBoost)
}
if let seed = directive?.seed {
params["seed"] = AnyCodable(seed)
}
addString("normalize", directive?.normalize)
addString("language", directive?.language)
if let latencyTier = directive?.latencyTier {
params["latencyTier"] = AnyCodable(latencyTier)
}
return params
}
// MARK: - Audio playback (MainActor helpers)
@MainActor

View File

@@ -66,22 +66,34 @@ struct ExecAllowlistTests {
#expect(match?.pattern == entry.pattern)
}
@Test func `match ignores basename pattern`() {
@Test func `match accepts basename pattern for PATH resolved executable`() {
let entry = ExecAllowlistEntry(pattern: "rg")
let resolution = Self.homebrewRGResolution()
let match = ExecAllowlistMatcher.match(entries: [entry], resolution: resolution)
#expect(match == nil)
#expect(match?.pattern == entry.pattern)
}
@Test func `match ignores basename for relative executable`() {
@Test func `match accepts basename glob for PATH resolved executable`() {
let entry = ExecAllowlistEntry(pattern: "r?")
let resolution = Self.homebrewRGResolution()
let match = ExecAllowlistMatcher.match(entries: [entry], resolution: resolution)
#expect(match?.pattern == entry.pattern)
}
@Test func `match ignores basename for path selected executable`() {
let entry = ExecAllowlistEntry(pattern: "echo")
let resolution = ExecCommandResolution(
let relativeResolution = ExecCommandResolution(
rawExecutable: "./echo",
resolvedPath: "/tmp/oc-basename/echo",
executableName: "echo",
cwd: "/tmp/oc-basename")
let match = ExecAllowlistMatcher.match(entries: [entry], resolution: resolution)
#expect(match == nil)
let absoluteResolution = ExecCommandResolution(
rawExecutable: "/tmp/oc-basename/echo",
resolvedPath: "/tmp/oc-basename/echo",
executableName: "echo",
cwd: "/tmp/oc-basename")
#expect(ExecAllowlistMatcher.match(entries: [entry], resolution: relativeResolution) == nil)
#expect(ExecAllowlistMatcher.match(entries: [entry], resolution: absoluteResolution) == nil)
}
@Test func `match is case insensitive`() {

View File

@@ -33,18 +33,13 @@ struct ExecApprovalHelpersTests {
#expect(ExecApprovalHelpers.isPathPattern("/usr/bin/rg"))
#expect(ExecApprovalHelpers.isPathPattern(" ~/bin/rg "))
#expect(!ExecApprovalHelpers.isPathPattern("rg"))
#expect(ExecApprovalHelpers.isValidAllowlistPattern("rg"))
if case let .invalid(reason) = ExecApprovalHelpers.validateAllowlistPattern(" ") {
#expect(reason == .empty)
} else {
Issue.record("Expected empty pattern rejection")
}
if case let .invalid(reason) = ExecApprovalHelpers.validateAllowlistPattern("echo") {
#expect(reason == .missingPathComponent)
} else {
Issue.record("Expected basename pattern rejection")
}
}
@Test func `requires ask matches policy`() {

View File

@@ -31,7 +31,7 @@ struct ExecApprovalsStoreRefactorTests {
}
@Test
func `update allowlist reports rejected basename pattern`() async throws {
func `update allowlist accepts basename pattern`() async throws {
try await self.withTempStateDir { _ in
let rejected = ExecApprovalsStore.updateAllowlist(
agentId: "main",
@@ -39,12 +39,10 @@ struct ExecApprovalsStoreRefactorTests {
ExecAllowlistEntry(pattern: "echo"),
ExecAllowlistEntry(pattern: "/bin/echo"),
])
#expect(rejected.count == 1)
#expect(rejected.first?.reason == .missingPathComponent)
#expect(rejected.first?.pattern == "echo")
#expect(rejected.isEmpty)
let resolved = ExecApprovalsStore.resolve(agentId: "main")
#expect(resolved.allowlist.map(\.pattern) == ["/bin/echo"])
#expect(resolved.allowlist.map(\.pattern) == ["echo", "/bin/echo"])
}
}

View File

@@ -1,3 +1,4 @@
import OpenClawKit
import Speech
import Testing
@testable import OpenClaw
@@ -16,23 +17,19 @@ struct TalkModeRuntimeSpeechTests {
let elevenLabsPlan = TalkModeRuntime.playbackPlan(
provider: "elevenlabs",
apiKey: "key",
voiceId: "voice"
)
voiceId: "voice")
let missingKeyPlan = TalkModeRuntime.playbackPlan(
provider: "elevenlabs",
apiKey: nil,
voiceId: "voice"
)
voiceId: "voice")
let missingVoicePlan = TalkModeRuntime.playbackPlan(
provider: "elevenlabs",
apiKey: "key",
voiceId: nil
)
voiceId: nil)
let blankKeyPlan = TalkModeRuntime.playbackPlan(
provider: "elevenlabs",
apiKey: "",
voiceId: "voice"
)
voiceId: "voice")
let mlxPlan = TalkModeRuntime.playbackPlan(provider: "mlx", apiKey: nil, voiceId: nil)
let systemPlan = TalkModeRuntime.playbackPlan(provider: "system", apiKey: nil, voiceId: nil)
@@ -43,4 +40,40 @@ struct TalkModeRuntimeSpeechTests {
#expect(mlxPlan == .mlxThenSystemVoice)
#expect(systemPlan == .systemVoiceOnly)
}
@Test func `talk speak params carry resolved voice and directive overrides`() {
let params = TalkModeRuntime.makeTalkSpeakParams(
text: "hello",
voiceId: "voice-123",
modelId: "eleven_v3",
outputFormat: "mp3_44100_128",
directive: TalkDirective(
modelId: "eleven_turbo_v2_5",
speed: 1.1,
rateWPM: 180,
stability: 0.4,
similarity: 0.7,
style: 0.2,
speakerBoost: true,
seed: 42,
normalize: "auto",
language: "en",
outputFormat: "mp3_44100_128",
latencyTier: 3))
#expect(params["text"]?.value as? String == "hello")
#expect(params["voiceId"]?.value as? String == "voice-123")
#expect(params["modelId"]?.value as? String == "eleven_turbo_v2_5")
#expect(params["outputFormat"]?.value as? String == "mp3_44100_128")
#expect(params["speed"]?.value as? Double == 1.1)
#expect(params["rateWpm"]?.value as? Int == 180)
#expect(params["stability"]?.value as? Double == 0.4)
#expect(params["similarity"]?.value as? Double == 0.7)
#expect(params["style"]?.value as? Double == 0.2)
#expect(params["speakerBoost"]?.value as? Bool == true)
#expect(params["seed"]?.value as? Int == 42)
#expect(params["normalize"]?.value as? String == "auto")
#expect(params["language"]?.value as? String == "en")
#expect(params["latencyTier"]?.value as? Int == 3)
}
}

View File

@@ -1,4 +1,4 @@
8f23e853ccde6cd021b84b32fe205f456f8516667683d16c9b56d6598f608989 config-baseline.json
037bf4a873587adb8349f531c0ad79cd4f90e01712f5aa5d8b4387be73538a7f config-baseline.core.json
22d7cd6d8279146b2d79c9531a55b80b52a2c99c81338c508104729154fdd02d config-baseline.channel.json
86f615b7d267b03888af0af7ccb3f8232a6b636f8a741d522ff425e46729ba81 config-baseline.plugin.json
13b68287fec00108ca66032120909a0eac797ed541e026357e175e3fce5bacdd config-baseline.json
77ee66fb3b2cde94b393712bc03a132b096cf601c193bde1fe42902eecb0b66b config-baseline.core.json
d72032762ab46b99480b57deb81130a0ab5b1401189cfbaf4f7fef4a063a7f6c config-baseline.channel.json
0d5ba81f0030bd39b7ae285096276cc18b150836c2252fd2217329fc6154e80e config-baseline.plugin.json

View File

@@ -3,6 +3,18 @@
"source": "OpenClaw",
"target": "OpenClaw"
},
{
"source": "OpenAI",
"target": "OpenAI"
},
{
"source": "OpenAI provider",
"target": "OpenAI provider"
},
{
"source": "Status",
"target": "Status"
},
{
"source": "Gateway",
"target": "Gateway 网关"

View File

@@ -86,6 +86,8 @@ This fires ~56 times per month instead of 01 times per month. OpenClaw use
**Main session** jobs enqueue a system event and optionally wake the heartbeat (`--wake now` or `--wake next-heartbeat`). **Isolated** jobs run a dedicated agent turn with a fresh session. **Custom sessions** (`session:xxx`) persist context across runs, enabling workflows like daily standups that build on previous summaries.
For isolated jobs, “fresh session” means a new transcript/session id for each run. OpenClaw may carry safe preferences such as thinking/fast/verbose settings, labels, and explicit user-selected model/auth overrides, but it does not inherit ambient conversation context from an older cron row: channel/group routing, send or queue policy, elevation, origin, or ACP runtime binding. Use `current` or `session:<id>` when a recurring job should deliberately build on the same conversation context.
For isolated jobs, runtime teardown now includes best-effort browser cleanup for that cron session. Cleanup failures are ignored so the actual cron result still wins.
Isolated cron runs also dispose any bundled MCP runtime instances created for the job through the shared runtime-cleanup path. This matches how main-session and custom-session MCP clients are torn down, so isolated cron jobs do not leak stdio child processes or long-lived MCP connections across runs.
@@ -94,6 +96,11 @@ When isolated cron runs orchestrate subagents, delivery also prefers the final
descendant output over stale parent interim text. If descendants are still
running, OpenClaw suppresses that partial parent update instead of announcing it.
For text-only Discord announce targets, OpenClaw sends the canonical final
assistant text once instead of replaying both streamed/intermediate text payloads
and the final answer. Media and structured Discord payloads are still delivered
as separate payloads so attachments and components are not dropped.
### Payload options for isolated jobs
- `--message`: prompt text (required for isolated)
@@ -111,7 +118,7 @@ Model-selection precedence for isolated jobs is:
1. Gmail hook model override (when the run came from Gmail and that override is allowed)
2. Per-job payload `model`
3. Stored cron session model override
3. User-selected stored cron session model override
4. Agent/default model selection
Fast mode follows the resolved live selection too. If the selected model config
@@ -119,10 +126,11 @@ has `params.fastMode`, isolated cron uses that by default. A stored session
`fastMode` override still wins over config in either direction.
If an isolated run hits a live model-switch handoff, cron retries with the
switched provider/model and persists that live selection before retrying. When
the switch also carries a new auth profile, cron persists that auth profile
override too. Retries are bounded: after the initial attempt plus 2 switch
retries, cron aborts instead of looping forever.
switched provider/model and persists that live selection for the active run
before retrying. When the switch also carries a new auth profile, cron persists
that auth profile override for the active run too. Retries are bounded: after
the initial attempt plus 2 switch retries, cron aborts instead of looping
forever.
## Delivery and output

View File

@@ -267,6 +267,9 @@ Now create some channels on your Discord server and start chatting. Your agent c
- Guild channels are isolated session keys (`agent:<agentId>:discord:channel:<channelId>`).
- Group DMs are ignored by default (`channels.discord.dm.groupEnabled=false`).
- Native slash commands run in isolated command sessions (`agent:<agentId>:discord:slash:<userId>`), while still carrying `CommandTargetSessionKey` to the routed conversation session.
- Text-only cron/heartbeat announce delivery to Discord uses the final
assistant-visible answer once. Media and structured component payloads remain
multi-message when the agent emits multiple deliverable payloads.
## Forum channels

View File

@@ -430,6 +430,12 @@ Full configuration: [Gateway configuration](/gateway/configuration)
- ✅ Thread replies
- ✅ Media replies stay thread-aware when replying to a thread message
For `groupSessionScope: "group_topic"` and `"group_topic_sender"`, native
Feishu/Lark topic groups use the event `thread_id` (`omt_*`) as the canonical
topic session key. Normal group replies that OpenClaw turns into threads keep
using the reply root message ID (`om_*`) so the first turn and follow-up turn
stay in the same session.
---
## Related

View File

@@ -104,6 +104,28 @@ existing approval as-is and creates a fresh pending upgrade request. Use
`openclaw devices list` to compare the currently approved access with the newly
requested access before you approve.
### Optional trusted-CIDR node auto-approve
Device pairing remains manual by default. For tightly controlled node networks,
you can opt in to first-time node auto-approval with explicit CIDRs or exact IPs:
```json5
{
gateway: {
nodes: {
pairing: {
autoApproveCidrs: ["192.168.1.0/24"],
},
},
},
}
```
This only applies to fresh `role: node` pairing requests with no requested
scopes. Operator, browser, Control UI, and WebChat clients still require manual
approval. Role, scope, metadata, and public-key changes still require manual
approval.
### Node pairing state storage
Stored under `~/.openclaw/devices/`:

View File

@@ -208,6 +208,7 @@ Groups:
- Outbound text is chunked to `channels.signal.textChunkLimit` (default 4000).
- Optional newline chunking: set `channels.signal.chunkMode="newline"` to split on blank lines (paragraph boundaries) before length chunking.
- Attachments supported (base64 fetched from `signal-cli`).
- Voice-note attachments use the `signal-cli` filename as a MIME fallback when `contentType` is missing, so audio transcription can still classify AAC voice memos.
- Default media cap: `channels.signal.mediaMaxMb` (default 8).
- Use `channels.signal.ignoreAttachments` to skip downloading media.
- Group history context uses `channels.signal.historyLimit` (or `channels.signal.accounts.*.historyLimit`), falling back to `messages.groupChat.historyLimit`. Set `0` to disable (default 50).

View File

@@ -273,9 +273,28 @@ curl "https://api.telegram.org/bot<bot_token>/getUpdates"
- `channels.telegram.streaming` is `off | partial | block | progress` (default: `partial`)
- `progress` maps to `partial` on Telegram (compat with cross-channel naming)
- `streaming.preview.toolProgress` controls whether tool/progress updates reuse the same edited preview message (default: `true`). Set `false` to keep separate tool/progress messages.
- `streaming.preview.toolProgress` controls whether tool/progress updates reuse the same edited preview message (default: `true` when preview streaming is active)
- legacy `channels.telegram.streamMode` and boolean `streaming` values are auto-mapped
Tool-progress preview updates are the short "Working..." lines shown while tools run, for example command execution, file reads, planning updates, or patch summaries. Telegram keeps these enabled by default to match released OpenClaw behavior from `v2026.4.22` and later. To keep the edited preview for answer text but hide tool-progress lines, set:
```json
{
"channels": {
"telegram": {
"streaming": {
"mode": "partial",
"preview": {
"toolProgress": false
}
}
}
}
}
```
Use `streaming.mode: "off"` only when you want to disable Telegram preview edits entirely. Use `streaming.preview.toolProgress: false` when you only want to disable the tool-progress status lines.
For text-only replies:
- DM: OpenClaw keeps the same preview message and performs a final edit in place (no second message)

View File

@@ -361,6 +361,7 @@ When the linked self number is also present in `allowFrom`, WhatsApp self-chat s
<Accordion title="Outbound media behavior">
- supports image, video, audio (PTT voice-note), and document payloads
- reply payloads preserve `audioAsVoice`; WhatsApp sends audio media as Baileys PTT voice notes
- `audio/ogg` is rewritten to `audio/ogg; codecs=opus` for voice-note compatibility
- animated GIF playback is supported via `gifPlayback: true` on video sends
- captions are applied to the first media item when sending multi-media reply payloads

View File

@@ -79,7 +79,7 @@ gh workflow run duplicate-after-merge.yml \
| `android` | Android unit tests for both flavors plus one debug APK build | Android-relevant changes |
| `test-performance-agent` | Daily Codex slow-test optimization after trusted activity | Main CI success or manual dispatch |
## Fail-Fast Order
## Fail-fast order
Jobs are ordered so cheap checks fail before expensive ones run:
@@ -90,8 +90,9 @@ Jobs are ordered so cheap checks fail before expensive ones run:
Scope logic lives in `scripts/ci-changed-scope.mjs` and is covered by unit tests in `src/scripts/ci-changed-scope.test.ts`.
CI workflow edits validate the Node CI graph plus workflow linting, but do not force Windows, Android, or macOS native builds by themselves; those platform lanes stay scoped to platform source changes.
CI routing-only edits, selected cheap core-test fixture edits, and narrow plugin contract helper/test-routing edits use a fast Node-only manifest path: preflight, security, and a single `checks-fast-core` task. That path avoids build artifacts, Node 22 compatibility, channel contracts, full core shards, bundled-plugin shards, and additional guard matrices when the changed files are limited to the routing or helper surfaces that the fast task exercises directly.
Windows Node checks are scoped to Windows-specific process/path wrappers, npm/pnpm/UI runner helpers, package manager config, and the CI workflow surfaces that execute that lane; unrelated source, plugin, install-smoke, and test-only changes stay on the Linux Node lanes so they do not reserve a 16-vCPU Windows worker for coverage that is already exercised by the normal test shards.
The separate `install-smoke` workflow reuses the same scope script through its own `preflight` job. It splits smoke coverage into `run_fast_install_smoke` and `run_full_install_smoke`. Pull requests run the fast path for Docker/package surfaces, bundled plugin package/manifest changes, and core plugin/channel/gateway/Plugin SDK surfaces that the Docker smoke jobs exercise. Source-only bundled plugin changes, test-only edits, and docs-only edits do not reserve Docker workers. The fast path builds the root Dockerfile image once, checks the CLI, runs the agents delete shared-workspace CLI smoke, runs the container gateway-network e2e, verifies a bundled extension build arg, and runs the bounded bundled-plugin Docker profile under a 120-second command timeout. The full path keeps QR package install and installer Docker/update coverage for nightly scheduled runs, manual dispatches, workflow-call release checks, and pull requests that truly touch installer/package/Docker surfaces. `main` pushes, including merge commits, do not force the full path; when changed-scope logic would request full coverage on a push, the workflow keeps the fast Docker smoke and leaves the full install smoke to nightly or release validation. The slow Bun global install image-provider smoke is separately gated by `run_bun_global_install_smoke`; it runs on the nightly schedule and from the release checks workflow, and manual `install-smoke` dispatches can opt into it, but pull requests and `main` pushes do not run it. QR and installer Docker tests keep their own install-focused Dockerfiles. Local `test:docker:all` prebuilds one shared live-test image and one shared `scripts/e2e/Dockerfile` built-app image, then runs the live/E2E smoke lanes with a weighted scheduler and `OPENCLAW_SKIP_DOCKER_BUILD=1`; tune the default main-pool slot count of 10 with `OPENCLAW_DOCKER_ALL_PARALLELISM` and the provider-sensitive tail-pool slot count of 10 with `OPENCLAW_DOCKER_ALL_TAIL_PARALLELISM`. Heavy lane caps default to `OPENCLAW_DOCKER_ALL_LIVE_LIMIT=6`, `OPENCLAW_DOCKER_ALL_NPM_LIMIT=8`, and `OPENCLAW_DOCKER_ALL_SERVICE_LIMIT=7` so npm install and multi-service lanes do not overcommit Docker while lighter lanes still fill available slots. Lane starts are staggered by 2 seconds by default to avoid local Docker daemon create storms; override with `OPENCLAW_DOCKER_ALL_START_STAGGER_MS=0` or another millisecond value. The local aggregate preflights Docker, removes stale OpenClaw E2E containers, emits active-lane status, persists lane timings for longest-first ordering, and supports `OPENCLAW_DOCKER_ALL_DRY_RUN=1` for scheduler inspection. It stops scheduling new pooled lanes after the first failure by default, and each lane has a 120-minute fallback timeout overrideable with `OPENCLAW_DOCKER_ALL_LANE_TIMEOUT_MS`; selected live/tail lanes use tighter per-lane caps. The reusable live/E2E workflow mirrors the shared-image pattern by building and pushing one SHA-tagged GHCR Docker E2E image before the Docker matrix, then running the matrix with `OPENCLAW_SKIP_DOCKER_BUILD=1`. The scheduled live/E2E workflow runs the full release-path Docker suite daily. The bundled update matrix is split by update target so repeated npm update and doctor repair passes can shard with other bundled checks.
The separate `install-smoke` workflow reuses the same scope script through its own `preflight` job. It splits smoke coverage into `run_fast_install_smoke` and `run_full_install_smoke`. Pull requests run the fast path for Docker/package surfaces, bundled plugin package/manifest changes, and core plugin/channel/gateway/Plugin SDK surfaces that the Docker smoke jobs exercise. Source-only bundled plugin changes, test-only edits, and docs-only edits do not reserve Docker workers. The fast path builds the root Dockerfile image once, checks the CLI, runs the agents delete shared-workspace CLI smoke, runs the container gateway-network e2e, verifies a bundled extension build arg, and runs the bounded bundled-plugin Docker profile under a 240-second aggregate command timeout with each scenario's Docker run capped separately. The full path keeps QR package install and installer Docker/update coverage for nightly scheduled runs, manual dispatches, workflow-call release checks, and pull requests that truly touch installer/package/Docker surfaces. `main` pushes, including merge commits, do not force the full path; when changed-scope logic would request full coverage on a push, the workflow keeps the fast Docker smoke and leaves the full install smoke to nightly or release validation. The slow Bun global install image-provider smoke is separately gated by `run_bun_global_install_smoke`; it runs on the nightly schedule and from the release checks workflow, and manual `install-smoke` dispatches can opt into it, but pull requests and `main` pushes do not run it. QR and installer Docker tests keep their own install-focused Dockerfiles. Local `test:docker:all` prebuilds one shared live-test image and one shared `scripts/e2e/Dockerfile` built-app image, then runs the live/E2E smoke lanes with a weighted scheduler and `OPENCLAW_SKIP_DOCKER_BUILD=1`; tune the default main-pool slot count of 10 with `OPENCLAW_DOCKER_ALL_PARALLELISM` and the provider-sensitive tail-pool slot count of 10 with `OPENCLAW_DOCKER_ALL_TAIL_PARALLELISM`. Heavy lane caps default to `OPENCLAW_DOCKER_ALL_LIVE_LIMIT=6`, `OPENCLAW_DOCKER_ALL_NPM_LIMIT=8`, and `OPENCLAW_DOCKER_ALL_SERVICE_LIMIT=7` so npm install and multi-service lanes do not overcommit Docker while lighter lanes still fill available slots. Lane starts are staggered by 2 seconds by default to avoid local Docker daemon create storms; override with `OPENCLAW_DOCKER_ALL_START_STAGGER_MS=0` or another millisecond value. The local aggregate preflights Docker, removes stale OpenClaw E2E containers, emits active-lane status, persists lane timings for longest-first ordering, and supports `OPENCLAW_DOCKER_ALL_DRY_RUN=1` for scheduler inspection. It stops scheduling new pooled lanes after the first failure by default, and each lane has a 120-minute fallback timeout overrideable with `OPENCLAW_DOCKER_ALL_LANE_TIMEOUT_MS`; selected live/tail lanes use tighter per-lane caps. The reusable live/E2E workflow mirrors the shared-image pattern by building and pushing one SHA-tagged GHCR Docker E2E image before the Docker matrix, then running the matrix with `OPENCLAW_SKIP_DOCKER_BUILD=1`. The scheduled live/E2E workflow runs the full release-path Docker suite daily. The bundled update matrix is split by update target so repeated npm update and doctor repair passes can shard with other bundled checks.
Local changed-lane logic lives in `scripts/changed-lanes.mjs` and is executed by `scripts/check-changed.mjs`. That local gate is stricter about architecture boundaries than the broad CI platform scope: core production changes run core prod typecheck plus core tests, core test-only changes run only core test typecheck/tests, extension production changes run extension prod typecheck plus extension tests, and extension test-only changes run only extension test typecheck/tests. Public Plugin SDK or plugin-contract changes expand to extension validation because extensions depend on those core contracts. Release metadata-only version bumps run targeted version/config/root-dependency checks. Unknown root/config changes fail safe to all lanes.

View File

@@ -53,6 +53,7 @@ openclaw agent --agent ops --message "Run locally" --local
- Gateway mode falls back to the embedded agent when the Gateway request fails. Use `--local` to force embedded execution up front.
- `--local` still preloads the plugin registry first, so plugin-provided providers, tools, and channels stay available during embedded runs.
- `--channel`, `--reply-channel`, and `--reply-account` affect reply delivery, not session routing.
- `--json` keeps stdout reserved for the JSON response. Gateway, plugin, and embedded-fallback diagnostics are routed to stderr so scripts can parse stdout directly.
- When this command triggers `models.json` regeneration, SecretRef-managed provider credentials are persisted as non-secret markers (for example env var names, `secretref-env:ENV_VAR_NAME`, or `secretref-managed`), not resolved secret plaintext.
- Marker writes are source-authoritative: OpenClaw persists markers from the active source config snapshot, not from resolved runtime secret values.

View File

@@ -164,6 +164,7 @@ Navigate/click/type (ref-based UI automation):
```bash
openclaw browser navigate https://example.com
openclaw browser click <ref>
openclaw browser click-coords 120 340
openclaw browser type <ref> "hello"
openclaw browser press Enter
openclaw browser hover <ref>

View File

@@ -36,6 +36,7 @@ openclaw config --section gateway --section daemon
openclaw config schema
openclaw config get browser.executablePath
openclaw config set browser.executablePath "/usr/bin/google-chrome"
openclaw config set browser.profiles.work.executablePath "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
openclaw config set agents.defaults.heartbeat.every "2h"
openclaw config set agents.list[0].tools.exec.node "node-id-or-name"
openclaw config set agents.defaults.models '{"openai/gpt-5.4":{}}' --strict-json --merge
@@ -185,7 +186,7 @@ openclaw config set secrets.providers.vaultfile \
--strict-json
```
## Provider Builder Flags
## Provider builder flags
Provider builder targets must use `secrets.providers.<alias>` as the path.
@@ -278,7 +279,7 @@ Dry-run behavior:
- `skippedExecRefs`: number of exec refs skipped because `--allow-exec` was not set
- `errors`: structured schema/resolvability failures when `ok=false`
### JSON Output Shape
### JSON output shape
```json5
{
@@ -385,6 +386,15 @@ untrusted until they validate. Invalid direct edits can be restored from the
last-known-good backup during startup or hot reload. See
[Gateway troubleshooting](/gateway/troubleshooting#gateway-restored-last-known-good-config).
Whole-file recovery is reserved for globally broken config, such as parse
errors, root-level schema failures, legacy migration failures, or mixed plugin
and root failures. If validation fails only under `plugins.entries.<id>...`,
OpenClaw keeps the active `openclaw.json` in place and reports the plugin-local
issue instead of restoring `.last-good`. This prevents plugin schema changes or
`minHostVersion` skew from rolling back unrelated user settings such as models,
providers, auth profiles, channels, gateway exposure, tools, memory, browser, or
cron config.
## Subcommands
- `config file`: Print the active config file path (resolved from `OPENCLAW_CONFIG_PATH` or default location). The path should name a regular file, not a symlink.

View File

@@ -33,6 +33,11 @@ Note: `--session` supports `main`, `isolated`, `current`, and `session:<id>`.
Use `current` to bind to the active session at creation time, or `session:<id>` for
an explicit persistent session key.
Note: `--session isolated` creates a fresh transcript/session id for each run.
Safe preferences and explicit user-selected model/auth overrides can carry, but
ambient conversation context does not: channel/group routing, send/queue policy,
elevation, origin, and ACP runtime binding are reset for the new isolated run.
Note: for one-shot CLI jobs, offset-less `--at` datetimes are treated as UTC unless you also pass
`--tz <iana>`, which interprets that local wall-clock time in the given timezone.
@@ -59,17 +64,17 @@ model override with no explicit per-job fallback list no longer appends the
agent primary as a hidden extra retry target.
Note: isolated cron model precedence is Gmail-hook override first, then per-job
`--model`, then any stored cron-session model override, then the normal
agent/default selection.
`--model`, then any user-selected stored cron-session model override, then the
normal agent/default selection.
Note: isolated cron fast mode follows the resolved live model selection. Model
config `params.fastMode` applies by default, but a stored session `fastMode`
override still wins over config.
Note: if an isolated run throws `LiveSessionModelSwitchError`, cron persists the
switched provider/model (and switched auth profile override when present) before
retrying. The outer retry loop is bounded to 2 switch retries after the initial
attempt, then aborts instead of looping forever.
switched provider/model (and switched auth profile override when present) for
the active run before retrying. The outer retry loop is bounded to 2 switch
retries after the initial attempt, then aborts instead of looping forever.
Note: failure notifications use `delivery.failureDestination` first, then
global `cron.failureDestination`, and finally fall back to the job's primary

View File

@@ -66,6 +66,12 @@ request. Review the `Requested` vs `Approved` columns in `openclaw devices list`
or use `openclaw devices approve --latest` to preview the exact upgrade before
approving it.
If the Gateway is explicitly configured with
`gateway.nodes.pairing.autoApproveCidrs`, first-time `role: node` requests from
matching client IPs can be approved before they appear in this list. That policy
is disabled by default and never applies to operator/browser clients or upgrade
requests.
```
openclaw devices approve
openclaw devices approve <requestId>
@@ -127,6 +133,8 @@ Pass `--token` or `--password` explicitly. Missing explicit credentials is an er
- Token rotation returns a new token (sensitive). Treat it like a secret.
- These commands require `operator.pairing` (or `operator.admin`) scope.
- `gateway.nodes.pairing.autoApproveCidrs` is an opt-in Gateway policy for
fresh node device pairing only; it does not change CLI approval authority.
- Token rotation stays inside the approved pairing role set and approved scope
baseline for that device. A stray cached token entry does not grant a new
rotate target.

View File

@@ -376,6 +376,9 @@ Important behavior:
- embedded Pi exposes configured MCP tools in normal `coding` and `messaging`
tool profiles; `minimal` still hides them, and `tools.deny: ["bundle-mcp"]`
disables them explicitly
- session-scoped bundled MCP runtimes are reaped after `mcp.sessionIdleTtlMs`
milliseconds of idle time (default 10 minutes; set `0` to disable) and
one-shot embedded runs clean them up at run end
## Saved MCP server definitions

View File

@@ -50,6 +50,10 @@ Notes:
- `models list --all` includes bundled provider-owned static catalog rows even
when you have not authenticated with that provider yet. Those rows still show
as unavailable until matching auth is configured.
- `models list` keeps native model metadata and runtime caps distinct. In table
output, `Ctx` shows `contextTokens/contextWindow` when an effective runtime
cap differs from the native context window; JSON rows include `contextTokens`
when a provider exposes that cap.
- `models list --provider <id>` filters by provider id, such as `moonshot` or
`openai-codex`. It does not accept display labels from interactive provider
pickers, such as `Moonshot AI`.

View File

@@ -123,6 +123,25 @@ openclaw devices list
openclaw devices approve <requestId>
```
On tightly controlled node networks, the Gateway operator can explicitly opt in
to auto-approving first-time node pairing from trusted CIDRs:
```json5
{
gateway: {
nodes: {
pairing: {
autoApproveCidrs: ["192.168.1.0/24"],
},
},
},
}
```
This is disabled by default. It only applies to fresh `role: node` pairing with
no requested scopes. Operator/browser clients, Control UI, WebChat, and role,
scope, metadata, or public-key upgrades still require manual approval.
If the node retries pairing with changed auth details (role/scopes/public key),
the previous pending request is superseded and a new `requestId` is created.
Run `openclaw devices list` again before approval.

View File

@@ -42,6 +42,9 @@ filter to nodes that connected within a duration (e.g. `24h`, `7d`).
Approval note:
- `openclaw nodes pending` only needs pairing scope.
- `gateway.nodes.pairing.autoApproveCidrs` can skip the pending step only for
explicitly trusted, first-time `role: node` device pairing. It is off by
default and does not approve upgrades.
- `openclaw nodes approve <requestId>` inherits extra scope requirements from the
pending request:
- commandless request: pairing only

View File

@@ -2,7 +2,7 @@
summary: "CLI reference for `openclaw voicecall` (voice-call plugin command surface)"
read_when:
- You use the voice-call plugin and want the CLI entry points
- You want quick examples for `voicecall call|continue|dtmf|status|tail|expose`
- You want quick examples for `voicecall setup|smoke|call|continue|dtmf|status|tail|expose`
title: "Voicecall"
---
@@ -17,6 +17,8 @@ Primary doc:
## Common commands
```bash
openclaw voicecall setup
openclaw voicecall smoke
openclaw voicecall status --call-id <id>
openclaw voicecall call --to "+15555550123" --message "Hello" --mode notify
openclaw voicecall continue --call-id <id> --message "Any questions?"
@@ -24,6 +26,25 @@ openclaw voicecall dtmf --call-id <id> --digits "ww123456#"
openclaw voicecall end --call-id <id>
```
`setup` prints human-readable readiness checks by default. Use `--json` for
scripts:
```bash
openclaw voicecall setup --json
```
For external providers (`twilio`, `telnyx`, `plivo`), setup must resolve a public
webhook URL from `publicUrl`, a tunnel, or Tailscale exposure. A loopback/private
serve fallback is rejected because carriers cannot reach it.
`smoke` runs the same readiness checks. It will not place a real phone call
unless both `--to` and `--yes` are present:
```bash
openclaw voicecall smoke --to "+15555550123" # dry run
openclaw voicecall smoke --to "+15555550123" --yes # live notify call
```
## Exposing webhooks (Tailscale)
```bash

View File

@@ -14,7 +14,7 @@ the finished turn to OpenClaw.
Runtimes are easy to confuse with providers because both show up near model
configuration. They are different layers:
| Layer | Examples | What It Means |
| Layer | Examples | What it means |
| ------------- | ------------------------------------- | ------------------------------------------------------------------- |
| Provider | `openai`, `anthropic`, `openai-codex` | How OpenClaw authenticates, discovers models, and names model refs. |
| Model | `gpt-5.5`, `claude-opus-4-6` | The model selected for the agent turn. |
@@ -46,6 +46,10 @@ That means OpenClaw selects an OpenAI model ref, then asks the Codex app-server
runtime to run the embedded agent turn. It does not mean the channel, model
provider catalog, or OpenClaw session store becomes Codex.
For the OpenAI-family prefix split, see [OpenAI](/providers/openai) and
[Model providers](/concepts/model-providers). For the Codex runtime support
contract, see [Codex harness](/plugins/codex-harness#v1-support-contract).
## Runtime ownership
Different runtimes own different amounts of the loop.
@@ -84,14 +88,16 @@ OpenClaw chooses an embedded runtime after provider and model resolution:
Explicit plugin runtimes fail closed by default. For example,
`runtime: "codex"` means Codex or a clear selection error unless you set
`fallback: "pi"` in the same override scope.
`fallback: "pi"` in the same override scope. A runtime override does not inherit
a broader fallback setting, so an agent-level `runtime: "codex"` is not silently
routed back to PI just because defaults used `fallback: "pi"`.
## Compatibility contract
When a runtime is not PI, it should document what OpenClaw surfaces it supports.
Use this shape for runtime docs:
| Question | Why It Matters |
| Question | Why it matters |
| -------------------------------------- | ------------------------------------------------------------------------------------------------- |
| Who owns the model loop? | Determines where retries, tool continuation, and final answer decisions happen. |
| Who owns canonical thread history? | Determines whether OpenClaw can edit history or only mirror it. |
@@ -122,6 +128,8 @@ session systems.
## Related
- [Codex harness](/plugins/codex-harness)
- [OpenAI](/providers/openai)
- [Agent harness plugins](/plugins/sdk-agent-harness)
- [Agent loop](/concepts/agent-loop)
- [Models](/concepts/models)
- [Status](/cli/status)

View File

@@ -113,6 +113,11 @@ the summary:
/compact Focus on the API design decisions
```
When `agents.defaults.compaction.keepRecentTokens` is set, manual compaction
honors that Pi cut-point and keeps the recent tail in rebuilt context. Without
an explicit keep budget, manual compaction behaves as a hard checkpoint and
continues from the new summary alone.
## Using a different model
By default, compaction uses your agent's primary model. You can use a more

View File

@@ -6,8 +6,7 @@ read_when:
title: "Model providers"
---
This page covers **LLM/model providers** (not chat channels like WhatsApp/Telegram).
For model selection rules, see [/concepts/models](/concepts/models).
Reference for **LLM/model providers** (not chat channels like WhatsApp/Telegram). For model selection rules, see [Models](/concepts/models).
## Quick rules
@@ -20,7 +19,8 @@ For model selection rules, see [/concepts/models](/concepts/models).
OpenAI API-key provider in PI, `openai-codex/<model>` uses Codex OAuth in PI,
and `openai/<model>` plus `agents.defaults.embeddedHarness.runtime: "codex"`
uses the native Codex app-server harness. See [OpenAI](/providers/openai)
and [Codex harness](/plugins/codex-harness).
and [Codex harness](/plugins/codex-harness). If the provider/runtime split is
confusing, read [Agent runtimes](/concepts/agent-runtimes) first.
- Plugin auto-enable follows that same boundary: `openai-codex/<model>` belongs
to the OpenAI plugin, while the Codex plugin is enabled by
`embeddedHarness.runtime: "codex"` or legacy `codex/<model>` refs.
@@ -30,11 +30,9 @@ For model selection rules, see [/concepts/models](/concepts/models).
`google-gemini-cli`, or `codex-cli` when you want a local CLI backend.
Legacy `claude-cli/*`, `google-gemini-cli/*`, and `codex-cli/*` refs migrate
back to canonical provider refs with the runtime recorded separately.
- GPT-5.5 is currently available through subscription/OAuth routes:
`openai-codex/gpt-5.5` in PI or `openai/gpt-5.5` with the Codex app-server
harness. The direct API-key route for `openai/gpt-5.5` is supported once
OpenAI enables GPT-5.5 on the public API; until then use API-enabled models
such as `openai/gpt-5.4` for `OPENAI_API_KEY` setups.
- GPT-5.5 is available through `openai-codex/gpt-5.5` in PI, the native
Codex app-server harness, and the public OpenAI API when the bundled PI
catalog exposes `openai/gpt-5.5` for your install.
## Plugin-owned provider behavior
@@ -73,8 +71,10 @@ OpenClaw ships with the piai catalog. These providers require **no**
- Provider: `openai`
- Auth: `OPENAI_API_KEY`
- Optional rotation: `OPENAI_API_KEYS`, `OPENAI_API_KEY_1`, `OPENAI_API_KEY_2`, plus `OPENCLAW_LIVE_OPENAI_KEY` (single override)
- Example models: `openai/gpt-5.4`, `openai/gpt-5.4-mini`
- GPT-5.5 direct API support is future-ready here once OpenAI exposes GPT-5.5 on the API
- Example models: `openai/gpt-5.5`, `openai/gpt-5.4`, `openai/gpt-5.4-mini`
- GPT-5.5 direct API support depends on the bundled PI catalog version for
your install; verify with `openclaw models list --provider openai` before
using `openai/gpt-5.5` without the Codex app-server runtime.
- CLI: `openclaw onboard --auth-choice openai-api-key`
- Default transport is `auto` (WebSocket-first, SSE fallback)
- Override per model via `agents.defaults.models["openai/<model>"].params.transport` (`"sse"`, `"websocket"`, or `"auto"`)
@@ -118,6 +118,7 @@ OpenClaw ships with the piai catalog. These providers require **no**
- Auth: OAuth (ChatGPT)
- PI model ref: `openai-codex/gpt-5.5`
- Native Codex app-server harness ref: `openai/gpt-5.5` with `agents.defaults.embeddedHarness.runtime: "codex"`
- Native Codex app-server harness docs: [Codex harness](/plugins/codex-harness)
- Legacy model refs: `codex/gpt-*`
- Plugin boundary: `openai-codex/*` loads the OpenAI plugin; the native Codex
app-server plugin is selected only by the Codex harness runtime or legacy
@@ -130,9 +131,9 @@ OpenClaw ships with the piai catalog. These providers require **no**
`User-Agent`) are only attached on native Codex traffic to
`chatgpt.com/backend-api`, not generic OpenAI-compatible proxies
- Shares the same `/fast` toggle and `params.fastMode` config as direct `openai/*`; OpenClaw maps that to `service_tier=priority`
- `openai-codex/gpt-5.5` keeps native `contextWindow = 1000000` and a default runtime `contextTokens = 272000`; override the runtime cap with `models.providers.openai-codex.models[].contextTokens`
- `openai-codex/gpt-5.5` uses the Codex catalog native `contextWindow = 400000` and default runtime `contextTokens = 272000`; override the runtime cap with `models.providers.openai-codex.models[].contextTokens`
- Policy note: OpenAI Codex OAuth is explicitly supported for external tools/workflows like OpenClaw.
- Current GPT-5.5 access uses this OAuth/subscription route until OpenAI enables GPT-5.5 on the public API.
- Use `openai-codex/gpt-5.5` when you want the Codex OAuth/subscription route; use `openai/gpt-5.5` when your API-key setup and local catalog expose the public API route.
```json5
{
@@ -156,14 +157,14 @@ OpenClaw ships with the piai catalog. These providers require **no**
- [Qwen Cloud](/providers/qwen): Qwen Cloud provider surface plus Alibaba DashScope and Coding Plan endpoint mapping
- [MiniMax](/providers/minimax): MiniMax Coding Plan OAuth or API key access
- [GLM Models](/providers/glm): Z.AI Coding Plan or general API endpoints
- [GLM models](/providers/glm): Z.AI Coding Plan or general API endpoints
### OpenCode
- Auth: `OPENCODE_API_KEY` (or `OPENCODE_ZEN_API_KEY`)
- Zen runtime provider: `opencode`
- Go runtime provider: `opencode-go`
- Example models: `opencode/claude-opus-4-6`, `opencode-go/kimi-k2.5`
- Example models: `opencode/claude-opus-4-6`, `opencode-go/kimi-k2.6`
- CLI: `openclaw onboard --auth-choice opencode-zen` or `openclaw onboard --auth-choice opencode-go`
```json5
@@ -266,7 +267,7 @@ See [/providers/kilocode](/providers/kilocode) for setup details.
Quirks worth knowing:
- **OpenRouter** applies its app-attribution headers and Anthropic `cache_control` markers only on verified `openrouter.ai` routes. As a proxy-style OpenAI-compatible path, it skips native-OpenAI-only shaping (`serviceTier`, Responses `store`, prompt-cache hints, OpenAI reasoning-compat). Gemini-backed refs keep proxy-Gemini thought-signature sanitation only.
- **OpenRouter** applies its app-attribution headers and Anthropic `cache_control` markers only on verified `openrouter.ai` routes. DeepSeek, Moonshot, and ZAI refs are cache-TTL eligible for OpenRouter-managed prompt caching but do not receive Anthropic cache markers. As a proxy-style OpenAI-compatible path, it skips native-OpenAI-only shaping (`serviceTier`, Responses `store`, prompt-cache hints, OpenAI reasoning-compat). Gemini-backed refs keep proxy-Gemini thought-signature sanitation only.
- **Kilo Gateway** Gemini-backed refs follow the same proxy-Gemini sanitation path; `kilocode/kilo/auto` and other proxy-reasoning-unsupported refs skip proxy reasoning injection.
- **MiniMax** API-key onboarding writes explicit text-only M2.7 chat model definitions; image understanding stays on the plugin-owned `MiniMax-VL-01` media provider.
- **xAI** uses the xAI Responses path. `/fast` or `params.fastMode: true` rewrites `grok-3`, `grok-3-mini`, `grok-4`, and `grok-4-0709` to their `*-fast` variants. `tool_stream` defaults on; disable via `agents.defaults.models["xai/<model>"].params.tool_stream=false`.
@@ -625,9 +626,12 @@ Notes:
- Recommended: set explicit values that match your proxy/model limits.
- For `api: "openai-completions"` on non-native endpoints (any non-empty `baseUrl` whose host is not `api.openai.com`), OpenClaw forces `compat.supportsDeveloperRole: false` to avoid provider 400 errors for unsupported `developer` roles.
- Proxy-style OpenAI-compatible routes also skip native OpenAI-only request
shaping: no `service_tier`, no Responses `store`, no prompt-cache hints, no
OpenAI reasoning-compat payload shaping, and no hidden OpenClaw attribution
headers.
shaping: no `service_tier`, no Responses `store`, no Completions `store`, no
prompt-cache hints, no OpenAI reasoning-compat payload shaping, and no hidden
OpenClaw attribution headers.
- For OpenAI-compatible Completions proxies that need vendor-specific fields,
set `agents.defaults.models["provider/model"].params.extra_body` (or
`extraBody`) to merge extra JSON into the outbound request body.
- If `baseUrl` is empty/omitted, OpenClaw keeps the default OpenAI behavior (which resolves to `api.openai.com`).
- For safety, an explicit `compat.supportsDeveloperRole: true` is still overridden on non-native `openai-completions` endpoints.
@@ -639,11 +643,11 @@ openclaw models set opencode/claude-opus-4-6
openclaw models list
```
See also: [/gateway/configuration](/gateway/configuration) for full configuration examples.
See also: [Configuration](/gateway/configuration) for full configuration examples.
## Related
- [Models](/concepts/models) — model configuration and aliases
- [Model Failover](/concepts/model-failover) — fallback chains and retry behavior
- [Configuration Reference](/gateway/config-agents#agent-defaults) — model config keys
- [Model failover](/concepts/model-failover) — fallback chains and retry behavior
- [Configuration reference](/gateway/config-agents#agent-defaults) — model config keys
- [Providers](/providers) — per-provider setup guides

View File

@@ -171,8 +171,8 @@ How to see what profile IDs exist:
Related docs:
- [/concepts/model-failover](/concepts/model-failover) (rotation + cooldown rules)
- [/tools/slash-commands](/tools/slash-commands) (command surface)
- [Model failover](/concepts/model-failover) (rotation + cooldown rules)
- [Slash commands](/tools/slash-commands) (command surface)
## Related

View File

@@ -7,8 +7,6 @@ read_when:
title: "Streaming and chunking"
---
# Streaming + chunking
OpenClaw has two separate streaming layers:
- **Block streaming (channels):** emit completed **blocks** as the assistant writes. These are normal channel messages (not token deltas).
@@ -54,6 +52,19 @@ Legend:
`message_end` still uses the chunker if the buffered text exceeds `maxChars`, so it can emit multiple chunks at the end.
### Media delivery with block streaming
`MEDIA:` directives are normal delivery metadata. When block streaming sends a
media block early, OpenClaw remembers that delivery for the turn. If the final
assistant payload repeats the same media URL, the final delivery strips the
duplicate media instead of sending the attachment again.
Exact duplicate final payloads are suppressed. If the final payload adds
distinct text around media that was already streamed, OpenClaw still sends the
new text while keeping the media single-delivery. This prevents duplicate voice
notes or files on channels such as Telegram when an agent emits `MEDIA:` during
streaming and the provider also includes it in the completed reply.
## Chunking algorithm (low/high bounds)
Block chunking is implemented by `EmbeddedBlockChunker`:
@@ -176,9 +187,28 @@ Preview streaming can also include **tool-progress** updates — short status li
Supported surfaces:
- **Discord**, **Slack**, and **Telegram** stream tool-progress into the live preview edit.
- **Discord**, **Slack**, and **Telegram** stream tool-progress into the live preview edit by default when preview streaming is active.
- Telegram has shipped with tool-progress preview updates enabled since `v2026.4.22`; keeping them enabled preserves that released behavior.
- **Mattermost** already folds tool activity into its single draft preview post (see above).
- Tool-progress edits follow the active preview streaming mode; they are skipped when preview streaming is `off` or when block streaming has taken over the message.
- To keep preview streaming but hide tool-progress lines, set `streaming.preview.toolProgress` to `false` for that channel. To disable preview edits entirely, set `streaming.mode` to `off`.
Example:
```json
{
"channels": {
"telegram": {
"streaming": {
"mode": "partial",
"preview": {
"toolProgress": false
}
}
}
}
}
```
## Related

View File

@@ -1165,6 +1165,7 @@
"plugins/hooks",
"plugins/sdk-channel-plugins",
"plugins/sdk-provider-plugins",
"plugins/compatibility",
"plugins/sdk-migration"
]
},

View File

@@ -349,6 +349,12 @@ When bundle MCP is enabled, OpenClaw:
If no MCP servers are enabled, OpenClaw still injects a strict config when a
backend opts into bundle MCP so background runs stay isolated.
Session-scoped bundled MCP runtimes are cached for reuse within a session, then
reaped after `mcp.sessionIdleTtlMs` milliseconds of idle time (default 10
minutes; set `0` to disable). One-shot embedded runs such as auth probes,
slug generation, and active-memory recall request cleanup at run end so stdio
children and Streamable HTTP/SSE streams do not outlive the run.
## Limitations
- **No direct OpenClaw tool calls.** OpenClaw does not inject tool calls into

View File

@@ -72,6 +72,7 @@ Disables automatic creation of workspace bootstrap files (`AGENTS.md`, `SOUL.md`
Controls when workspace bootstrap files are injected into the system prompt. Default: `"always"`.
- `"continuation-skip"`: safe continuation turns (after a completed assistant response) skip workspace bootstrap re-injection, reducing prompt size. Heartbeat runs and post-compaction retries still rebuild context.
- `"never"`: disable workspace bootstrap and context-file injection on every turn. Use this only for agents that fully own their prompt lifecycle (custom context engines, native runtimes that build their own context, or specialized bootstrap-free workflows). Heartbeat and compaction-recovery turns also skip injection.
```json5
{
@@ -363,12 +364,13 @@ Time format in system prompt. Default: `auto` (OS preference).
- `verboseDefault`: default verbose level for agents. Values: `"off"`, `"on"`, `"full"`. Default: `"off"`.
- `elevatedDefault`: default elevated-output level for agents. Values: `"off"`, `"on"`, `"ask"`, `"full"`. Default: `"on"`.
- `model.primary`: format `provider/model` (e.g. `openai/gpt-5.4` for API-key access or `openai-codex/gpt-5.5` for Codex OAuth). If you omit the provider, OpenClaw tries an alias first, then a unique configured-provider match for that exact model id, and only then falls back to the configured default provider (deprecated compatibility behavior, so prefer explicit `provider/model`). If that provider no longer exposes the configured default model, OpenClaw falls back to the first configured provider/model instead of surfacing a stale removed-provider default.
- `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`, `responsesServerCompaction`, `responsesCompactThreshold`).
- `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`, `responsesServerCompaction`, `responsesCompactThreshold`, `extra_body`/`extraBody`).
- Safe edits: use `openclaw config set agents.defaults.models '<json>' --strict-json --merge` to add entries. `config set` refuses replacements that would remove existing allowlist entries unless you pass `--replace`.
- Provider-scoped configure/onboarding flows merge selected provider models into this map and preserve unrelated providers already configured.
- For direct OpenAI Responses models, server-side compaction is enabled automatically. Use `params.responsesServerCompaction: false` to stop injecting `context_management`, or `params.responsesCompactThreshold` to override the threshold. See [OpenAI server-side compaction](/providers/openai#server-side-compaction-responses-api).
- `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.
- `params.extra_body`/`params.extraBody`: advanced pass-through JSON merged into `api: "openai-completions"` request bodies for OpenAI-compatible proxies. If it collides with generated request keys, the extra body wins; non-native completions routes still strip OpenAI-only `store` afterward.
- `embeddedHarness`: default low-level embedded agent runtime policy. Omitted runtime defaults to OpenClaw Pi. Use `runtime: "pi"` to force the built-in PI harness, `runtime: "auto"` to let registered plugin harnesses claim supported models, or a registered harness id such as `runtime: "codex"`. Set `fallback: "none"` to disable automatic PI fallback. Explicit plugin runtimes such as `codex` fail closed by default unless you set `fallback: "pi"` in the same override scope. Keep model refs canonical as `provider/model`; select Codex, Claude CLI, Gemini CLI, and other execution backends through runtime config instead of legacy runtime provider prefixes. See [Agent runtimes](/concepts/agent-runtimes) for how this differs from provider/model selection.
- 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.
@@ -399,7 +401,7 @@ Codex app-server harness. For the mental model, see
- `fallback`: `"pi"` or `"none"`. In `runtime: "auto"`, omitted fallback defaults to `"pi"` so old configs can keep using PI when no plugin harness claims a run. In explicit plugin runtime mode, such as `runtime: "codex"`, omitted fallback defaults to `"none"` so a missing harness fails instead of silently using PI. Runtime overrides do not inherit fallback from a broader scope; set `fallback: "pi"` alongside the explicit runtime when you intentionally want that compatibility fallback. Selected plugin harness failures always surface directly.
- Environment overrides: `OPENCLAW_AGENT_RUNTIME=<id|auto|pi>` overrides `runtime`; `OPENCLAW_AGENT_HARNESS_FALLBACK=pi|none` overrides fallback for that process.
- For Codex-only deployments, set `model: "openai/gpt-5.5"` and `embeddedHarness.runtime: "codex"`. You may also set `embeddedHarness.fallback: "none"` explicitly for readability; it is the default for explicit plugin runtimes.
- Harness choice is pinned per session id after the first embedded run. Config/env changes affect new or reset sessions, not an existing transcript. Legacy sessions with transcript history but no recorded pin are treated as PI-pinned. `/status` shows non-PI harness ids such as `codex` next to `Fast`.
- Harness choice is pinned per session id after the first embedded run. Config/env changes affect new or reset sessions, not an existing transcript. Legacy sessions with transcript history but no recorded pin are treated as PI-pinned. `/status` reports the effective runtime, for example `Runtime: OpenClaw Pi Default` or `Runtime: OpenAI Codex`.
- This only controls the embedded chat harness. Media generation, vision, PDF, music, video, and TTS still use their provider/model settings.
**Built-in alias shorthands** (only apply when the model is in `agents.defaults.models`):
@@ -541,8 +543,10 @@ Periodic heartbeat runs.
provider: "my-provider", // id of a registered compaction provider plugin (optional)
timeoutSeconds: 900,
reserveTokensFloor: 24000,
keepRecentTokens: 50000,
identifierPolicy: "strict", // strict | off | custom
identifierInstructions: "Preserve deployment IDs, ticket IDs, and host:port pairs exactly.", // used when identifierPolicy=custom
qualityGuard: { enabled: true, maxRetries: 1 },
postCompactionSections: ["Session Startup", "Red Lines"], // [] disables reinjection
model: "openrouter/anthropic/claude-sonnet-4-6", // optional compaction-only model override
notifyUser: true, // send brief notices when compaction starts and completes (default: false)
@@ -561,8 +565,10 @@ Periodic heartbeat runs.
- `mode`: `default` or `safeguard` (chunked summarization for long histories). See [Compaction](/concepts/compaction).
- `provider`: id of a registered compaction provider plugin. When set, the provider's `summarize()` is called instead of built-in LLM summarization. Falls back to built-in on failure. Setting a provider forces `mode: "safeguard"`. See [Compaction](/concepts/compaction).
- `timeoutSeconds`: maximum seconds allowed for a single compaction operation before OpenClaw aborts it. Default: `900`.
- `keepRecentTokens`: Pi cut-point budget for keeping the most recent transcript tail verbatim. Manual `/compact` honors this when explicitly set; otherwise manual compaction is a hard checkpoint.
- `identifierPolicy`: `strict` (default), `off`, or `custom`. `strict` prepends built-in opaque identifier retention guidance during compaction summarization.
- `identifierInstructions`: optional custom identifier-preservation text used when `identifierPolicy=custom`.
- `qualityGuard`: retry-on-malformed-output checks for safeguard summaries. Enabled by default in safeguard mode; set `enabled: false` to skip the audit.
- `postCompactionSections`: optional AGENTS.md H2/H3 section names to re-inject after compaction. Defaults to `["Session Startup", "Red Lines"]`; set `[]` to disable reinjection. When unset or explicitly set to that default pair, older `Every Session`/`Safety` headings are also accepted as a legacy fallback.
- `model`: optional `provider/model-id` override for compaction summarization only. Use this when the main session should keep one model but compaction summaries should run on another; when unset, compaction uses the session's primary model.
- `notifyUser`: when `true`, sends brief notices to the user when compaction starts and when it completes (for example, "Compacting context..." and "Compaction complete"). Disabled by default to keep compaction silent.
@@ -1257,27 +1263,34 @@ Batches rapid text-only messages from the same sender into a single agent turn.
maxTextLength: 4000,
timeoutMs: 30000,
prefsPath: "~/.openclaw/settings/tts.json",
elevenlabs: {
apiKey: "elevenlabs_api_key",
baseUrl: "https://api.elevenlabs.io",
voiceId: "voice_id",
modelId: "eleven_multilingual_v2",
seed: 42,
applyTextNormalization: "auto",
languageCode: "en",
voiceSettings: {
stability: 0.5,
similarityBoost: 0.75,
style: 0.0,
useSpeakerBoost: true,
speed: 1.0,
providers: {
elevenlabs: {
apiKey: "elevenlabs_api_key",
baseUrl: "https://api.elevenlabs.io",
voiceId: "voice_id",
modelId: "eleven_multilingual_v2",
seed: 42,
applyTextNormalization: "auto",
languageCode: "en",
voiceSettings: {
stability: 0.5,
similarityBoost: 0.75,
style: 0.0,
useSpeakerBoost: true,
speed: 1.0,
},
},
microsoft: {
voice: "en-US-AvaMultilingualNeural",
lang: "en-US",
outputFormat: "audio-24khz-48kbitrate-mono-mp3",
},
openai: {
apiKey: "openai_api_key",
baseUrl: "https://api.openai.com/v1",
model: "gpt-4o-mini-tts",
voice: "alloy",
},
},
openai: {
apiKey: "openai_api_key",
baseUrl: "https://api.openai.com/v1",
model: "gpt-4o-mini-tts",
voice: "alloy",
},
},
},
@@ -1288,8 +1301,9 @@ Batches rapid text-only messages from the same sender into a single agent turn.
- `summaryModel` overrides `agents.defaults.model.primary` for auto-summary.
- `modelOverrides` is enabled by default; `modelOverrides.allowProvider` defaults to `false` (opt-in).
- API keys fall back to `ELEVENLABS_API_KEY`/`XI_API_KEY` and `OPENAI_API_KEY`.
- `openai.baseUrl` overrides the OpenAI TTS endpoint. Resolution order is config, then `OPENAI_TTS_BASE_URL`, then `https://api.openai.com/v1`.
- When `openai.baseUrl` points to a non-OpenAI endpoint, OpenClaw treats it as an OpenAI-compatible TTS server and relaxes model/voice validation.
- Bundled speech providers are plugin-owned. If `plugins.allow` is set, include each TTS provider plugin you want to use, for example `microsoft` for Edge TTS. The legacy `edge` provider id is accepted as an alias for `microsoft`.
- `providers.openai.baseUrl` overrides the OpenAI TTS endpoint. Resolution order is config, then `OPENAI_TTS_BASE_URL`, then `https://api.openai.com/v1`.
- When `providers.openai.baseUrl` points to a non-OpenAI endpoint, OpenClaw treats it as an OpenAI-compatible TTS server and relaxes model/voice validation.
---

View File

@@ -415,7 +415,7 @@ OpenClaw uses the built-in model catalog. Add custom providers via `models.provi
- `request.allowPrivateNetwork`: when `true`, allow HTTPS to `baseUrl` when DNS resolves to private, CGNAT, or similar ranges, via the provider HTTP fetch guard (operator opt-in for trusted self-hosted OpenAI-compatible endpoints). WebSocket uses the same `request` for headers/TLS but not that fetch SSRF gate. Default `false`.
- `models.providers.*.models`: explicit provider model catalog entries.
- `models.providers.*.models.*.contextWindow`: native model context window metadata.
- `models.providers.*.models.*.contextTokens`: optional runtime context cap. Use this when you want a smaller effective context budget than the model's native `contextWindow`.
- `models.providers.*.models.*.contextTokens`: optional runtime context cap. Use this when you want a smaller effective context budget than the model's native `contextWindow`; `openclaw models list` shows both values when they differ.
- `models.providers.*.models.*.compat.supportsDeveloperRole`: optional compatibility hint. For `api: "openai-completions"` with a non-empty non-native `baseUrl` (host not `api.openai.com`), OpenClaw forces this to `false` at runtime. Empty/omitted `baseUrl` keeps default OpenAI behavior.
- `models.providers.*.models.*.compat.requiresStringContent`: optional compatibility hint for string-only OpenAI-compatible chat endpoints. When `true`, OpenClaw flattens pure text `messages[].content` arrays into plain strings before sending the request.
- `plugins.entries.amazon-bedrock.config.discovery`: Bedrock auto-discovery settings root.

View File

@@ -501,6 +501,28 @@ Save to `~/.openclaw/openclaw.json` and you can DM the bot from that number.
}
```
### Trusted node network auto-approval
Keep device pairing manual unless you control the network path. For a dedicated
lab or tailnet subnet, you can opt in to first-time node device auto-approval
with exact CIDRs or IPs:
```json5
{
gateway: {
nodes: {
pairing: {
autoApproveCidrs: ["192.168.1.0/24", "fd00:1234:5678::/64"],
},
},
},
}
```
This remains off when unset. It only applies to fresh `role: node` pairing with
no requested scopes. Operator/browser clients and role, scope, metadata, or
public-key upgrades still require manual approval.
### Secure DM mode (shared inbox / multi-user DMs)
If more than one person can DM your bot (multiple entries in `allowFrom`, pairing approvals for multiple people, or `dmPolicy: "open"`), enable **secure DM mode** so DMs from different senders dont share one context by default:

View File

@@ -8,7 +8,7 @@ read_when:
Core config reference for `~/.openclaw/openclaw.json`. For a task-oriented overview, see [Configuration](/gateway/configuration).
This page covers the main OpenClaw config surfaces and links out when a subsystem has its own deeper reference. It does **not** try to inline every channel/plugin-owned command catalog or every deep memory/QMD knob on one page.
Covers the main OpenClaw config surfaces and links out when a subsystem has its own deeper reference. Channel- and plugin-owned command catalogs and deep memory/QMD knobs live on their own pages rather than on this one.
Code truth:
@@ -19,7 +19,7 @@ Code truth:
Dedicated deep references:
- [Memory configuration reference](/reference/memory-config) for `agents.defaults.memorySearch.*`, `memory.qmd.*`, `memory.citations`, and dreaming config under `plugins.entries.memory-core.config.dreaming`
- [Slash Commands](/tools/slash-commands) for the current built-in + bundled command catalog
- [Slash commands](/tools/slash-commands) for the current built-in + bundled command catalog
- owning channel/plugin pages for channel-specific command surfaces
Config format is **JSON5** (comments + trailing commas allowed). All fields are optional — OpenClaw uses safe defaults when omitted.
@@ -51,6 +51,44 @@ Tool policy, experimental toggles, provider-backed tool config, and custom
provider / base-URL setup moved to a dedicated page — see
[Configuration — tools and custom providers](/gateway/config-tools).
## MCP
OpenClaw-managed MCP server definitions live under `mcp.servers` and are
consumed by embedded Pi and other runtime adapters. The `openclaw mcp list`,
`show`, `set`, and `unset` commands manage this block without connecting to the
target server during config edits.
```json5
{
mcp: {
// Optional. Default: 600000 ms (10 minutes). Set 0 to disable idle eviction.
sessionIdleTtlMs: 600000,
servers: {
docs: {
command: "npx",
args: ["-y", "@modelcontextprotocol/server-fetch"],
},
remote: {
url: "https://example.com/mcp",
transport: "streamable-http", // streamable-http | sse
headers: {
Authorization: "Bearer ${MCP_REMOTE_TOKEN}",
},
},
},
},
}
```
- `mcp.servers`: named stdio or remote MCP server definitions for runtimes that
expose configured MCP tools.
- `mcp.sessionIdleTtlMs`: idle TTL for session-scoped bundled MCP runtimes.
One-shot embedded runs request run-end cleanup; this TTL is the backstop for
long-lived sessions and future callers.
See [MCP](/cli/mcp#openclaw-as-an-mcp-client-registry) and
[CLI backends](/gateway/cli-backends#bundle-mcp-overlays) for runtime behavior.
## Skills
```json5
@@ -167,9 +205,19 @@ See [Plugins](/tools/plugin).
// hostnameAllowlist: ["*.example.com", "example.com"],
// allowedHostnames: ["localhost"],
},
tabCleanup: {
enabled: true,
idleMinutes: 120,
maxTabsPerSession: 8,
sweepMinutes: 5,
},
profiles: {
openclaw: { cdpPort: 18800, color: "#FF4500" },
work: { cdpPort: 18801, color: "#0066CC" },
work: {
cdpPort: 18801,
color: "#0066CC",
executablePath: "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
},
user: { driver: "existing-session", attachOnly: true, color: "#00AA00" },
brave: {
driver: "existing-session",
@@ -190,6 +238,9 @@ See [Plugins](/tools/plugin).
```
- `evaluateEnabled: false` disables `act:evaluate` and `wait --fn`.
- `tabCleanup` reclaims tracked primary-agent tabs after idle time or when a
session exceeds its cap. Set `idleMinutes: 0` or `maxTabsPerSession: 0` to
disable those individual cleanup modes.
- `ssrfPolicy.dangerouslyAllowPrivateNetwork` is disabled when unset, so browser navigation stays strict by default.
- Set `ssrfPolicy.dangerouslyAllowPrivateNetwork: true` only when you intentionally trust private-network browser navigation.
- In strict mode, remote CDP profile endpoints (`profiles.*.cdpUrl`) are subject to the same private-network blocking during reachability/discovery checks.
@@ -209,7 +260,11 @@ See [Plugins](/tools/plugin).
`responsebody`, PDF export, download interception, or batch actions.
- Local managed `openclaw` profiles auto-assign `cdpPort` and `cdpUrl`; only
set `cdpUrl` explicitly for remote CDP.
- Local managed profiles can set `executablePath` to override the global
`browser.executablePath` for that profile. Use this to run one profile in
Chrome and another in Brave.
- Auto-detect order: default browser if Chromium-based → Chrome → Brave → Edge → Chromium → Chrome Canary.
- `browser.executablePath` accepts `~` for your OS home directory.
- Control service: loopback only (port derived from `gateway.port`, default `18791`).
- `extraArgs` appends extra launch flags to local Chromium startup (for example
`--disable-gpu`, window sizing, or debug flags).
@@ -280,6 +335,14 @@ See [Plugins](/tools/plugin).
trustedProxies: ["10.0.0.1"],
// Optional. Default false.
allowRealIpFallback: false,
nodes: {
pairing: {
// Optional. Default unset/disabled.
autoApproveCidrs: ["192.168.1.0/24", "fd00:1234:5678::/64"],
},
allowCommands: ["canvas.navigate"],
denyCommands: ["system.run"],
},
tools: {
// Additional /tools/invoke HTTP denies
deny: ["browser"],
@@ -342,6 +405,8 @@ See [Plugins](/tools/plugin).
- If `gateway.auth.token` / `gateway.auth.password` is explicitly configured via SecretRef and unresolved, resolution fails closed (no remote fallback masking).
- `trustedProxies`: reverse proxy IPs that terminate TLS or inject forwarded-client headers. Only list proxies you control. Loopback entries are still valid for same-host proxy/local-detection setups (for example Tailscale Serve or a local reverse proxy), but they do **not** make loopback requests eligible for `gateway.auth.mode: "trusted-proxy"`.
- `allowRealIpFallback`: when `true`, the gateway accepts `X-Real-IP` if `X-Forwarded-For` is missing. Default `false` for fail-closed behavior.
- `gateway.nodes.pairing.autoApproveCidrs`: optional CIDR/IP allowlist for auto-approving first-time node device pairing with no requested scopes. It is disabled when unset. This does not auto-approve operator/browser/Control UI/WebChat pairing, and it does not auto-approve role, scope, metadata, or public-key upgrades.
- `gateway.nodes.allowCommands` / `gateway.nodes.denyCommands`: global allow/deny shaping for declared node commands after pairing and allowlist evaluation.
- `gateway.tools.deny`: extra tool names blocked for HTTP `POST /tools/invoke` (extends default deny list).
- `gateway.tools.allow`: remove tool names from the default HTTP deny list.
@@ -831,6 +896,7 @@ Notes:
- `otel.sampleRate`: trace sampling rate `0``1`.
- `otel.flushIntervalMs`: periodic telemetry flush interval in ms.
- `otel.captureContent`: opt-in raw content capture for OTEL span attributes. Defaults to off. Boolean `true` captures non-system message/tool content; the object form lets you enable `inputMessages`, `outputMessages`, `toolInputs`, `toolOutputs`, and `systemPrompt` explicitly.
- `OPENCLAW_OTEL_PRELOADED=1`: environment toggle for hosts that already registered a global OpenTelemetry SDK. OpenClaw then skips plugin-owned SDK startup/shutdown while keeping diagnostic listeners active.
- `cacheTrace.enabled`: log cache trace snapshots for embedded runs (default: `false`).
- `cacheTrace.filePath`: output path for cache trace JSONL (default: `$OPENCLAW_STATE_DIR/logs/cache-trace.jsonl`).
- `cacheTrace.includeMessages` / `includePrompt` / `includeSystem`: control what is included in cache trace output (all default: `true`).

View File

@@ -91,6 +91,10 @@ as `.clobbered.*`, restores the last-known-good copy, and logs the recovery
reason. The next agent turn also receives a system-event warning so the main
agent does not blindly rewrite the restored config. Promotion to last-known-good
is skipped when a candidate contains redacted secret placeholders such as `***`.
When every validation issue is scoped to `plugins.entries.<id>...`, OpenClaw
does not perform whole-file recovery. It keeps the current config active and
surfaces the plugin-local failure so a plugin schema or host-version mismatch
cannot roll back unrelated user settings.
## Common tasks
@@ -504,6 +508,10 @@ config writes use the same schema gate before writing; destructive clobbers such
as dropping `gateway.mode` or shrinking the file by more than half are rejected
and saved as `.rejected.*` for inspection.
Plugin-local validation failures are the exception: if all issues are under
`plugins.entries.<id>...`, reload keeps the current config and reports the plugin
issue instead of restoring `.last-good`.
If you see `Config auto-restored from last-known-good` or
`config reload restored last-known-good config` in logs, inspect the matching
`.clobbered.*` file next to `openclaw.json`, fix the rejected payload, then run

View File

@@ -172,9 +172,11 @@ Current migrations:
- `routing.agentToAgent``tools.agentToAgent`
- `routing.transcribeAudio``tools.media.audio.models`
- `messages.tts.<provider>` (`openai`/`elevenlabs`/`microsoft`/`edge`) → `messages.tts.providers.<provider>`
- `messages.tts.provider: "edge"` and `messages.tts.providers.edge``messages.tts.provider: "microsoft"` and `messages.tts.providers.microsoft`
- `channels.discord.voice.tts.<provider>` (`openai`/`elevenlabs`/`microsoft`/`edge`) → `channels.discord.voice.tts.providers.<provider>`
- `channels.discord.accounts.<id>.voice.tts.<provider>` (`openai`/`elevenlabs`/`microsoft`/`edge`) → `channels.discord.accounts.<id>.voice.tts.providers.<provider>`
- `plugins.entries.voice-call.config.tts.<provider>` (`openai`/`elevenlabs`/`microsoft`/`edge`) → `plugins.entries.voice-call.config.tts.providers.<provider>`
- `plugins.entries.voice-call.config.tts.provider: "edge"` and `plugins.entries.voice-call.config.tts.providers.edge``provider: "microsoft"` and `providers.microsoft`
- `plugins.entries.voice-call.config.provider: "log"``"mock"`
- `plugins.entries.voice-call.config.twilio.from``plugins.entries.voice-call.config.fromNumber`
- `plugins.entries.voice-call.config.streaming.sttProvider``plugins.entries.voice-call.config.streaming.provider`

View File

@@ -265,6 +265,9 @@ Use `accountId` to target a specific account on multi-account channels like Tele
send chat output to, and it is disabled by `typingMode: "never"`.
- Heartbeat-only replies do **not** keep the session alive; the last `updatedAt`
is restored so idle expiry behaves normally.
- Control UI and WebChat history hide heartbeat prompts and OK-only
acknowledgments. The underlying session transcript can still contain those
turns for audit/replay.
- Detached [background tasks](/automation/tasks) can enqueue a system event and wake heartbeat when the main session should notice something quickly. That wake does not make the heartbeat run a background task.
## Visibility controls

View File

@@ -115,6 +115,34 @@ The macOS app can optionally attempt a **silent approval** when:
If silent approval fails, it falls back to the normal “Approve/Reject” prompt.
## Trusted-CIDR device auto-approval
WS device pairing for `role: node` remains manual by default. For private
node networks where the Gateway already trusts the network path, operators can
opt in with explicit CIDRs or exact IPs:
```json5
{
gateway: {
nodes: {
pairing: {
autoApproveCidrs: ["192.168.1.0/24"],
},
},
},
}
```
Security boundary:
- Disabled when `gateway.nodes.pairing.autoApproveCidrs` is unset.
- No blanket LAN or private-network auto-approve mode exists.
- Only fresh `role: node` device pairing with no requested scopes is eligible.
- Operator, browser, Control UI, and WebChat clients stay manual.
- Role, scope, metadata, and public-key upgrades stay manual.
- Same-host loopback trusted-proxy header paths are not eligible because that
path can be spoofed by local callers.
## Metadata-upgrade auto-approval
When an already paired device reconnects with only non-sensitive metadata

View File

@@ -111,6 +111,7 @@ Use this as the quick model when triaging risk:
| `canvas.eval` / browser evaluate | Intentional operator capability when enabled | "Any JS eval primitive is automatically a vuln in this trust model" |
| Local TUI `!` shell | Explicit operator-triggered local execution | "Local shell convenience command is remote injection" |
| Node pairing and node commands | Operator-level remote execution on paired devices | "Remote device control should be treated as untrusted user access by default" |
| `gateway.nodes.pairing.autoApproveCidrs` | Opt-in trusted-network node enrollment policy | "A disabled-by-default allowlist is an automatic pairing vulnerability" |
## Not vulnerabilities by design
@@ -133,6 +134,12 @@ a real boundary bypass is demonstrated:
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.
- Reports that treat configured `gateway.nodes.pairing.autoApproveCidrs` as a
vulnerability by itself. This setting is disabled by default, requires
explicit CIDR/IP entries, only applies to first-time `role: node` pairing with
no requested scopes, and does not auto-approve operator/browser/Control UI,
WebChat, role upgrades, scope upgrades, metadata changes, public-key changes,
or same-host loopback trusted-proxy header paths.
- "Missing per-user authorization" findings that treat `sessionKey` as an
auth token.
@@ -353,6 +360,12 @@ gateway:
When `trustedProxies` is configured, the Gateway uses `X-Forwarded-For` to determine the client IP. `X-Real-IP` is ignored by default unless `gateway.allowRealIpFallback: true` is explicitly set.
Trusted proxy headers do not make node device pairing automatically trusted.
`gateway.nodes.pairing.autoApproveCidrs` is a separate, disabled-by-default
operator policy. Even when enabled, loopback-source trusted-proxy header paths
are excluded from node auto-approval because local callers can forge those
headers.
Good reverse proxy behavior (overwrite incoming forwarding headers):
```nginx

View File

@@ -55,9 +55,9 @@ Fix options:
Related:
- [/providers/anthropic](/providers/anthropic)
- [/reference/token-use](/reference/token-use)
- [/help/faq-first-run#why-am-i-seeing-http-429-ratelimiterror-from-anthropic](/help/faq-first-run#why-am-i-seeing-http-429-ratelimiterror-from-anthropic)
- [Anthropic](/providers/anthropic)
- [Token use and costs](/reference/token-use)
- [Why am I seeing HTTP 429 from Anthropic?](/help/faq-first-run#why-am-i-seeing-http-429-ratelimiterror-from-anthropic)
## Local OpenAI-compatible backend passes direct probes but agent runs fail
@@ -110,9 +110,9 @@ Fix options:
Related:
- [/gateway/local-models](/gateway/local-models)
- [/gateway/configuration](/gateway/configuration)
- [/gateway/configuration-reference#openai-compatible-endpoints](/gateway/configuration-reference#openai-compatible-endpoints)
- [Local models](/gateway/local-models)
- [Configuration](/gateway/configuration)
- [OpenAI-compatible endpoints](/gateway/configuration-reference#openai-compatible-endpoints)
## No replies
@@ -140,9 +140,9 @@ Common signatures:
Related:
- [/channels/troubleshooting](/channels/troubleshooting)
- [/channels/pairing](/channels/pairing)
- [/channels/groups](/channels/groups)
- [Channel troubleshooting](/channels/troubleshooting)
- [Pairing](/channels/pairing)
- [Groups](/channels/groups)
## Dashboard control ui connectivity
@@ -223,11 +223,11 @@ If `openclaw devices rotate` / `revoke` / `remove` is denied unexpectedly:
Related:
- [/web/control-ui](/web/control-ui)
- [/gateway/configuration](/gateway/configuration) (gateway auth modes)
- [/gateway/trusted-proxy-auth](/gateway/trusted-proxy-auth)
- [/gateway/remote](/gateway/remote)
- [/cli/devices](/cli/devices)
- [Control UI](/web/control-ui)
- [Configuration](/gateway/configuration) (gateway auth modes)
- [Trusted proxy auth](/gateway/trusted-proxy-auth)
- [Remote access](/gateway/remote)
- [Devices](/cli/devices)
## Gateway service not running
@@ -258,9 +258,9 @@ Common signatures:
Related:
- [/gateway/background-process](/gateway/background-process)
- [/gateway/configuration](/gateway/configuration)
- [/gateway/doctor](/gateway/doctor)
- [Background exec and process tool](/gateway/background-process)
- [Configuration](/gateway/configuration)
- [Doctor](/gateway/doctor)
## Gateway restored last-known-good config
@@ -287,6 +287,9 @@ What happened:
- OpenClaw preserved the rejected payload as `.clobbered.*`.
- The active config was restored from the last validated last-known-good copy.
- The next main-agent turn is warned not to blindly rewrite the rejected config.
- If all validation issues were under `plugins.entries.<id>...`, OpenClaw would
not restore the whole file. Plugin-local failures stay loud while unrelated
user settings remain in the active config.
Inspect and repair:
@@ -315,10 +318,10 @@ Fix options:
Related:
- [/gateway/configuration#strict-validation](/gateway/configuration#strict-validation)
- [/gateway/configuration#config-hot-reload](/gateway/configuration#config-hot-reload)
- [/cli/config](/cli/config)
- [/gateway/doctor](/gateway/doctor)
- [Configuration: strict validation](/gateway/configuration#strict-validation)
- [Configuration: hot reload](/gateway/configuration#config-hot-reload)
- [Config](/cli/config)
- [Doctor](/gateway/doctor)
## Gateway probe warnings
@@ -345,9 +348,9 @@ Common signatures:
Related:
- [/cli/gateway](/cli/gateway)
- [/gateway#multiple-gateways-same-host](/gateway#multiple-gateways-same-host)
- [/gateway/remote](/gateway/remote)
- [Gateway](/cli/gateway)
- [Multiple gateways on the same host](/gateway#multiple-gateways-same-host)
- [Remote access](/gateway/remote)
## Channel connected messages not flowing
@@ -375,10 +378,10 @@ Common signatures:
Related:
- [/channels/troubleshooting](/channels/troubleshooting)
- [/channels/whatsapp](/channels/whatsapp)
- [/channels/telegram](/channels/telegram)
- [/channels/discord](/channels/discord)
- [Channel troubleshooting](/channels/troubleshooting)
- [WhatsApp](/channels/whatsapp)
- [Telegram](/channels/telegram)
- [Discord](/channels/discord)
## Cron and heartbeat delivery
@@ -410,9 +413,9 @@ Common signatures:
Related:
- [/automation/cron-jobs#troubleshooting](/automation/cron-jobs#troubleshooting)
- [/automation/cron-jobs](/automation/cron-jobs)
- [/gateway/heartbeat](/gateway/heartbeat)
- [Scheduled tasks: troubleshooting](/automation/cron-jobs#troubleshooting)
- [Scheduled tasks](/automation/cron-jobs)
- [Heartbeat](/gateway/heartbeat)
## Node paired tool fails
@@ -441,9 +444,9 @@ Common signatures:
Related:
- [/nodes/troubleshooting](/nodes/troubleshooting)
- [/nodes/index](/nodes/index)
- [/tools/exec-approvals](/tools/exec-approvals)
- [Node troubleshooting](/nodes/troubleshooting)
- [Nodes](/nodes/index)
- [Exec approvals](/tools/exec-approvals)
## Browser tool fails
@@ -489,8 +492,8 @@ Common signatures:
Related:
- [/tools/browser-linux-troubleshooting](/tools/browser-linux-troubleshooting)
- [/tools/browser](/tools/browser)
- [Browser troubleshooting](/tools/browser-linux-troubleshooting)
- [Browser (OpenClaw-managed)](/tools/browser)
## If you upgraded and something suddenly broke
@@ -563,9 +566,9 @@ openclaw gateway restart
Related:
- [/gateway/pairing](/gateway/pairing)
- [/gateway/authentication](/gateway/authentication)
- [/gateway/background-process](/gateway/background-process)
- [Gateway-owned pairing](/gateway/pairing)
- [Authentication](/gateway/authentication)
- [Background exec and process tool](/gateway/background-process)
## Related

View File

@@ -13,6 +13,35 @@ For quick start, QA runners, unit/integration suites, and Docker flows, see
suites: model matrix, CLI backends, ACP, and media-provider live tests, plus
credential handling.
## Live: local profile smoke commands
Source `~/.profile` before ad hoc live checks so provider keys and local tool
paths match your shell:
```bash
source ~/.profile
```
Safe media smoke:
```bash
pnpm openclaw infer tts convert --local --json \
--text "OpenClaw live smoke." \
--output /tmp/openclaw-live-smoke.mp3
```
Safe voice-call readiness smoke:
```bash
pnpm openclaw voicecall setup --json
pnpm openclaw voicecall smoke --to "+15555550123"
```
`voicecall smoke` is a dry run unless `--yes` is also present. Use `--yes` only
when you intentionally want to place a real notify call. For Twilio, Telnyx, and
Plivo, a successful readiness check requires a public webhook URL; local-only
loopback/private fallbacks are rejected by design.
## Live: Android node capability sweep
- Test: `src/gateway/android-node.capabilities.live.test.ts`
@@ -120,7 +149,7 @@ openclaw models list --json
- `OPENCLAW_LIVE_CLI_BACKEND_MODEL="codex-cli/gpt-5.2"`
- `OPENCLAW_LIVE_CLI_BACKEND_COMMAND="/full/path/to/codex"`
- `OPENCLAW_LIVE_CLI_BACKEND_ARGS='["exec","--json","--color","never","--sandbox","read-only","--skip-git-repo-check"]'`
- `OPENCLAW_LIVE_CLI_BACKEND_IMAGE_PROBE=1` to send a real image attachment (paths are injected into the prompt).
- `OPENCLAW_LIVE_CLI_BACKEND_IMAGE_PROBE=1` to send a real image attachment (paths are injected into the prompt). Docker recipes default this off unless explicitly requested.
- `OPENCLAW_LIVE_CLI_BACKEND_IMAGE_ARG="--image"` to pass image file paths as CLI args instead of prompt injection.
- `OPENCLAW_LIVE_CLI_BACKEND_IMAGE_MODE="repeat"` (or `"list"`) to control how image args are passed when `IMAGE_ARG` is set.
- `OPENCLAW_LIVE_CLI_BACKEND_RESUME_PROBE=1` to send a second turn and validate resume flow.

View File

@@ -7,22 +7,20 @@ read_when:
title: "Nix"
---
# Nix Installation
Install OpenClaw declaratively with **[nix-openclaw](https://github.com/openclaw/nix-openclaw)** -- a batteries-included Home Manager module.
Install OpenClaw declaratively with **[nix-openclaw](https://github.com/openclaw/nix-openclaw)** — a batteries-included Home Manager module.
<Info>
The [nix-openclaw](https://github.com/openclaw/nix-openclaw) repo is the source of truth for Nix installation. This page is a quick overview.
</Info>
## What You Get
## What you get
- Gateway + macOS app + tools (whisper, spotify, cameras) -- all pinned
- Launchd service that survives reboots
- Plugin system with declarative config
- Instant rollback: `home-manager switch --rollback`
## Quick Start
## Quick start
<Steps>
<Step title="Install Determinate Nix">
@@ -50,7 +48,7 @@ The [nix-openclaw](https://github.com/openclaw/nix-openclaw) repo is the source
See the [nix-openclaw README](https://github.com/openclaw/nix-openclaw) for full module options and examples.
## Nix Mode Runtime Behavior
## Nix-mode runtime behavior
When `OPENCLAW_NIX_MODE=1` is set (automatic with nix-openclaw), OpenClaw enters a deterministic mode that disables auto-install flows.
@@ -82,6 +80,18 @@ OpenClaw reads JSON5 config from `OPENCLAW_CONFIG_PATH` and stores mutable data
| `OPENCLAW_STATE_DIR` | `~/.openclaw` |
| `OPENCLAW_CONFIG_PATH` | `$OPENCLAW_STATE_DIR/openclaw.json` |
### Service PATH discovery
The launchd/systemd gateway service auto-discovers Nix-profile binaries so
plugins and tools that shell out to `nix`-installed executables work without
manual PATH setup:
- When `NIX_PROFILES` is set, every entry is added to the service PATH in
right-to-left precedence (matches Nix shell precedence — rightmost wins).
- When `NIX_PROFILES` is unset, `~/.nix-profile/bin` is added as a fallback.
This applies to both macOS launchd and Linux systemd service environments.
## Related
- [nix-openclaw](https://github.com/openclaw/nix-openclaw) -- full setup guide

View File

@@ -307,6 +307,10 @@ Notes:
- Set `headers` when your collector requires auth.
- Environment variables supported: `OTEL_EXPORTER_OTLP_ENDPOINT`,
`OTEL_SERVICE_NAME`, `OTEL_EXPORTER_OTLP_PROTOCOL`.
- Set `OPENCLAW_OTEL_PRELOADED=1` when another preload or host process already
registered the global OpenTelemetry SDK. In that mode the plugin does not start
or shut down its own SDK, but it still wires OpenClaw diagnostic listeners and
honors `diagnostics.otel.traces`, `metrics`, and `logs`.
### Exported metrics (names + types)
@@ -389,6 +393,8 @@ classes you opted into.
`OTEL_EXPORTER_OTLP_ENDPOINT`.
- If the endpoint already contains `/v1/traces` or `/v1/metrics`, it is used as-is.
- If the endpoint already contains `/v1/logs`, it is used as-is for logs.
- `OPENCLAW_OTEL_PRELOADED=1` reuses an externally registered OpenTelemetry SDK
for traces/metrics instead of starting a plugin-owned NodeSDK.
- `diagnostics.otel.logs` enables OTLP log export for the main logger output.
### Log export behavior

View File

@@ -117,6 +117,25 @@ openclaw devices reject <requestId>
Pairing details: [Pairing](/channels/pairing).
Optional: if the Android node always connects from a tightly controlled subnet,
you can opt in to first-time node auto-approval with explicit CIDRs or exact IPs:
```json5
{
gateway: {
nodes: {
pairing: {
autoApproveCidrs: ["192.168.1.0/24"],
},
},
},
}
```
This is disabled by default. It applies only to fresh `role: node` pairing with
no requested scopes. Operator/browser pairing and any role, scope, metadata, or
public-key change still require manual approval.
### 5) Verify the node is connected
- Via nodes status:

View File

@@ -44,6 +44,25 @@ If the app retries pairing with changed auth details (role/scopes/public key),
the previous pending request is superseded and a new `requestId` is created.
Run `openclaw devices list` again before approval.
Optional: if the iOS node always connects from a tightly controlled subnet, you
can opt in to first-time node auto-approval with explicit CIDRs or exact IPs:
```json5
{
gateway: {
nodes: {
pairing: {
autoApproveCidrs: ["192.168.1.0/24"],
},
},
},
}
```
This is disabled by default. It applies only to fresh `role: node` pairing with
no requested scopes. Operator/browser pairing and any role, scope, metadata, or
public-key change still require manual approval.
4. Verify connection:
```bash

View File

@@ -102,7 +102,7 @@ Example:
Notes:
- `allowlist` entries are glob patterns for resolved binary paths.
- `allowlist` entries are glob patterns for resolved binary paths, or bare command names for PATH-invoked commands.
- Raw shell command text that contains shell control or expansion syntax (`&&`, `||`, `;`, `|`, `` ` ``, `$`, `<`, `>`, `(`, `)`) is treated as an allowlist miss and requires explicit approval (or allowlisting the shell binary).
- Choosing “Always Allow” in the prompt adds that command to the allowlist.
- `system.run` environment overrides are filtered (drops `PATH`, `DYLD_*`, `LD_*`, `NODE_OPTIONS`, `PYTHON*`, `PERL*`, `RUBYOPT`, `SHELLOPTS`, `PS4`) and then merged with the apps environment.

View File

@@ -148,9 +148,12 @@ reserve root command names before parsing.
The important design boundary:
- discovery + config validation should work from **manifest/schema metadata**
- manifest/config validation should work from **manifest/schema metadata**
without executing plugin code
- native capability discovery may load trusted plugin entry code to build a
non-activating registry snapshot
- native runtime behavior comes from the plugin module's `register(api)` path
with `api.registrationMode === "full"`
That split lets OpenClaw validate config, explain missing/disabled plugins, and
build UI/schema hints before the full runtime is active.

View File

@@ -26,7 +26,7 @@ These are in-process OpenClaw hooks, not Codex `hooks.json` command hooks:
- `before_prompt_build`
- `before_compaction`, `after_compaction`
- `llm_input`, `llm_output`
- `after_tool_call`
- `before_tool_call`, `after_tool_call`
- `before_message_write` for mirrored transcript records
- `agent_end`
@@ -36,6 +36,9 @@ result is returned to Codex. This is separate from the public
`tool_result_persist` plugin hook, which transforms OpenClaw-owned transcript
tool-result writes.
For the plugin hook semantics themselves, see [Plugin hooks](/plugins/hooks)
and [Plugin guard behavior](/tools/plugin).
The harness is off by default. New configs should keep OpenAI model refs
canonical as `openai/gpt-*` and explicitly force
`embeddedHarness.runtime: "codex"` or `OPENCLAW_AGENT_RUNTIME=codex` when they
@@ -369,9 +372,19 @@ To opt in to Codex guardian-reviewed approvals, set `appServer.mode:
}
```
Guardian is a native Codex approval reviewer. When Codex asks to leave the sandbox, write outside the workspace, or add permissions like network access, Codex routes that approval request to a reviewer subagent instead of a human prompt. The reviewer applies Codex's risk framework and approves or denies the specific request. Use Guardian when you want more guardrails than YOLO mode but still need unattended agents to make progress.
Guardian mode uses Codex's native auto-review approval path. When Codex asks to
leave the sandbox, write outside the workspace, or add permissions like network
access, Codex routes that approval request to the native reviewer instead of a
human prompt. The reviewer applies Codex's risk framework and approves or denies
the specific request. Use Guardian when you want more guardrails than YOLO mode
but still need unattended agents to make progress.
The `guardian` preset expands to `approvalPolicy: "on-request"`, `approvalsReviewer: "guardian_subagent"`, and `sandbox: "workspace-write"`. Individual policy fields still override `mode`, so advanced deployments can mix the preset with explicit choices.
The `guardian` preset expands to `approvalPolicy: "on-request"`,
`approvalsReviewer: "auto_review"`, and `sandbox: "workspace-write"`.
Individual policy fields still override `mode`, so advanced deployments can mix
the preset with explicit choices. The older `guardian_subagent` reviewer value is
still accepted as a compatibility alias, but new configs should use
`auto_review`.
For an already-running app-server, use WebSocket transport:
@@ -397,20 +410,20 @@ For an already-running app-server, use WebSocket transport:
Supported `appServer` fields:
| Field | Default | Meaning |
| ------------------- | ---------------------------------------- | --------------------------------------------------------------------------------------------------------- |
| `transport` | `"stdio"` | `"stdio"` spawns Codex; `"websocket"` connects to `url`. |
| `command` | `"codex"` | Executable for stdio transport. |
| `args` | `["app-server", "--listen", "stdio://"]` | Arguments for stdio transport. |
| `url` | unset | WebSocket app-server URL. |
| `authToken` | unset | Bearer token for WebSocket transport. |
| `headers` | `{}` | Extra WebSocket headers. |
| `requestTimeoutMs` | `60000` | Timeout for app-server control-plane calls. |
| `mode` | `"yolo"` | Preset for YOLO or guardian-reviewed execution. |
| `approvalPolicy` | `"never"` | Native Codex approval policy sent to thread start/resume/turn. |
| `sandbox` | `"danger-full-access"` | Native Codex sandbox mode sent to thread start/resume. |
| `approvalsReviewer` | `"user"` | Use `"guardian_subagent"` to let Codex Guardian review prompts. |
| `serviceTier` | unset | Optional Codex app-server service tier: `"fast"`, `"flex"`, or `null`. Invalid legacy values are ignored. |
| Field | Default | Meaning |
| ------------------- | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| `transport` | `"stdio"` | `"stdio"` spawns Codex; `"websocket"` connects to `url`. |
| `command` | `"codex"` | Executable for stdio transport. |
| `args` | `["app-server", "--listen", "stdio://"]` | Arguments for stdio transport. |
| `url` | unset | WebSocket app-server URL. |
| `authToken` | unset | Bearer token for WebSocket transport. |
| `headers` | `{}` | Extra WebSocket headers. |
| `requestTimeoutMs` | `60000` | Timeout for app-server control-plane calls. |
| `mode` | `"yolo"` | Preset for YOLO or guardian-reviewed execution. |
| `approvalPolicy` | `"never"` | Native Codex approval policy sent to thread start/resume/turn. |
| `sandbox` | `"danger-full-access"` | Native Codex sandbox mode sent to thread start/resume. |
| `approvalsReviewer` | `"user"` | Use `"auto_review"` to let Codex review native approval prompts. `guardian_subagent` remains a legacy alias. |
| `serviceTier` | unset | Optional Codex app-server service tier: `"fast"`, `"flex"`, or `null`. Invalid legacy values are ignored. |
The older environment variables still work as fallbacks for local testing when
the matching config field is unset:
@@ -477,7 +490,7 @@ Guardian-reviewed Codex approvals:
appServer: {
mode: "guardian",
approvalPolicy: "on-request",
approvalsReviewer: "guardian_subagent",
approvalsReviewer: "auto_review",
sandbox: "workspace-write",
},
},
@@ -553,9 +566,11 @@ The Codex harness has three hook layers:
| Codex native hooks | Codex | Low-level Codex lifecycle and native tool policy from Codex config. |
OpenClaw does not use project or global Codex `hooks.json` files to route
OpenClaw plugin behavior. Codex native hooks are useful for Codex-owned
operations such as shell policy, native tool result review, stop handling, and
native compaction/model lifecycle, but they are not the OpenClaw plugin API.
OpenClaw plugin behavior. For the supported native tool and permission bridge,
OpenClaw injects per-thread Codex config for `PreToolUse`, `PostToolUse`, and
`PermissionRequest`. Other Codex hooks such as `SessionStart`,
`UserPromptSubmit`, and `Stop` remain Codex-level controls; they are not exposed
as OpenClaw plugin hooks in the v1 contract.
For OpenClaw dynamic tools, OpenClaw executes the tool after Codex asks for the
call, so OpenClaw fires the plugin and middleware behavior it owns in the
@@ -564,10 +579,9 @@ OpenClaw can mirror selected events, but it cannot rewrite the native Codex
thread unless Codex exposes that operation through app-server or native hook
callbacks.
When newer Codex app-server builds expose native compaction and model lifecycle
hook events, OpenClaw should version-gate that protocol support and map the
events into the existing OpenClaw hook contract where the semantics are honest.
Until then, OpenClaw's `before_compaction`, `after_compaction`, `llm_input`, and
Compaction and LLM lifecycle projections come from Codex app-server
notifications and OpenClaw adapter state, not native Codex hook commands.
OpenClaw's `before_compaction`, `after_compaction`, `llm_input`, and
`llm_output` events are adapter-level observations, not byte-for-byte captures
of Codex's internal request or compaction payloads.
@@ -583,31 +597,31 @@ around that boundary.
Supported in Codex runtime v1:
| Surface | Support | Why |
| --------------------------------------- | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| OpenAI model loop through Codex | Supported | Codex app-server owns the OpenAI turn, native thread resume, and native tool continuation. |
| OpenClaw channel routing and delivery | Supported | Telegram, Discord, Slack, WhatsApp, iMessage, and other channels stay outside the model runtime. |
| OpenClaw dynamic tools | Supported | Codex asks OpenClaw to execute these tools, so OpenClaw stays in the execution path. |
| Prompt and context plugins | Supported | OpenClaw builds prompt overlays and projects context into the Codex turn before starting or resuming the thread. |
| Context engine lifecycle | Supported | Assemble, ingest or after-turn maintenance, and context-engine compaction coordination run for Codex turns. |
| Dynamic tool hooks | Supported | `before_tool_call`, `after_tool_call`, and tool-result middleware run around OpenClaw-owned dynamic tools. |
| Lifecycle hooks | Supported as adapter observations | `llm_input`, `llm_output`, `agent_end`, `before_compaction`, and `after_compaction` fire with honest Codex-mode payloads. |
| Native shell and patch block or observe | Supported through the native hook relay | Codex `PreToolUse` and `PostToolUse` are relayed for supported Codex-native tools. Blocking is supported; argument rewriting is not. |
| Native permission policy | Supported through the native hook relay | Codex `PermissionRequest` can be routed through OpenClaw policy where the runtime exposes it. |
| App-server trajectory capture | Supported | OpenClaw records the request it sent to app-server and the app-server notifications it receives. |
| Surface | Support | Why |
| --------------------------------------- | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| OpenAI model loop through Codex | Supported | Codex app-server owns the OpenAI turn, native thread resume, and native tool continuation. |
| OpenClaw channel routing and delivery | Supported | Telegram, Discord, Slack, WhatsApp, iMessage, and other channels stay outside the model runtime. |
| OpenClaw dynamic tools | Supported | Codex asks OpenClaw to execute these tools, so OpenClaw stays in the execution path. |
| Prompt and context plugins | Supported | OpenClaw builds prompt overlays and projects context into the Codex turn before starting or resuming the thread. |
| Context engine lifecycle | Supported | Assemble, ingest or after-turn maintenance, and context-engine compaction coordination run for Codex turns. |
| Dynamic tool hooks | Supported | `before_tool_call`, `after_tool_call`, and tool-result middleware run around OpenClaw-owned dynamic tools. |
| Lifecycle hooks | Supported as adapter observations | `llm_input`, `llm_output`, `agent_end`, `before_compaction`, and `after_compaction` fire with honest Codex-mode payloads. |
| Native shell and patch block or observe | Supported through the native hook relay | Codex `PreToolUse` and `PostToolUse` are relayed for the committed native tool surfaces. Blocking is supported; argument rewriting is not. |
| Native permission policy | Supported through the native hook relay | Codex `PermissionRequest` can be routed through OpenClaw policy where the runtime exposes it. |
| App-server trajectory capture | Supported | OpenClaw records the request it sent to app-server and the app-server notifications it receives. |
Not supported in Codex runtime v1:
| Surface | V1 Boundary | Future Path |
| --------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
| Native tool argument mutation | Codex native pre-tool hooks can block, but OpenClaw does not rewrite Codex-native tool arguments. | Requires Codex hook/schema support for replacement tool input. |
| Editable Codex-native transcript history | Codex owns canonical native thread history. OpenClaw owns a mirror and can project future context, but should not mutate unsupported internals. | Add explicit Codex app-server APIs if native thread surgery is needed. |
| `tool_result_persist` for Codex-native tool records | That hook transforms OpenClaw-owned transcript writes, not Codex-native tool records. | Could mirror transformed records, but canonical rewrite needs Codex support. |
| Rich native compaction metadata | OpenClaw observes compaction start and completion, but does not receive a stable kept/dropped list, token delta, or summary payload. | Needs richer Codex compaction events. |
| Compaction intervention | Current OpenClaw compaction hooks are notification-level in Codex mode. | Add Codex pre/post compaction hooks if plugins need to veto or rewrite native compaction. |
| Stop or final-answer gating | Codex has native stop hooks, but OpenClaw does not expose final-answer gating as a v1 plugin contract. | Future opt-in primitive with loop and timeout safeguards. |
| Native MCP hook parity | Codex owns MCP execution, and full pre/post hook payload parity depends on Codex MCP handler support. | Add Codex MCP hook payloads, then relay them through the same native hook path. |
| Byte-for-byte model API request capture | OpenClaw can capture app-server requests and notifications, but Codex core builds the final OpenAI API request internally. | Needs a Codex model-request tracing event or debug API. |
| Surface | V1 boundary | Future path |
| --------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
| Native tool argument mutation | Codex native pre-tool hooks can block, but OpenClaw does not rewrite Codex-native tool arguments. | Requires Codex hook/schema support for replacement tool input. |
| Editable Codex-native transcript history | Codex owns canonical native thread history. OpenClaw owns a mirror and can project future context, but should not mutate unsupported internals. | Add explicit Codex app-server APIs if native thread surgery is needed. |
| `tool_result_persist` for Codex-native tool records | That hook transforms OpenClaw-owned transcript writes, not Codex-native tool records. | Could mirror transformed records, but canonical rewrite needs Codex support. |
| Rich native compaction metadata | OpenClaw observes compaction start and completion, but does not receive a stable kept/dropped list, token delta, or summary payload. | Needs richer Codex compaction events. |
| Compaction intervention | Current OpenClaw compaction hooks are notification-level in Codex mode. | Add Codex pre/post compaction hooks if plugins need to veto or rewrite native compaction. |
| Stop or final-answer gating | Codex has native stop hooks, but OpenClaw does not expose final-answer gating as a v1 plugin contract. | Future opt-in primitive with loop and timeout safeguards. |
| Native MCP hook parity as a committed v1 surface | The relay is generic, but OpenClaw has not version-gated and tested native MCP pre/post hook behavior end to end. | Add OpenClaw MCP relay tests and docs once the supported app-server protocol floor covers those payloads. |
| Byte-for-byte model API request capture | OpenClaw can capture app-server requests and notifications, but Codex core builds the final OpenAI API request internally. | Needs a Codex model-request tracing event or debug API. |
## Tools, media, and compaction
@@ -617,6 +631,11 @@ OpenClaw still builds the tool list and receives dynamic tool results from the
harness. Text, images, video, music, TTS, approvals, and messaging-tool output
continue through the normal OpenClaw delivery path.
The native hook relay is intentionally generic, but the v1 support contract is
limited to the Codex-native tool and permission paths that OpenClaw tests. Do not
assume every future Codex hook event is an OpenClaw plugin surface until the
runtime contract names it.
Codex MCP tool approval elicitations are routed through OpenClaw's plugin
approval flow when Codex marks `_meta.codex_approval_kind` as
`"mcp_tool_call"`. Codex `request_user_input` prompts are sent back to the
@@ -644,9 +663,11 @@ understanding continue to use the matching provider/model settings such as
## Troubleshooting
**Codex does not appear in `/model`:** enable `plugins.entries.codex.enabled`,
select an `openai/gpt-*` model with `embeddedHarness.runtime: "codex"` (or a
legacy `codex/*` ref), and check whether `plugins.allow` excludes `codex`.
**Codex does not appear as a normal `/model` provider:** that is expected for
new configs. Select an `openai/gpt-*` model with
`embeddedHarness.runtime: "codex"` (or a legacy `codex/*` ref), enable
`plugins.entries.codex.enabled`, and check whether `plugins.allow` excludes
`codex`.
**OpenClaw uses PI instead of Codex:** `runtime: "auto"` can still use PI as the
compatibility backend when no Codex harness claims the run. Set
@@ -672,8 +693,11 @@ turn for that agent must be a Codex-supported OpenAI model.
## Related
- [Agent Harness Plugins](/plugins/sdk-agent-harness)
- [Agent harness plugins](/plugins/sdk-agent-harness)
- [Agent runtimes](/concepts/agent-runtimes)
- [Model Providers](/concepts/model-providers)
- [Configuration Reference](/gateway/configuration-reference)
- [Model providers](/concepts/model-providers)
- [OpenAI provider](/providers/openai)
- [Status](/cli/status)
- [Plugin hooks](/plugins/hooks)
- [Configuration reference](/gateway/configuration-reference)
- [Testing](/help/testing-live#live-codex-app-server-harness-smoke)

View File

@@ -0,0 +1,98 @@
---
summary: "Plugin compatibility contracts, deprecation metadata, and migration expectations"
title: "Plugin compatibility"
read_when:
- You maintain an OpenClaw plugin
- You see a plugin compatibility warning
- You are planning a plugin SDK or manifest migration
---
OpenClaw keeps older plugin contracts wired through named compatibility
adapters before removing them. This protects existing bundled and external
plugins while the SDK, manifest, setup, config, and agent runtime contracts
evolve.
## Compatibility registry
Plugin compatibility contracts are tracked in the core registry at
`src/plugins/compat/registry.ts`.
Each record has:
- a stable compatibility code
- status: `active`, `deprecated`, `removal-pending`, or `removed`
- owner: SDK, config, setup, channel, provider, plugin execution, agent runtime,
or core
- introduction and deprecation dates when applicable
- replacement guidance
- docs, diagnostics, and tests that cover the old and new behavior
The registry is the source for maintainer planning and future plugin inspector
checks. If a plugin-facing behavior changes, add or update the compatibility
record in the same change that adds the adapter.
## Plugin inspector package
The plugin inspector should live outside the core OpenClaw repo as a separate
package/repository backed by the versioned compatibility and manifest
contracts.
The day-one CLI should be:
```sh
openclaw-plugin-inspector ./my-plugin
```
It should emit:
- manifest/schema validation
- the contract compatibility version being checked
- install/source metadata checks
- cold-path import checks
- deprecation and compatibility warnings
Use `--json` for stable machine-readable output in CI annotations. OpenClaw
core should expose contracts and fixtures the inspector can consume, but should
not publish the inspector binary from the main `openclaw` package.
## Deprecation policy
OpenClaw should not remove a documented plugin contract in the same release
that introduces its replacement.
The migration sequence is:
1. Add the new contract.
2. Keep the old behavior wired through a named compatibility adapter.
3. Emit diagnostics or warnings when plugin authors can act.
4. Document the replacement and timeline.
5. Test both old and new paths.
6. Wait through the announced migration window.
7. Remove only with explicit breaking-release approval.
Deprecated records must include a warning start date, replacement, docs link,
and target removal date when known.
## Current compatibility areas
Current compatibility records include:
- legacy broad SDK imports such as `openclaw/plugin-sdk/compat`
- legacy hook-only plugin shapes and `before_agent_start`
- bundled plugin allowlist and enablement behavior
- legacy provider/channel env-var manifest metadata
- activation hints that are being replaced by manifest contribution ownership
- `embeddedHarness` and `agent-harness` naming aliases while public naming moves
toward `agentRuntime`
- generated bundled channel config metadata fallback while registry-first
`channelConfigs` metadata lands
New plugin code should prefer the replacement listed in the registry and in the
specific migration guide. Existing plugins can keep using a compatibility path
until the docs, diagnostics, and release notes announce a removal window.
## Release notes
Release notes should include upcoming plugin deprecations with target dates and
links to migration docs. That warning needs to happen before a compatibility
path moves to `removal-pending` or `removed`.

View File

@@ -119,6 +119,10 @@ openclaw googlemeet create --no-join
the OpenClaw Chrome profile on the node to already be signed in to Google.
Browser automation handles Meet's own first-run microphone prompt; that prompt
is not treated as a Google login failure.
Join and create flows also try to reuse an existing Meet tab before opening a
new one. Matching ignores harmless URL query strings such as `authuser`, so an
agent retry should focus the already-open meeting instead of creating a second
Chrome tab.
The command/tool output includes a `source` field (`api` or `browser`) so agents
can explain which path was used. `create` joins the new meeting by default and
@@ -141,12 +145,20 @@ For an observe-only/browser-control join, set `"mode": "transcribe"`. That does
not start the duplex realtime model bridge, so it will not talk back into the
meeting.
During realtime sessions, `google_meet` status includes browser and audio bridge
health such as `inCall`, `manualActionRequired`, `providerConnected`,
`realtimeReady`, `audioInputActive`, `audioOutputActive`, last input/output
timestamps, byte counters, and bridge closed state. If a safe Meet page prompt
appears, browser automation handles it when it can. Login, host admission, and
browser/OS permission prompts are reported as manual action with a reason and
message for the agent to relay.
Chrome joins as the signed-in Chrome profile. In Meet, pick `BlackHole 2ch` for
the microphone/speaker path used by OpenClaw. For clean duplex audio, use
separate virtual devices or a Loopback-style graph; a single BlackHole device is
enough for a first smoke test but can echo.
### Local Gateway + Parallels Chrome
### Local gateway + Parallels Chrome
You do **not** need a full OpenClaw Gateway or model API key inside a macOS VM
just to make the VM own Chrome. Run the Gateway and agent locally, then run a
@@ -425,8 +437,51 @@ OAuth is optional for creating a Meet link because `googlemeet create` can fall
back to browser automation. Configure OAuth when you want official API create,
space resolution, or Meet Media API preflight checks.
Google Meet API access uses a personal OAuth client first. Configure
`oauth.clientId` and optionally `oauth.clientSecret`, then run:
Google Meet API access uses user OAuth: create a Google Cloud OAuth client,
request the required scopes, authorize a Google account, then store the
resulting refresh token in the Google Meet plugin config or provide the
`OPENCLAW_GOOGLE_MEET_*` environment variables.
OAuth does not replace the Chrome join path. Chrome and Chrome-node transports
still join through a signed-in Chrome profile, BlackHole/SoX, and a connected
node when you use browser participation. OAuth is only for the official Google
Meet API path: create meeting spaces, resolve spaces, and run Meet Media API
preflight checks.
### Create Google credentials
In Google Cloud Console:
1. Create or select a Google Cloud project.
2. Enable **Google Meet REST API** for that project.
3. Configure the OAuth consent screen.
- **Internal** is simplest for a Google Workspace organization.
- **External** works for personal/test setups; while the app is in Testing,
add each Google account that will authorize the app as a test user.
4. Add the scopes OpenClaw requests:
- `https://www.googleapis.com/auth/meetings.space.created`
- `https://www.googleapis.com/auth/meetings.space.readonly`
- `https://www.googleapis.com/auth/meetings.conference.media.readonly`
5. Create an OAuth client ID.
- Application type: **Web application**.
- Authorized redirect URI:
```text
http://localhost:8085/oauth2callback
```
6. Copy the client ID and client secret.
`meetings.space.created` is required by Google Meet `spaces.create`.
`meetings.space.readonly` lets OpenClaw resolve Meet URLs/codes to spaces.
`meetings.conference.media.readonly` is for Meet Media API preflight and media
work; Google may require Developer Preview enrollment for actual Media API use.
If you only need browser-based Chrome joins, skip OAuth entirely.
### Mint the refresh token
Configure `oauth.clientId` and optionally `oauth.clientSecret`, or pass them as
environment variables, then run:
```bash
openclaw googlemeet auth login --json
@@ -436,11 +491,116 @@ The command prints an `oauth` config block with a refresh token. It uses PKCE,
localhost callback on `http://localhost:8085/oauth2callback`, and a manual
copy/paste flow with `--manual`.
Examples:
```bash
OPENCLAW_GOOGLE_MEET_CLIENT_ID="your-client-id" \
OPENCLAW_GOOGLE_MEET_CLIENT_SECRET="your-client-secret" \
openclaw googlemeet auth login --json
```
Use manual mode when the browser cannot reach the local callback:
```bash
OPENCLAW_GOOGLE_MEET_CLIENT_ID="your-client-id" \
OPENCLAW_GOOGLE_MEET_CLIENT_SECRET="your-client-secret" \
openclaw googlemeet auth login --json --manual
```
The JSON output includes:
```json
{
"oauth": {
"clientId": "your-client-id",
"clientSecret": "your-client-secret",
"refreshToken": "refresh-token",
"accessToken": "access-token",
"expiresAt": 1770000000000
},
"scope": "..."
}
```
Store the `oauth` object under the Google Meet plugin config:
```json5
{
plugins: {
entries: {
"google-meet": {
enabled: true,
config: {
oauth: {
clientId: "your-client-id",
clientSecret: "your-client-secret",
refreshToken: "refresh-token",
},
},
},
},
},
}
```
Prefer environment variables when you do not want the refresh token in config.
If both config and environment values are present, the plugin resolves config
first and then environment fallback.
The OAuth consent includes Meet space creation, Meet space read access, and Meet
conference media read access. If you authenticated before meeting creation
support existed, rerun `openclaw googlemeet auth login --json` so the refresh
token has the `meetings.space.created` scope.
### Verify OAuth with doctor
Run the OAuth doctor when you want a fast, non-secret health check:
```bash
openclaw googlemeet doctor --oauth --json
```
This does not load the Chrome runtime or require a connected Chrome node. It
checks that OAuth config exists and that the refresh token can mint an access
token. The JSON report includes only status fields such as `ok`, `configured`,
`tokenSource`, `expiresAt`, and check messages; it does not print the access
token, refresh token, or client secret.
Common results:
| Check | Meaning |
| -------------------- | --------------------------------------------------------------------------------------- |
| `oauth-config` | `oauth.clientId` plus `oauth.refreshToken`, or a cached access token, is present. |
| `oauth-token` | The cached access token is still valid, or the refresh token minted a new access token. |
| `meet-spaces-get` | Optional `--meeting` check resolved an existing Meet space. |
| `meet-spaces-create` | Optional `--create-space` check created a new Meet space. |
To prove Google Meet API enablement and `spaces.create` scope as well, run the
side-effecting create check:
```bash
openclaw googlemeet doctor --oauth --create-space --json
openclaw googlemeet create --no-join --json
```
`--create-space` creates a throwaway Meet URL. Use it when you need to confirm
that the Google Cloud project has the Meet API enabled and that the authorized
account has the `meetings.space.created` scope.
To prove read access for an existing meeting space:
```bash
openclaw googlemeet doctor --oauth --meeting https://meet.google.com/abc-defg-hij --json
openclaw googlemeet resolve-space --meeting https://meet.google.com/abc-defg-hij
```
`doctor --oauth --meeting` and `resolve-space` prove read access to an existing
space that the authorized Google account can access. A `403` from these checks
usually means the Google Meet REST API is disabled, the consented refresh token
is missing the required scope, or the Google account cannot access that Meet
space. A refresh-token error means rerun `openclaw googlemeet auth login
--json` and store the new `oauth` block.
No OAuth credentials are needed for the browser fallback. In that mode, Google
auth comes from the signed-in Chrome profile on the selected node, not from
OpenClaw config.
@@ -468,6 +628,37 @@ Run preflight before media work:
openclaw googlemeet preflight --meeting https://meet.google.com/abc-defg-hij
```
List meeting artifacts and attendance after Meet has created conference records:
```bash
openclaw googlemeet artifacts --meeting https://meet.google.com/abc-defg-hij
openclaw googlemeet attendance --meeting https://meet.google.com/abc-defg-hij
```
If you already know the conference record id, address it directly:
```bash
openclaw googlemeet artifacts --conference-record conferenceRecords/abc123 --json
openclaw googlemeet attendance --conference-record conferenceRecords/abc123 --json
```
Write a readable report:
```bash
openclaw googlemeet artifacts --conference-record conferenceRecords/abc123 \
--format markdown --output meet-artifacts.md
openclaw googlemeet attendance --conference-record conferenceRecords/abc123 \
--format markdown --output meet-attendance.md
```
`artifacts` returns conference record metadata plus participant, recording,
transcript, structured transcript-entry, and smart-note resource metadata when
Google exposes it for the meeting. Use `--no-transcript-entries` to skip
entry lookup for large meetings. `attendance` expands participants into
participant-session rows with join/leave timestamps. These commands use the Meet
REST API only; Google Docs/Drive document body download is intentionally out of
scope because that requires separate Google Docs/Drive access.
Create a fresh Meet space:
```bash
@@ -500,6 +691,30 @@ Example JSON output from the browser fallback:
}
```
If the browser fallback hits Google login or a Meet permission blocker before it
can create the URL, the Gateway method returns a failed response and the
`google_meet` tool returns structured details instead of a plain string:
```json
{
"source": "browser",
"error": "google-login-required: Sign in to Google in the OpenClaw browser profile, then retry meeting creation.",
"manualActionRequired": true,
"manualActionReason": "google-login-required",
"manualActionMessage": "Sign in to Google in the OpenClaw browser profile, then retry meeting creation.",
"browser": {
"nodeId": "ba0f4e4bc...",
"targetId": "tab-1",
"browserUrl": "https://accounts.google.com/signin",
"browserTitle": "Sign in - Google Accounts"
}
}
```
When an agent sees `manualActionRequired: true`, it should report the
`manualActionMessage` plus the browser node/tab context and stop opening new
Meet tabs until the operator completes the browser step.
Example JSON output from API create:
```json
@@ -876,6 +1091,10 @@ to the pinned Chrome node browser. Confirm:
- For browser fallback: retries reuse an existing `https://meet.google.com/new`
or Google account prompt tab before opening a new tab. If an agent times out,
retry the tool call rather than manually opening another Meet tab.
- For browser fallback: if the tool returns `manualActionRequired: true`, use
the returned `browser.nodeId`, `browser.targetId`, `browserUrl`, and
`manualActionMessage` to guide the operator. Do not retry in a loop until that
action is complete.
- For browser fallback: if Meet shows "Do you want people to hear you in the
meeting?", leave the tab open. OpenClaw should click **Use microphone** or, for
create-only fallback, **Continue without microphone** through browser
@@ -888,7 +1107,7 @@ Check the realtime path:
```bash
openclaw googlemeet setup
openclaw googlemeet status
openclaw googlemeet doctor
```
Use `mode: "realtime"` for listen/talk-back. `mode: "transcribe"` intentionally
@@ -903,6 +1122,28 @@ Also verify:
- Meet microphone and speaker are routed through the virtual audio path used by
OpenClaw.
`googlemeet doctor [session-id]` prints the session, node, in-call state,
manual action reason, realtime provider connection, `realtimeReady`, audio
input/output activity, last audio timestamps, byte counters, and browser URL.
Use `googlemeet status [session-id]` when you need the raw JSON. Use
`googlemeet doctor --oauth` when you need to verify Google Meet OAuth refresh
without exposing tokens; add `--meeting` or `--create-space` when you need a
Google Meet API proof as well.
If an agent timed out and you can see a Meet tab already open, inspect that tab
without opening another one:
```bash
openclaw googlemeet recover-tab
openclaw googlemeet recover-tab https://meet.google.com/abc-defg-hij
```
The equivalent tool action is `recover_current_tab`. It focuses and inspects an
existing Meet tab on the configured Chrome node. It does not open a new tab or
create a new session; it reports the current blocker, such as login, admission,
permissions, or audio-choice state. The CLI command talks to the configured
Gateway, so the Gateway must be running and the Chrome node must be connected.
### Twilio setup checks fail
`twilio-voice-call-plugin` fails when `voice-call` is not allowed or not enabled.
@@ -922,6 +1163,21 @@ Then restart or reload the Gateway and run:
```bash
openclaw googlemeet setup
openclaw voicecall setup
openclaw voicecall smoke
```
`voicecall smoke` is readiness-only by default. To dry-run a specific number:
```bash
openclaw voicecall smoke --to "+15555550123"
```
Only add `--yes` when you intentionally want to place a live outbound notify
call:
```bash
openclaw voicecall smoke --to "+15555550123" --yes
```
### Twilio call starts but never enters the meeting

View File

@@ -190,6 +190,11 @@ Use message hooks for channel-level routing and delivery policy:
- `message_sending`: rewrite `content` or return `{ cancel: true }`.
- `message_sent`: observe final success or failure.
For audio-only TTS replies, `content` may contain the hidden spoken transcript
even when the channel payload has no visible text/caption. Rewriting that
`content` updates the hook-visible transcript only; it is not rendered as a
media caption.
Message hook contexts expose stable correlation fields when available:
`ctx.sessionKey`, `ctx.runId`, `ctx.messageId`, `ctx.senderId`, `ctx.trace`,
`ctx.traceId`, `ctx.spanId`, `ctx.parentSpanId`, and `ctx.callDepth`. Prefer

View File

@@ -143,6 +143,7 @@ or npm install metadata. Those belong in your plugin code and `package.json`.
| `providers` | No | `string[]` | Provider ids owned by this plugin. |
| `providerDiscoveryEntry` | No | `string` | Lightweight provider-discovery module path, relative to the plugin root, for manifest-scoped provider catalog metadata that can be loaded without activating the full plugin runtime. |
| `modelSupport` | No | `object` | Manifest-owned shorthand model-family metadata used to auto-load the plugin before runtime. |
| `modelCatalog` | No | `object` | Declarative model catalog metadata for providers owned by this plugin. This is the control-plane contract for future read-only listing, onboarding, model pickers, aliases, and suppression without loading plugin runtime. |
| `providerEndpoints` | No | `object[]` | Manifest-owned endpoint host/baseUrl metadata for provider routes that core must classify before provider runtime loads. |
| `cliBackends` | No | `string[]` | CLI inference backend ids owned by this plugin. Used for startup auto-activation from explicit config refs. |
| `syntheticAuthRefs` | No | `string[]` | Provider or CLI backend refs whose plugin-owned synthetic auth hook should be probed during cold model discovery before runtime loads. |
@@ -583,6 +584,105 @@ Fields:
| `modelPrefixes` | `string[]` | Prefixes matched with `startsWith` against shorthand model ids. |
| `modelPatterns` | `string[]` | Regex sources matched against shorthand model ids after profile suffix removal. |
## modelCatalog reference
Use `modelCatalog` when OpenClaw should know provider model metadata before
loading plugin runtime. This is the manifest-owned source for fixed catalog
rows, provider aliases, suppression rules, and discovery mode. Runtime refresh
still belongs in provider runtime code, but the manifest tells core when runtime
is required.
```json
{
"providers": ["openai"],
"modelCatalog": {
"providers": {
"openai": {
"baseUrl": "https://api.openai.com/v1",
"api": "openai-responses",
"models": [
{
"id": "gpt-5.4",
"name": "GPT-5.4",
"input": ["text", "image"],
"reasoning": true,
"contextWindow": 256000,
"maxTokens": 128000,
"cost": {
"input": 1.25,
"output": 10,
"cacheRead": 0.125
},
"status": "available",
"tags": ["default"]
}
]
}
},
"aliases": {
"azure-openai-responses": {
"provider": "openai",
"api": "azure-openai-responses"
}
},
"suppressions": [
{
"provider": "azure-openai-responses",
"model": "gpt-5.3-codex-spark",
"reason": "not available on Azure OpenAI Responses"
}
],
"discovery": {
"openai": "static"
}
}
}
```
Top-level fields:
| Field | Type | What it means |
| -------------- | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| `providers` | `Record<string, object>` | Catalog rows for provider ids owned by this plugin. Keys should also appear in top-level `providers`. |
| `aliases` | `Record<string, object>` | Provider aliases that should resolve to an owned provider for catalog or suppression planning. |
| `suppressions` | `object[]` | Model rows from another source that this plugin suppresses for a provider-specific reason. |
| `discovery` | `Record<string, "static" \| "refreshable" \| "runtime">` | Whether the provider catalog can be read from manifest metadata, refreshed into cache, or requires runtime. |
Provider fields:
| Field | Type | What it means |
| --------- | ------------------------ | ----------------------------------------------------------------- |
| `baseUrl` | `string` | Optional default base URL for models in this provider catalog. |
| `api` | `ModelApi` | Optional default API adapter for models in this provider catalog. |
| `headers` | `Record<string, string>` | Optional static headers that apply to this provider catalog. |
| `models` | `object[]` | Required model rows. Rows without an `id` are ignored. |
Model fields:
| Field | Type | What it means |
| --------------- | -------------------------------------------------------------- | --------------------------------------------------------------------------- |
| `id` | `string` | Provider-local model id, without the `provider/` prefix. |
| `name` | `string` | Optional display name. |
| `api` | `ModelApi` | Optional per-model API override. |
| `baseUrl` | `string` | Optional per-model base URL override. |
| `headers` | `Record<string, string>` | Optional per-model static headers. |
| `input` | `Array<"text" \| "image" \| "document">` | Modalities the model accepts. |
| `reasoning` | `boolean` | Whether the model exposes reasoning behavior. |
| `contextWindow` | `number` | Native provider context window. |
| `contextTokens` | `number` | Optional effective runtime context cap when different from `contextWindow`. |
| `maxTokens` | `number` | Maximum output tokens when known. |
| `cost` | `object` | Optional USD per million token pricing, including optional `tieredPricing`. |
| `compat` | `object` | Optional compatibility flags matching OpenClaw model config compatibility. |
| `status` | `"available"` \| `"preview"` \| `"deprecated"` \| `"disabled"` | Listing status. Suppress only when the row must not appear at all. |
| `statusReason` | `string` | Optional reason shown with non-available status. |
| `replaces` | `string[]` | Older provider-local model ids this model supersedes. |
| `replacedBy` | `string` | Replacement provider-local model id for deprecated rows. |
| `tags` | `string[]` | Stable tags used by pickers and filters. |
Do not put runtime-only data in `modelCatalog`. If a provider needs account
state, an API request, or local process discovery to know the complete model
set, declare that provider as `refreshable` or `runtime` in `discovery`.
Legacy top-level capability keys are deprecated. Use `openclaw doctor --fix` to
move `speechProviders`, `realtimeTranscriptionProviders`,
`realtimeVoiceProviders`, `mediaUnderstandingProviders`,

View File

@@ -152,6 +152,14 @@ paths and should return the channel metadata, setup-safe config adapter, status
adapter, and channel secret target metadata needed for those summaries. Do not
start clients, listeners, or transport runtimes from the setup entry.
Keep the main channel entry import path narrow too. Discovery can evaluate the
entry and the channel plugin module to register capabilities without activating
the channel. Files such as `channel-plugin-api.ts` should export the channel
plugin object without importing setup wizards, transport clients, socket
listeners, subprocess launchers, or service startup modules. Put those runtime
pieces in modules loaded from `registerFull(...)`, runtime setters, or lazy
capability adapters.
`createOptionalChannelSetupWizard`, `DEFAULT_ACCOUNT_ID`,
`createTopLevelChannelDmPolicy`, `setSetupChannelEnabled`, and
`splitSetupEntries`
@@ -466,6 +474,14 @@ should use `resolveInboundMentionDecision({ facts, policy })`.
You can also pass raw adapter objects instead of the declarative options
if you need full control.
Raw outbound adapters may define a `chunker(text, limit, ctx)` function.
The optional `ctx.formatting` carries delivery-time formatting decisions
such as `maxLinesPerMessage`; apply it before sending so reply threading
and chunk boundaries are resolved once by shared outbound delivery.
Send contexts also include `replyToIdSource` (`implicit` or `explicit`)
when a native reply target was resolved, so payload helpers can preserve
explicit reply tags without consuming an implicit single-use reply slot.
</Accordion>
</Step>

View File

@@ -121,11 +121,16 @@ export default defineChannelPluginEntry({
- `setRuntime` is called during registration so you can store the runtime reference
(typically via `createPluginRuntimeStore`). It is skipped during CLI metadata
capture.
- `registerCliMetadata` runs during both `api.registrationMode === "cli-metadata"`
and `api.registrationMode === "full"`.
- `registerCliMetadata` runs during `api.registrationMode === "cli-metadata"`,
`api.registrationMode === "discovery"`, and
`api.registrationMode === "full"`.
Use it as the canonical place for channel-owned CLI descriptors so root help
stays non-activating while normal CLI command registration remains compatible
with full plugin loads.
stays non-activating, discovery snapshots include static command metadata, and
normal CLI command registration remains compatible with full plugin loads.
- Discovery registration is non-activating, not import-free. OpenClaw may
evaluate the trusted plugin entry and channel plugin module to build the
snapshot, so keep top-level imports side-effect-free and put sockets,
clients, workers, and services behind `"full"`-only paths.
- `registerFull` only runs when `api.registrationMode === "full"`. It is skipped
during setup-only loading.
- Like `definePluginEntry`, `configSchema` can be a lazy factory and OpenClaw
@@ -197,19 +202,24 @@ setter before the full channel entry loads.
`api.registrationMode` tells your plugin how it was loaded:
| Mode | When | What to register |
| ----------------- | --------------------------------- | ----------------------------------------------------------------------------------------- |
| `"full"` | Normal gateway startup | Everything |
| `"setup-only"` | Disabled/unconfigured channel | Channel registration only |
| `"setup-runtime"` | Setup flow with runtime available | Channel registration plus only the lightweight runtime needed before the full entry loads |
| `"cli-metadata"` | Root help / CLI metadata capture | CLI descriptors only |
| Mode | When | What to register |
| ----------------- | --------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `"full"` | Normal gateway startup | Everything |
| `"discovery"` | Read-only capability discovery | Channel registration plus static CLI descriptors; entry code may load, but skip sockets, workers, clients, and services |
| `"setup-only"` | Disabled/unconfigured channel | Channel registration only |
| `"setup-runtime"` | Setup flow with runtime available | Channel registration plus only the lightweight runtime needed before the full entry loads |
| `"cli-metadata"` | Root help / CLI metadata capture | CLI descriptors only |
`defineChannelPluginEntry` handles this split automatically. If you use
`definePluginEntry` directly for a channel, check mode yourself:
```typescript
register(api) {
if (api.registrationMode === "cli-metadata" || api.registrationMode === "full") {
if (
api.registrationMode === "cli-metadata" ||
api.registrationMode === "discovery" ||
api.registrationMode === "full"
) {
api.registerCli(/* ... */);
if (api.registrationMode === "cli-metadata") return;
}
@@ -222,6 +232,13 @@ register(api) {
}
```
Discovery mode builds a non-activating registry snapshot. It may still evaluate
the plugin entry and the channel plugin object so OpenClaw can register channel
capabilities and static CLI descriptors. Treat module evaluation in discovery as
trusted but lightweight: no network clients, subprocesses, listeners, database
connections, background workers, credential reads, or other live runtime side
effects at top level.
Treat `"setup-runtime"` as the window where setup-only startup surfaces must
exist without re-entering the full bundled channel runtime. Good fits are
channel registration, setup-safe HTTP routes, setup-safe gateway methods, and
@@ -234,6 +251,10 @@ For CLI registrars specifically:
want OpenClaw to lazy-load the real CLI module on first invocation
- make sure those descriptors cover every top-level command root exposed by the
registrar
- keep descriptor command names to letters, numbers, hyphen, and underscore,
starting with a letter or number; OpenClaw rejects descriptor names outside
that shape and strips terminal control sequences from descriptions before
rendering help
- use `commands` alone only for eager compatibility paths
## Plugin shapes

View File

@@ -262,7 +262,7 @@ releases.
| `plugin-sdk/inbound-reply-dispatch` | Inbound reply helpers | Shared record-and-dispatch helpers |
| `plugin-sdk/messaging-targets` | Messaging target parsing | Target parsing/matching helpers |
| `plugin-sdk/outbound-media` | Outbound media helpers | Shared outbound media loading |
| `plugin-sdk/outbound-runtime` | Outbound runtime helpers | Outbound identity/send delegate and payload planning helpers |
| `plugin-sdk/outbound-runtime` | Outbound runtime helpers | Outbound delivery, identity/send delegate, session, formatting, and payload planning helpers |
| `plugin-sdk/thread-bindings-runtime` | Thread-binding helpers | Thread-binding lifecycle and adapter helpers |
| `plugin-sdk/agent-media-payload` | Legacy media payload helpers | Agent media payload builder for legacy field layouts |
| `plugin-sdk/channel-runtime` | Deprecated compatibility shim | Legacy channel runtime utilities only |

View File

@@ -122,8 +122,8 @@ await api.runtime.subagent.deleteSession({
### `api.runtime.nodes`
List connected nodes and invoke a node-host command from Gateway-loaded plugin
code. Use this when a plugin owns local work on a paired device, for example a
browser or audio bridge on another Mac.
code or from plugin CLI commands. Use this when a plugin owns local work on a
paired device, for example a browser or audio bridge on another Mac.
```typescript
const { nodes } = await api.runtime.nodes.list({ connected: true });
@@ -136,7 +136,9 @@ const result = await api.runtime.nodes.invoke({
});
```
This runtime is only available inside the Gateway. Node commands still go
Inside the Gateway this runtime is in-process. In plugin CLI commands it calls
the configured Gateway over RPC, so commands such as `openclaw googlemeet
recover-tab` can inspect paired nodes from the terminal. Node commands still go
through normal Gateway node pairing, command allowlists, and node-local command
handling.
@@ -460,6 +462,6 @@ Beyond `api.runtime`, the API object also provides:
## Related
- [SDK Overview](/plugins/sdk-overview) -- subpath reference
- [SDK Entry Points](/plugins/sdk-entrypoints) -- `definePluginEntry` options
- [Plugin Internals](/plugins/architecture) -- capability model and registry
- [SDK overview](/plugins/sdk-overview) subpath reference
- [SDK entry points](/plugins/sdk-entrypoints) `definePluginEntry` options
- [Plugin internals](/plugins/architecture) capability model and registry

View File

@@ -566,6 +566,6 @@ startup installs; keep using the explicit plugin installer.
## Related
- [SDK Entry Points](/plugins/sdk-entrypoints) -- `definePluginEntry` and `defineChannelPluginEntry`
- [Plugin Manifest](/plugins/manifest) -- full manifest schema reference
- [Building Plugins](/plugins/building-plugins) -- step-by-step getting started guide
- [SDK entry points](/plugins/sdk-entrypoints) `definePluginEntry` and `defineChannelPluginEntry`
- [Plugin manifest](/plugins/manifest) full manifest schema reference
- [Building plugins](/plugins/building-plugins) step-by-step getting started guide

View File

@@ -50,7 +50,7 @@ For the plugin authoring guide, see [Plugin SDK overview](/plugins/sdk-overview)
| `plugin-sdk/inbound-reply-dispatch` | Shared inbound record-and-dispatch helpers |
| `plugin-sdk/messaging-targets` | Target parsing/matching helpers |
| `plugin-sdk/outbound-media` | Shared outbound media loading helpers |
| `plugin-sdk/outbound-runtime` | Outbound identity, send delegate, and payload planning helpers |
| `plugin-sdk/outbound-runtime` | Outbound delivery, identity, send delegate, session, formatting, and payload planning helpers |
| `plugin-sdk/poll-runtime` | Narrow poll normalization helpers |
| `plugin-sdk/thread-bindings-runtime` | Thread-binding lifecycle and adapter helpers |
| `plugin-sdk/agent-media-payload` | Legacy agent media payload builder |
@@ -265,7 +265,7 @@ For the plugin authoring guide, see [Plugin SDK overview](/plugins/sdk-overview)
<Accordion title="Reserved bundled-helper subpaths">
| Family | Current subpaths | Intended use |
| --- | --- | --- |
| Browser | `plugin-sdk/browser-cdp`, `plugin-sdk/browser-config-runtime`, `plugin-sdk/browser-config-support`, `plugin-sdk/browser-control-auth`, `plugin-sdk/browser-node-runtime`, `plugin-sdk/browser-profiles`, `plugin-sdk/browser-security-runtime`, `plugin-sdk/browser-setup-tools`, `plugin-sdk/browser-support` | Bundled browser plugin support helpers (`browser-support` remains the compatibility barrel) |
| Browser | `plugin-sdk/browser-cdp`, `plugin-sdk/browser-config-runtime`, `plugin-sdk/browser-config-support`, `plugin-sdk/browser-control-auth`, `plugin-sdk/browser-node-runtime`, `plugin-sdk/browser-profiles`, `plugin-sdk/browser-security-runtime`, `plugin-sdk/browser-setup-tools`, `plugin-sdk/browser-support` | Bundled browser plugin support helpers. `browser-profiles` exports `resolveBrowserConfig`, `resolveProfile`, `ResolvedBrowserConfig`, `ResolvedBrowserProfile`, and `ResolvedBrowserTabCleanupConfig` for the normalized `browser.tabCleanup` shape. `browser-support` remains the compatibility barrel. |
| Matrix | `plugin-sdk/matrix`, `plugin-sdk/matrix-helper`, `plugin-sdk/matrix-runtime-heavy`, `plugin-sdk/matrix-runtime-shared`, `plugin-sdk/matrix-runtime-surface`, `plugin-sdk/matrix-surface`, `plugin-sdk/matrix-thread-bindings` | Bundled Matrix helper/runtime surface |
| Line | `plugin-sdk/line`, `plugin-sdk/line-core`, `plugin-sdk/line-runtime`, `plugin-sdk/line-surface` | Bundled LINE helper/runtime surface |
| IRC | `plugin-sdk/irc`, `plugin-sdk/irc-surface` | Bundled IRC helper surface |

View File

@@ -6,8 +6,6 @@ read_when:
title: "Voice call plugin"
---
# Voice Call (plugin)
Voice calls for OpenClaw via a plugin. Supports outbound notifications and
multi-turn conversations with inbound policies.
@@ -141,6 +139,36 @@ Set config under `plugins.entries.voice-call.config`:
}
```
Check setup before testing with a real provider:
```bash
openclaw voicecall setup
```
The default output is readable in chat logs and terminal sessions. It checks
whether the plugin is enabled, the provider and credentials are present, webhook
exposure is configured, and only one audio mode is active. Use
`openclaw voicecall setup --json` for scripts.
For Twilio, Telnyx, and Plivo, setup must resolve to a public webhook URL. If the
configured `publicUrl`, tunnel URL, Tailscale URL, or serve fallback resolves to
loopback or private network space, setup fails instead of starting a provider
that cannot receive real carrier webhooks.
For a no-surprises smoke test, run:
```bash
openclaw voicecall smoke
openclaw voicecall smoke --to "+15555550123"
```
The second command is still a dry run. Add `--yes` to place a short outbound
notify call:
```bash
openclaw voicecall smoke --to "+15555550123" --yes
```
Notes:
- Twilio/Telnyx require a **publicly reachable** webhook URL.
@@ -448,11 +476,14 @@ streaming speech on calls. You can override it under the plugin config with the
Notes:
- Legacy `tts.<provider>` keys inside plugin config (`openai`, `elevenlabs`, `microsoft`, `edge`) are auto-migrated to `tts.providers.<provider>` on load. Prefer the `providers` shape in committed config.
- Legacy `tts.<provider>` keys inside plugin config (`openai`, `elevenlabs`, `microsoft`, `edge`) are repaired by `openclaw doctor --fix`; committed config should use `tts.providers.<provider>`.
- **Microsoft speech is ignored for voice calls** (telephony audio needs PCM; the current Microsoft transport does not expose telephony PCM output).
- Core TTS is used when Twilio media streaming is enabled; otherwise calls fall back to provider native voices.
- If a Twilio media stream is already active, Voice Call does not fall back to TwiML `<Say>`. If telephony TTS is unavailable in that state, the playback request fails instead of mixing two playback paths.
- When telephony TTS falls back to a secondary provider, Voice Call logs a warning with the provider chain (`from`, `to`, `attempts`) for debugging.
- When Twilio barge-in or stream teardown clears the pending TTS queue, queued
playback requests settle instead of hanging callers that are awaiting playback
completion.
### More examples
@@ -564,6 +595,9 @@ For outbound `conversation` calls, first-message handling is tied to live playba
- Barge-in queue clear and auto-response are suppressed only while the initial greeting is actively speaking.
- If initial playback fails, the call returns to `listening` and the initial message remains queued for retry.
- Initial playback for Twilio streaming starts on stream connect without extra delay.
- Barge-in aborts active playback and clears queued-but-not-yet-playing Twilio
TTS entries. Cleared entries resolve as skipped, so follow-up response logic
can continue without waiting on audio that will never play.
- Realtime voice conversations use the realtime stream's own opening turn. Voice Call does not post a legacy `<Say>` TwiML update for that initial message, so outbound `<Connect><Stream>` sessions stay attached.
### Twilio stream disconnect grace

View File

@@ -90,6 +90,12 @@ back on the follow-up request. OpenClaw handles this inside the DeepSeek plugin,
so normal multi-turn tool use works with `deepseek/deepseek-v4-flash` and
`deepseek/deepseek-v4-pro`.
If you switch an existing session from another OpenAI-compatible provider to a
DeepSeek V4 model, older assistant tool-call turns may not have native
DeepSeek `reasoning_content`. OpenClaw fills that missing field for DeepSeek V4
thinking requests so the provider can accept the replayed tool-call history
without requiring `/new`.
When thinking is disabled in OpenClaw (including the UI **None** selection),
OpenClaw sends DeepSeek `thinking: { type: "disabled" }` and strips replayed
`reasoning_content` from the outgoing history. This keeps disabled-thinking

View File

@@ -267,6 +267,7 @@ To use Google as the default TTS provider:
google: {
model: "gemini-3.1-flash-tts-preview",
voiceName: "Kore",
audioProfile: "Speak professionally with a calm tone.",
},
},
},
@@ -274,9 +275,14 @@ To use Google as the default TTS provider:
}
```
Gemini API TTS accepts expressive square-bracket audio tags in the text, such as
`[whispers]` or `[laughs]`. To keep tags out of the visible chat reply while
sending them to TTS, put them inside a `[[tts:text]]...[[/tts:text]]` block:
Gemini API TTS uses natural-language prompting for style control. Set
`audioProfile` to prepend a reusable style prompt before the spoken text. Set
`speakerName` when your prompt text refers to a named speaker.
Gemini API TTS also accepts expressive square-bracket audio tags in the text,
such as `[whispers]` or `[laughs]`. To keep tags out of the visible chat reply
while sending them to TTS, put them inside a `[[tts:text]]...[[/tts:text]]`
block:
```text
Here is the clean reply text.

View File

@@ -244,6 +244,29 @@ exposed separately through the plugin-owned `MiniMax-VL-01` media provider.
See [Image Generation](/tools/image-generation) for shared tool parameters, provider selection, and failover behavior.
</Note>
### Text-to-speech
The bundled `minimax` plugin registers MiniMax T2A v2 as a speech provider for
`messages.tts`.
- Default TTS model: `speech-2.8-hd`
- Default voice: `English_expressive_narrator`
- Normal audio attachments stay MP3.
- Voice-note targets such as Feishu and Telegram are transcoded from MiniMax
MP3 to 48kHz Opus with `ffmpeg`, because the Feishu/Lark file API only
accepts `file_type: "opus"` for native audio messages.
- MiniMax T2A accepts fractional `speed` and `vol`, but `pitch` is sent as an
integer; OpenClaw truncates fractional `pitch` values before the API request.
| Setting | Env var | Default | Description |
| ---------------------------------------- | ---------------------- | ----------------------------- | -------------------------------- |
| `messages.tts.providers.minimax.baseUrl` | `MINIMAX_API_HOST` | `https://api.minimax.io` | MiniMax T2A API host. |
| `messages.tts.providers.minimax.model` | `MINIMAX_TTS_MODEL` | `speech-2.8-hd` | TTS model id. |
| `messages.tts.providers.minimax.voiceId` | `MINIMAX_TTS_VOICE_ID` | `English_expressive_narrator` | Voice id used for speech output. |
| `messages.tts.providers.minimax.speed` | | `1.0` | Playback speed, `0.5..2.0`. |
| `messages.tts.providers.minimax.vol` | | `1.0` | Volume, `(0, 10]`. |
| `messages.tts.providers.minimax.pitch` | | `0` | Integer pitch shift, `-12..12`. |
### Music generation
The bundled `minimax` plugin also registers music generation through the shared

View File

@@ -15,6 +15,10 @@ OpenAI provides developer APIs for GPT models. OpenClaw supports three OpenAI-fa
OpenAI explicitly supports subscription OAuth usage in external tools and workflows like OpenClaw.
Provider, model, runtime, and channel are separate layers. If those labels are
getting mixed together, read [Agent runtimes](/concepts/agent-runtimes) before
changing config.
## Quick choice
| Goal | Use | Notes |
@@ -96,7 +100,9 @@ Choose your preferred auth method and follow the setup steps.
<Note>
`openai/*` is the direct OpenAI API-key route unless you explicitly force
the Codex app-server harness. GPT-5.5 itself is currently subscription/OAuth
only; use `openai-codex/*` for Codex OAuth through the default PI runner.
only; use `openai-codex/*` for Codex OAuth through the default PI runner, or
use `openai/gpt-5.5` with `embeddedHarness.runtime: "codex"` for native
Codex app-server execution.
</Note>
### Config example
@@ -789,6 +795,8 @@ the Server-side compaction accordion below.
**Proxy/compatible routes:**
- Use looser compat behavior
- Strip Completions `store` from non-native `openai-completions` payloads
- Accept advanced `params.extra_body`/`params.extraBody` pass-through JSON for OpenAI-compatible Completions proxies
- Do not force strict tool schemas or native-only headers
Azure OpenAI uses native transport and compat behavior but does not receive the hidden attribution headers.

View File

@@ -48,7 +48,7 @@ As of the bundled pi catalog, the provider includes:
</Step>
<Step title="Set a Go model as default">
```bash
openclaw config set agents.defaults.model.primary "opencode-go/kimi-k2.5"
openclaw config set agents.defaults.model.primary "opencode-go/kimi-k2.6"
```
</Step>
<Step title="Verify models are available">
@@ -80,7 +80,7 @@ As of the bundled pi catalog, the provider includes:
```json5
{
env: { OPENCODE_API_KEY: "YOUR_API_KEY_HERE" }, // pragma: allowlist secret
agents: { defaults: { model: { primary: "opencode-go/kimi-k2.5" } } },
agents: { defaults: { model: { primary: "opencode-go/kimi-k2.6" } } },
}
```

View File

@@ -66,7 +66,7 @@ as one OpenCode setup.
</Step>
<Step title="Set a Go model as the default">
```bash
openclaw config set agents.defaults.model.primary "opencode-go/kimi-k2.5"
openclaw config set agents.defaults.model.primary "opencode-go/kimi-k2.6"
```
</Step>
<Step title="Verify models are available">
@@ -102,7 +102,7 @@ as one OpenCode setup.
| Property | Value |
| ---------------- | ------------------------------------------------------------------------ |
| Runtime provider | `opencode-go` |
| Example models | `opencode-go/kimi-k2.5`, `opencode-go/glm-5`, `opencode-go/minimax-m2.5` |
| Example models | `opencode-go/kimi-k2.6`, `opencode-go/glm-5`, `opencode-go/minimax-m2.5` |
## Advanced configuration

View File

@@ -79,6 +79,32 @@ OpenRouter can also back the `image_generate` tool. Use an OpenRouter image mode
OpenClaw sends image requests to OpenRouter's chat completions image API with `modalities: ["image", "text"]`. Gemini image models receive supported `aspectRatio` and `resolution` hints through OpenRouter's `image_config`.
## Text-to-speech
OpenRouter can also be used as a TTS provider through its OpenAI-compatible
`/audio/speech` endpoint.
```json5
{
messages: {
tts: {
auto: "always",
provider: "openrouter",
providers: {
openrouter: {
model: "hexgrad/kokoro-82m",
voice: "af_alloy",
responseFormat: "mp3",
},
},
},
},
}
```
If `messages.tts.providers.openrouter.apiKey` is omitted, TTS reuses
`models.providers.openrouter.apiKey`, then `OPENROUTER_API_KEY`.
## Authentication and headers
OpenRouter uses a Bearer token with your API key under the hood.

View File

@@ -16,7 +16,7 @@ cache values still take precedence over transcript fallback values.
Why this matters: lower token cost, faster responses, and more predictable performance for long-running sessions. Without caching, repeated prompts pay the full prompt cost on every turn even when most input did not change.
This page covers all cache-related knobs that affect prompt reuse and token cost.
The sections below cover every cache-related knob that affects prompt reuse and token cost.
Provider references:
@@ -105,6 +105,7 @@ Per-agent heartbeat is supported at `agents.list[].heartbeat`.
- Prompt caching is automatic on supported recent models. OpenClaw does not need to inject block-level cache markers.
- OpenClaw uses `prompt_cache_key` to keep cache routing stable across turns and uses `prompt_cache_retention: "24h"` only when `cacheRetention: "long"` is selected on direct OpenAI hosts.
- OpenAI-compatible Completions providers receive `prompt_cache_key` only when their model config explicitly sets `compat.supportsPromptCacheKey: true`; `cacheRetention: "none"` still suppresses it.
- OpenAI responses expose cached prompt tokens via `usage.prompt_tokens_details.cached_tokens` (or `input_tokens_details.cached_tokens` on Responses API events). OpenClaw maps that to `cacheRead`.
- OpenAI does not expose a separate cache-write token counter, so `cacheWrite` stays `0` on OpenAI paths even when the provider is warming a cache.
- OpenAI returns useful tracing and rate-limit headers such as `x-request-id`, `openai-processing-ms`, and `x-ratelimit-*`, but cache-hit accounting should come from the usage payload, not from headers.
@@ -122,7 +123,7 @@ Per-agent heartbeat is supported at `agents.list[].heartbeat`.
- Anthropic Claude model refs (`amazon-bedrock/*anthropic.claude*`) support explicit `cacheRetention` pass-through.
- Non-Anthropic Bedrock models are forced to `cacheRetention: "none"` at runtime.
### OpenRouter Anthropic models
### OpenRouter models
For `openrouter/anthropic/*` model refs, OpenClaw injects Anthropic
`cache_control` on system/developer prompt blocks to improve prompt-cache
@@ -130,6 +131,16 @@ reuse only when the request is still targeting a verified OpenRouter route
(`openrouter` on its default endpoint, or any provider/base URL that resolves
to `openrouter.ai`).
For `openrouter/deepseek/*`, `openrouter/moonshot*/*`, and `openrouter/zai/*`
model refs, `contextPruning.mode: "cache-ttl"` is allowed because OpenRouter
handles provider-side prompt caching automatically. OpenClaw does not inject
Anthropic `cache_control` markers into those requests.
DeepSeek cache construction is best-effort and can take a few seconds. An
immediate follow-up may still show `cached_tokens: 0`; verify with a repeated
same-prefix request after a short delay and use `usage.prompt_tokens_details.cached_tokens`
as the cache-hit signal.
If you repoint the model at an arbitrary OpenAI-compatible proxy URL, OpenClaw
stops injecting those OpenRouter-specific Anthropic cache markers.
@@ -337,9 +348,9 @@ Defaults:
Related docs:
- [Anthropic](/providers/anthropic)
- [Token Use and Costs](/reference/token-use)
- [Session Pruning](/concepts/session-pruning)
- [Gateway Configuration Reference](/gateway/configuration-reference)
- [Token use and costs](/reference/token-use)
- [Session pruning](/concepts/session-pruning)
- [Gateway configuration reference](/gateway/configuration-reference)
## Related

View File

@@ -15,6 +15,11 @@ Assistant output can carry a small set of delivery/render directives:
These directives are separate. `MEDIA:` and reply/voice tags remain delivery metadata; `[embed ...]` is the web-only rich render path.
When block streaming is enabled, `MEDIA:` remains single-delivery metadata for a
turn. If the same media URL is sent in a streamed block and repeated in the final
assistant payload, OpenClaw delivers the attachment once and strips the duplicate
from the final payload.
## `[embed ...]`
`[embed ...]` is the only agent-facing rich render syntax for the Control UI.
@@ -34,7 +39,7 @@ Rules:
- The web UI strips the shortcode from visible text and renders the embed inline.
- `MEDIA:` is not an embed alias and should not be used for rich embed rendering.
## Stored Rendering Shape
## Stored rendering shape
The normalized/stored assistant content block is a structured `canvas` item:

View File

@@ -114,6 +114,7 @@ Notes:
- Auth-profile plan targets require `agentId`.
- Plan entries target `profiles.*.key` / `profiles.*.token` and write sibling refs (`keyRef` / `tokenRef`).
- Auth-profile refs are included in runtime resolution and audit coverage.
- In `openclaw.json`, SecretRefs must use structured objects such as `{"source":"env","provider":"default","id":"DISCORD_BOT_TOKEN"}`. Legacy `secretref-env:<ENV_VAR>` marker strings are rejected on SecretRef credential paths; run `openclaw doctor --fix` to migrate valid markers.
- OAuth policy guard: `auth.profiles.<id>.mode = "oauth"` cannot be combined with SecretRef inputs for that profile. Startup/reload and auth-profile resolution fail fast when this policy is violated.
- For SecretRef-managed model providers, generated `agents/*/agent/models.json` entries persist non-secret markers (not resolved secret values) for `apiKey`/header surfaces.
- Marker persistence is source-authoritative: OpenClaw writes markers from the active source config snapshot (pre-resolution), not from resolved runtime secret values.

View File

@@ -7,9 +7,7 @@ read_when:
title: "Session management deep dive"
---
# Session Management & Compaction (Deep Dive)
This document explains how OpenClaw manages sessions end-to-end:
This page explains how OpenClaw manages sessions end-to-end:
- **Session routing** (how inbound messages map to a `sessionKey`)
- **Session store** (`sessions.json`) and what it tracks
@@ -21,12 +19,12 @@ This document explains how OpenClaw manages sessions end-to-end:
If you want a higher-level overview first, start with:
- [/concepts/session](/concepts/session)
- [/concepts/compaction](/concepts/compaction)
- [/concepts/memory](/concepts/memory)
- [/concepts/memory-search](/concepts/memory-search)
- [/concepts/session-pruning](/concepts/session-pruning)
- [/reference/transcript-hygiene](/reference/transcript-hygiene)
- [Session management](/concepts/session)
- [Compaction](/concepts/compaction)
- [Memory overview](/concepts/memory)
- [Memory search](/concepts/memory-search)
- [Session pruning](/concepts/session-pruning)
- [Transcript hygiene](/reference/transcript-hygiene)
---
@@ -103,6 +101,14 @@ Isolated cron runs also create session entries/transcripts, and they have dedica
- `cron.sessionRetention` (default `24h`) prunes old isolated cron run sessions from the session store (`false` disables).
- `cron.runLog.maxBytes` + `cron.runLog.keepLines` prune `~/.openclaw/cron/runs/<jobId>.jsonl` files (defaults: `2_000_000` bytes and `2000` lines).
When cron force-creates a new isolated run session, it sanitizes the previous
`cron:<jobId>` session entry before writing the new row. It carries safe
preferences such as thinking/fast/verbose settings, labels, and explicit
user-selected model/auth overrides. It drops ambient conversation context such
as channel/group routing, send or queue policy, elevation, origin, and ACP
runtime binding so a fresh isolated run cannot inherit stale delivery or
runtime authority from an older run.
---
## Session keys (`sessionKey`)
@@ -267,6 +273,10 @@ OpenClaw also enforces a safety floor for embedded runs:
- Default floor is `20000` tokens.
- Set `agents.defaults.compaction.reserveTokensFloor: 0` to disable the floor.
- If its already higher, OpenClaw leaves it alone.
- Manual `/compact` honors an explicit `agents.defaults.compaction.keepRecentTokens`
and keeps Pi's recent-tail cut point. Without an explicit keep budget,
manual compaction remains a hard checkpoint and rebuilt context starts from
the new summary.
Why: leave enough headroom for multi-turn “housekeeping” (like memory writes) before compaction becomes unavoidable.
@@ -283,6 +293,10 @@ Plugins can register a compaction provider via `registerCompactionProvider()` on
- Setting a `provider` forces `mode: "safeguard"`.
- Providers receive the same compaction instructions and identifier-preservation policy as the built-in path.
- The safeguard still preserves recent-turn and split-turn suffix context after provider output.
- Built-in safeguard summarization re-distills prior summaries with new messages
instead of preserving the full previous summary verbatim.
- Safeguard mode enables summary quality audits by default; set
`qualityGuard.enabled: false` to skip retry-on-malformed-output behavior.
- If the provider fails or returns an empty result, OpenClaw falls back to built-in LLM summarization automatically.
- Abort/timeout signals are re-thrown (not swallowed) to respect caller cancellation.

View File

@@ -27,7 +27,7 @@ Scope includes:
If you need transcript storage details, see:
- [/reference/session-management-compaction](/reference/session-management-compaction)
- [Session management deep dive](/reference/session-management-compaction)
---
@@ -112,11 +112,11 @@ external end-user instructions.
**OpenAI / OpenAI Codex**
- Image sanitization only.
- Drop orphaned reasoning signatures (standalone reasoning items without a following content block) for OpenAI Responses/Codex transcripts.
- Drop orphaned reasoning signatures (standalone reasoning items without a following content block) for OpenAI Responses/Codex transcripts, and drop replayable OpenAI reasoning after a model route switch.
- No tool call id sanitization.
- No tool result pairing repair.
- Tool result pairing repair may move real matched outputs and synthesize Codex-style `aborted` outputs for missing tool calls.
- No turn validation or reordering.
- No synthetic tool results.
- Missing OpenAI Responses-family tool outputs are synthesized as `aborted` to match Codex replay normalization.
- No thought signature stripping.
**Google (Generative AI / Gemini CLI / Antigravity)**

View File

@@ -8,8 +8,8 @@ title: "ACP agents — setup"
---
For the overview, operator runbook, and concepts, see [ACP agents](/tools/acp-agents).
This page covers acpx harness config, plugin setup for the MCP bridges, and
permission configuration.
The sections below cover acpx harness config, plugin setup for the MCP bridges, and permission configuration.
## acpx harness support (current)
@@ -235,8 +235,9 @@ Restart the gateway after changing this value.
### Health probe agent configuration
The bundled `acpx` plugin probes one harness agent while deciding whether the
embedded runtime backend is ready. It defaults to `codex`. If your deployment
uses a different default ACP agent, set the probe agent to the same id:
embedded runtime backend is ready. If `acp.allowedAgents` is set, it defaults to
the first allowed agent; otherwise it defaults to `codex`. If your deployment
needs a different ACP agent for health checks, set the probe agent explicitly:
```bash
openclaw config set plugins.entries.acpx.config.probeAgent claude

View File

@@ -66,7 +66,9 @@ injects a per-turn native hook relay so plugin hooks can block
`PermissionRequest` events through OpenClaw approvals. The v1 relay is
deliberately conservative: it does not mutate Codex-native tool arguments,
rewrite Codex thread records, or gate final answers/Stop hooks. Use explicit
ACP only when you want the ACP runtime/session model.
ACP only when you want the ACP runtime/session model. The embedded Codex support
boundary is documented in the
[Codex harness v1 support contract](/plugins/codex-harness#v1-support-contract).
Natural-language triggers that should route to the ACP runtime:

View File

@@ -165,6 +165,7 @@ openclaw browser responsebody "**/api" --max-chars 5000
openclaw browser navigate https://example.com
openclaw browser resize 1280 720
openclaw browser click 12 --double # or e12 for role refs
openclaw browser click-coords 120 340 # viewport coordinates
openclaw browser type 23 "hello" --submit
openclaw browser press Enter
openclaw browser hover 44
@@ -212,7 +213,7 @@ openclaw browser set device "iPhone 14"
Notes:
- `upload` and `dialog` are **arming** calls; run them before the click/press that triggers the chooser/dialog.
- `click`/`type`/etc require a `ref` from `snapshot` (numeric `12` or role ref `e12`). CSS selectors are intentionally not supported for actions.
- `click`/`type`/etc require a `ref` from `snapshot` (numeric `12` or role ref `e12`). CSS selectors are intentionally not supported for actions. Use `click-coords` when the visible viewport position is the only reliable target.
- Download, trace, and upload paths are constrained to OpenClaw temp roots: `/tmp/openclaw{,/downloads,/uploads}` (fallback: `${os.tmpdir()}/openclaw/...`).
- `upload` can also set file inputs directly via `--input-ref` or `--element`.

View File

@@ -25,6 +25,16 @@ chromium-browser is already the newest version (2:1snap1-0ubuntu2).
This is NOT a real browser - it's just a wrapper.
Other common Linux launch failures:
- `The profile appears to be in use by another Chromium process` means Chrome
found stale `Singleton*` lock files in the managed profile directory. OpenClaw
removes those locks and retries once when the lock points at a dead or
different-host process.
- `Missing X server or $DISPLAY` means OpenClaw is trying to launch a visible
browser on a host without a desktop session. Use `browser.headless: true`,
start `Xvfb`, or run OpenClaw in a real desktop session.
### Solution 1: Install Google Chrome (Recommended)
Install the official Google Chrome `.deb` package, which is not sandboxed by snap:

View File

@@ -129,6 +129,12 @@ Browser settings live in `~/.openclaw/openclaw.json`.
// cdpUrl: "http://127.0.0.1:18792", // legacy single-profile override
remoteCdpTimeoutMs: 1500, // remote CDP HTTP timeout (ms)
remoteCdpHandshakeTimeoutMs: 3000, // remote CDP WebSocket handshake timeout (ms)
tabCleanup: {
enabled: true, // default: true
idleMinutes: 120, // set 0 to disable idle cleanup
maxTabsPerSession: 8, // set 0 to disable the per-session cap
sweepMinutes: 5,
},
defaultProfile: "openclaw",
color: "#FF4500",
headless: false,
@@ -137,7 +143,12 @@ Browser settings live in `~/.openclaw/openclaw.json`.
executablePath: "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser",
profiles: {
openclaw: { cdpPort: 18800, color: "#FF4500" },
work: { cdpPort: 18801, color: "#0066CC", headless: true },
work: {
cdpPort: 18801,
color: "#0066CC",
headless: true,
executablePath: "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
},
user: {
driver: "existing-session",
attachOnly: true,
@@ -162,6 +173,7 @@ Browser settings live in `~/.openclaw/openclaw.json`.
- Control service binds to loopback on a port derived from `gateway.port` (default `18791` = gateway + 2). Overriding `gateway.port` or `OPENCLAW_GATEWAY_PORT` shifts the derived ports in the same family.
- Local `openclaw` profiles auto-assign `cdpPort`/`cdpUrl`; set those only for remote CDP. `cdpUrl` defaults to the managed local CDP port when unset.
- `remoteCdpTimeoutMs` applies to remote (non-loopback) CDP HTTP reachability checks; `remoteCdpHandshakeTimeoutMs` applies to remote CDP WebSocket handshakes.
- `tabCleanup` is best-effort cleanup for tabs opened by primary-agent browser sessions. Subagent, cron, and ACP lifecycle cleanup still closes their explicit tracked tabs at session end; primary sessions keep active tabs reusable, then close idle or excess tracked tabs in the background.
</Accordion>
@@ -169,6 +181,8 @@ Browser settings live in `~/.openclaw/openclaw.json`.
- Browser navigation and open-tab are SSRF-guarded before navigation and best-effort re-checked on the final `http(s)` URL afterwards.
- In strict SSRF mode, remote CDP endpoint discovery and `/json/version` probes (`cdpUrl`) are checked too.
- Gateway/provider `HTTP_PROXY`, `HTTPS_PROXY`, `ALL_PROXY`, and `NO_PROXY` environment variables do not automatically proxy the OpenClaw-managed browser. Managed Chrome launches direct by default so provider proxy settings do not weaken browser SSRF checks.
- To proxy the managed browser itself, pass explicit Chrome proxy flags through `browser.extraArgs`, such as `--proxy-server=...` or `--proxy-pac-url=...`. Strict SSRF mode blocks explicit browser proxy routing unless private-network browser access is intentionally enabled.
- `browser.ssrfPolicy.dangerouslyAllowPrivateNetwork` is off by default; enable only when private-network browser access is intentionally trusted.
- `browser.ssrfPolicy.allowPrivateNetwork` remains supported as a legacy alias.
@@ -178,6 +192,7 @@ Browser settings live in `~/.openclaw/openclaw.json`.
- `attachOnly: true` means never launch a local browser; only attach if one is already running.
- `headless` can be set globally or per local managed profile. Per-profile values override `browser.headless`, so one locally launched profile can stay headless while another remains visible.
- `executablePath` can be set globally or per local managed profile. Per-profile values override `browser.executablePath`, so different managed profiles can launch different Chromium-based browsers.
- `color` (top-level and per-profile) tints the browser UI so you can see which profile is active.
- Default profile is `openclaw` (managed standalone). Use `defaultProfile: "user"` to opt into the signed-in user browser.
- Auto-detect order: system default browser if Chromium-based; otherwise Chrome → Brave → Edge → Chromium → Chrome Canary.
@@ -192,10 +207,11 @@ Browser settings live in `~/.openclaw/openclaw.json`.
If your **system default** browser is Chromium-based (Chrome/Brave/Edge/etc),
OpenClaw uses it automatically. Set `browser.executablePath` to override
auto-detection:
auto-detection. `~` expands to your OS home directory:
```bash
openclaw config set browser.executablePath "/usr/bin/google-chrome"
openclaw config set browser.profiles.work.executablePath "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
```
Or set it in config, per platform:
@@ -230,6 +246,10 @@ Or set it in config, per platform:
</Tab>
</Tabs>
Per-profile `executablePath` only affects local managed profiles that OpenClaw
launches. `existing-session` profiles attach to an already-running browser
instead, and remote CDP profiles use the browser behind `cdpUrl`.
## Local vs remote control
- **Local control (default):** the Gateway starts the loopback control service and can launch a local browser.
@@ -237,6 +257,9 @@ Or set it in config, per platform:
- **Remote CDP:** set `browser.profiles.<name>.cdpUrl` (or `browser.cdpUrl`) to
attach to a remote Chromium-based browser. In this case, OpenClaw will not launch a local browser.
- `headless` only affects local managed profiles that OpenClaw launches. It does not restart or change existing-session or remote CDP browsers.
- `executablePath` follows the same local managed profile rule. Changing it on a
running local managed profile marks that profile for restart/reconcile so the
next launch uses the new binary.
Stopping behavior differs by profile mode:
@@ -404,7 +427,7 @@ Defaults:
All control endpoints accept `?profile=<name>`; the CLI uses `--browser-profile`.
## Existing-session via Chrome DevTools MCP
## Existing session via Chrome DevTools MCP
OpenClaw can also attach to a running Chromium-based browser profile through the
official Chrome DevTools MCP server. This reuses the tabs and login state
@@ -506,7 +529,7 @@ Notes:
Compared to the managed `openclaw` profile, existing-session drivers are more constrained:
- **Screenshots** — page captures and `--ref` element captures work; CSS `--element` selectors do not. `--full-page` cannot combine with `--ref` or `--element`. Playwright is not required for page or ref-based element screenshots.
- **Actions** — `click`, `type`, `hover`, `scrollIntoView`, `drag`, and `select` require snapshot refs (no CSS selectors). `click` is left-button only. `type` does not support `slowly=true`; use `fill` or `press`. `press` does not support `delayMs`. `type`, `hover`, `scrollIntoView`, `drag`, `select`, `fill`, and `evaluate` do not support per-call timeouts. `select` accepts a single value.
- **Actions** — `click`, `type`, `hover`, `scrollIntoView`, `drag`, and `select` require snapshot refs (no CSS selectors). `click-coords` clicks visible viewport coordinates and does not require a snapshot ref. `click` is left-button only. `type` does not support `slowly=true`; use `fill` or `press`. `press` does not support `delayMs`. `type`, `hover`, `scrollIntoView`, `drag`, `select`, `fill`, and `evaluate` do not support per-call timeouts. `select` accepts a single value.
- **Wait / upload / dialog** — `wait --url` supports exact, substring, and glob patterns; `wait --load networkidle` is not supported. Upload hooks require `ref` or `inputRef`, one file at a time, no CSS `element`. Dialog hooks do not support timeout overrides.
- **Managed-only features** — batch actions, PDF export, download interception, and `responsebody` still require the managed browser path.

View File

@@ -102,13 +102,13 @@ automatically.
### Safe bins versus allowlist
| Topic | `tools.exec.safeBins` | Allowlist (`exec-approvals.json`) |
| ---------------- | ------------------------------------------------------ | ------------------------------------------------------------ |
| Goal | Auto-allow narrow stdin filters | Explicitly trust specific executables |
| Match type | Executable name + safe-bin argv policy | Resolved executable path glob pattern |
| Argument scope | Restricted by safe-bin profile and literal-token rules | Path match only; arguments are otherwise your responsibility |
| Typical examples | `head`, `tail`, `tr`, `wc` | `jq`, `python3`, `node`, `ffmpeg`, custom CLIs |
| Best use | Low-risk text transforms in pipelines | Any tool with broader behavior or side effects |
| Topic | `tools.exec.safeBins` | Allowlist (`exec-approvals.json`) |
| ---------------- | ------------------------------------------------------ | ---------------------------------------------------------------------------------- |
| Goal | Auto-allow narrow stdin filters | Explicitly trust specific executables |
| Match type | Executable name + safe-bin argv policy | Resolved executable path glob, or bare command-name glob for PATH-invoked commands |
| Argument scope | Restricted by safe-bin profile and literal-token rules | Path match only; arguments are otherwise your responsibility |
| Typical examples | `head`, `tail`, `tr`, `wc` | `jq`, `python3`, `node`, `ffmpeg`, custom CLIs |
| Best use | Low-risk text transforms in pipelines | Any tool with broader behavior or side effects |
Configuration location:

View File

@@ -248,13 +248,17 @@ This is defense-in-depth for interpreter loaders that do not map cleanly to one
## Allowlist (per agent)
Allowlists are **per agent**. If multiple agents exist, switch which agent youre
editing in the macOS app. Patterns are **case-insensitive glob matches**.
Patterns should resolve to **binary paths** (basename-only entries are ignored).
editing in the macOS app. Patterns are glob matches.
Patterns can be resolved binary path globs or bare command-name globs. Bare names
match only commands invoked through PATH, so `rg` can match `/opt/homebrew/bin/rg`
when the command is `rg`, but not `./rg` or `/tmp/rg`. Use a path glob when you
want to trust one specific binary location.
Legacy `agents.default` entries are migrated to `agents.main` on load.
Shell chains such as `echo ok && pwd` still need every top-level segment to satisfy allowlist rules.
Examples:
- `rg`
- `~/Projects/**/bin/peekaboo`
- `~/.local/bin/*`
- `/opt/homebrew/bin/rg`

View File

@@ -175,11 +175,13 @@ only path.
## Allowlist + safe bins
Manual allowlist enforcement matches **resolved binary paths only** (no basename matches). When
`security=allowlist`, shell commands are auto-allowed only if every pipeline segment is
allowlisted or a safe bin. Chaining (`;`, `&&`, `||`) and redirections are rejected in
allowlist mode unless every top-level segment satisfies the allowlist (including safe bins).
Redirections remain unsupported.
Manual allowlist enforcement matches resolved binary path globs and bare command-name
globs. Bare names match only commands invoked through PATH, so `rg` can match
`/opt/homebrew/bin/rg` when the command is `rg`, but not `./rg` or `/tmp/rg`.
When `security=allowlist`, shell commands are auto-allowed only if every pipeline
segment is allowlisted or a safe bin. Chaining (`;`, `&&`, `||`) and redirections
are rejected in allowlist mode unless every top-level segment satisfies the
allowlist (including safe bins). Redirections remain unsupported.
Durable `allow-always` trust does not bypass that rule: a chained command still requires every
top-level segment to match.

View File

@@ -368,18 +368,22 @@ public contract.
`api.registrationMode` tells a plugin why its entry is being loaded:
| Mode | Meaning |
| --------------- | ------------------------------------------------------------------------------------------------------ |
| `full` | Runtime activation. Register tools, hooks, services, commands, routes, and other live side effects. |
| `discovery` | Read-only capability discovery. Register providers and metadata, but skip expensive live side effects. |
| `setup-only` | Channel setup metadata loading through a lightweight setup entry. |
| `setup-runtime` | Channel setup loading that also needs the runtime entry. |
| `cli-metadata` | CLI command metadata collection only. |
| Mode | Meaning |
| --------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `full` | Runtime activation. Register tools, hooks, services, commands, routes, and other live side effects. |
| `discovery` | Read-only capability discovery. Register providers and metadata; trusted plugin entry code may load, but skip live side effects. |
| `setup-only` | Channel setup metadata loading through a lightweight setup entry. |
| `setup-runtime` | Channel setup loading that also needs the runtime entry. |
| `cli-metadata` | CLI command metadata collection only. |
Plugin entries that open sockets, databases, background workers, or long-lived
clients should guard those side effects with `api.registrationMode === "full"`.
Discovery loads are cached separately from activating loads and do not replace
the running Gateway registry.
the running Gateway registry. Discovery is non-activating, not import-free:
OpenClaw may evaluate the trusted plugin entry or channel plugin module to build
the snapshot. Keep module top levels lightweight and side-effect-free, and move
network clients, subprocesses, listeners, credential reads, and service startup
behind full-runtime paths.
Common registration methods:
@@ -416,15 +420,16 @@ Native Codex app-server runs bridge Codex-native tool events back into this
hook surface. Plugins can block native Codex tools through `before_tool_call`,
observe results through `after_tool_call`, and participate in Codex
`PermissionRequest` approvals. The bridge does not rewrite Codex-native tool
arguments yet.
arguments yet. The exact Codex runtime support boundary lives in the
[Codex harness v1 support contract](/plugins/codex-harness#v1-support-contract).
For full typed hook behavior, see [SDK Overview](/plugins/sdk-overview#hook-decision-semantics).
For full typed hook behavior, see [SDK overview](/plugins/sdk-overview#hook-decision-semantics).
## Related
- [Building Plugins](/plugins/building-plugins) — create your own plugin
- [Plugin Bundles](/plugins/bundles) — Codex/Claude/Cursor bundle compatibility
- [Plugin Manifest](/plugins/manifest) — manifest schema
- [Registering Tools](/plugins/building-plugins#registering-agent-tools) — add agent tools in a plugin
- [Plugin Internals](/plugins/architecture) — capability model and load pipeline
- [Community Plugins](/plugins/community) — third-party listings
- [Building plugins](/plugins/building-plugins) — create your own plugin
- [Plugin bundles](/plugins/bundles) — Codex/Claude/Cursor bundle compatibility
- [Plugin manifest](/plugins/manifest) — manifest schema
- [Registering tools](/plugins/building-plugins#registering-agent-tools) — add agent tools in a plugin
- [Plugin internals](/plugins/architecture) — capability model and load pipeline
- [Community plugins](/plugins/community) — third-party listings

View File

@@ -203,6 +203,11 @@ Fields under `metadata.openclaw`:
- `primaryEnv` — env var name associated with `skills.entries.<name>.apiKey`.
- `install` — optional array of installer specs used by the macOS Skills UI (brew/node/go/uv/download).
Legacy `metadata.clawdbot` blocks are still accepted when
`metadata.openclaw` is absent, so older installed skills keep their dependency
gates and installer hints. New and updated skills should use
`metadata.openclaw`.
Note on sandboxing:
- `requires.bins` is checked on the **host** at skill load time.

View File

@@ -170,7 +170,7 @@ Auto-archive:
- Auto-archive applies equally to depth-1 and depth-2 sessions.
- Browser cleanup is separate from archive cleanup: tracked browser tabs/processes are best-effort closed when the run finishes, even if the transcript/session record is kept.
## Nested Sub-Agents
## Nested sub-agents
By default, sub-agents cannot spawn their own sub-agents (`maxSpawnDepth: 1`). You can enable one level of nesting by setting `maxSpawnDepth: 2`, which allows the **orchestrator pattern**: main → orchestrator sub-agent → worker sub-sub-agents.
@@ -214,6 +214,11 @@ Operational guidance:
- Start child work once and wait for completion events instead of building poll
loops around `sessions_list`, `sessions_history`, `/subagents list`, or
`exec` sleep commands.
- `sessions_list` and `/subagents list` keep child-session relationships focused
on live work: live children remain attached, ended children stay visible for a
short recent window, and stale store-only child links are ignored after their
freshness window. This prevents old `spawnedBy` / `parentSessionKey` metadata
from resurrecting ghost children after restart.
- If a child completion event arrives after you already sent the final answer,
the correct follow-up is the exact silent token `NO_REPLY` / `no_reply`.
@@ -343,6 +348,18 @@ Sub-agents use a dedicated in-process queue lane:
- Lane name: `subagent`
- Concurrency: `agents.defaults.subagents.maxConcurrent` (default `8`)
## Liveness and recovery
OpenClaw does not treat `endedAt` absence as permanent proof that a sub-agent
is still alive. Unended runs older than the stale-run window stop counting as
active/pending in `/subagents list`, status summaries, descendant completion
gating, and per-session concurrency checks.
After a gateway restart, stale unended restored runs are pruned unless their
child session is marked `abortedLastRun: true`. Those restart-aborted child
sessions remain recoverable through the sub-agent orphan recovery flow, which
sends a synthetic resume message before clearing the aborted marker.
## Stopping
- Sending `/stop` in the requester chat aborts the requester session and stops any active sub-agent runs spawned from it, cascading to nested children.

View File

@@ -231,6 +231,32 @@ Resolution order is `messages.tts.providers.xai.apiKey` -> `XAI_API_KEY`.
Current live voices are `ara`, `eve`, `leo`, `rex`, `sal`, and `una`; `eve` is
the default. `language` accepts a BCP-47 tag or `auto`.
### OpenRouter primary
```json5
{
messages: {
tts: {
auto: "always",
provider: "openrouter",
providers: {
openrouter: {
apiKey: "openrouter_api_key",
model: "hexgrad/kokoro-82m",
voice: "af_alloy",
responseFormat: "mp3",
},
},
},
},
}
```
OpenRouter TTS uses the same `OPENROUTER_API_KEY` path as the bundled
OpenRouter model provider. Resolution order is
`messages.tts.providers.openrouter.apiKey` ->
`models.providers.openrouter.apiKey` -> `OPENROUTER_API_KEY`.
### Gradium primary
```json5
@@ -321,13 +347,15 @@ Then run:
- `mode`: `"final"` (default) or `"all"` (includes tool/block replies).
- `provider`: speech provider id such as `"elevenlabs"`, `"google"`, `"gradium"`, `"microsoft"`, `"minimax"`, `"openai"`, `"vydra"`, or `"xai"` (fallback is automatic).
- If `provider` is **unset**, OpenClaw uses the first configured speech provider in registry auto-select order.
- Legacy `provider: "edge"` still works and is normalized to `microsoft`.
- Legacy `provider: "edge"` config is repaired by `openclaw doctor --fix` and
rewritten to `provider: "microsoft"`.
- `summaryModel`: optional cheap model for auto-summary; defaults to `agents.defaults.model.primary`.
- Accepts `provider/model` or a configured model alias.
- `modelOverrides`: allow the model to emit TTS directives (on by default).
- `allowProvider` defaults to `false` (provider switching is opt-in).
- `providers.<id>`: provider-owned settings keyed by speech provider id.
- Legacy direct provider blocks (`messages.tts.openai`, `messages.tts.elevenlabs`, `messages.tts.microsoft`, `messages.tts.edge`) are auto-migrated to `messages.tts.providers.<id>` on load.
- Legacy direct provider blocks (`messages.tts.openai`, `messages.tts.elevenlabs`, `messages.tts.microsoft`, `messages.tts.edge`) are repaired by `openclaw doctor --fix`; committed config should use `messages.tts.providers.<id>`.
- Legacy `messages.tts.providers.edge` is also repaired by `openclaw doctor --fix`; committed config should use `messages.tts.providers.microsoft`.
- `maxTextLength`: hard cap for TTS input (chars). `/tts audio` fails if exceeded.
- `timeoutMs`: request timeout (ms).
- `prefsPath`: override the local prefs JSON path (provider/limit/summary).
@@ -348,9 +376,11 @@ Then run:
- `providers.minimax.voiceId`: voice identifier (default `English_expressive_narrator`, env: `MINIMAX_TTS_VOICE_ID`).
- `providers.minimax.speed`: playback speed `0.5..2.0` (default 1.0).
- `providers.minimax.vol`: volume `(0, 10]` (default 1.0; must be greater than 0).
- `providers.minimax.pitch`: pitch shift `-12..12` (default 0).
- `providers.minimax.pitch`: integer pitch shift `-12..12` (default 0). Fractional values are truncated before calling MiniMax T2A because the API rejects non-integer pitch values.
- `providers.google.model`: Gemini TTS model (default `gemini-3.1-flash-tts-preview`).
- `providers.google.voiceName`: Gemini prebuilt voice name (default `Kore`; `voice` is also accepted).
- `providers.google.audioProfile`: natural-language style prompt prepended before the spoken text.
- `providers.google.speakerName`: optional speaker label prepended before the spoken text when your TTS prompt uses a named speaker.
- `providers.google.baseUrl`: override the Gemini API base URL. Only `https://generativelanguage.googleapis.com` is accepted.
- If `messages.tts.providers.google.apiKey` is omitted, TTS can reuse `models.providers.google.apiKey` before env fallback.
- `providers.gradium.baseUrl`: override Gradium API base URL (default `https://api.gradium.ai`).
@@ -361,6 +391,12 @@ Then run:
- `providers.xai.language`: BCP-47 language code or `auto` (default `en`).
- `providers.xai.responseFormat`: `mp3`, `wav`, `pcm`, `mulaw`, or `alaw` (default `mp3`).
- `providers.xai.speed`: provider-native speed override.
- `providers.openrouter.apiKey`: OpenRouter API key (env: `OPENROUTER_API_KEY`; can reuse `models.providers.openrouter.apiKey`).
- `providers.openrouter.baseUrl`: override the OpenRouter TTS base URL (default `https://openrouter.ai/api/v1`; legacy `https://openrouter.ai/v1` is normalized).
- `providers.openrouter.model`: OpenRouter TTS model id (default `hexgrad/kokoro-82m`; `modelId` is also accepted).
- `providers.openrouter.voice`: provider-specific voice id (default `af_alloy`; `voiceId` is also accepted).
- `providers.openrouter.responseFormat`: `mp3` or `pcm` (default `mp3`).
- `providers.openrouter.speed`: provider-native speed override.
- `providers.microsoft.enabled`: allow Microsoft speech usage (default `true`; no API key).
- `providers.microsoft.voice`: Microsoft neural voice name (e.g. `en-US-MichelleNeural`).
- `providers.microsoft.lang`: language code (e.g. `en-US`).
@@ -370,7 +406,8 @@ Then run:
- `providers.microsoft.saveSubtitles`: write JSON subtitles alongside the audio file.
- `providers.microsoft.proxy`: proxy URL for Microsoft speech requests.
- `providers.microsoft.timeoutMs`: request timeout override (ms).
- `edge.*`: legacy alias for the same Microsoft settings.
- `edge.*`: legacy alias for the same Microsoft settings. Run
`openclaw doctor --fix` to rewrite persisted config to `providers.microsoft`.
## Model-driven overrides (default on)
@@ -400,7 +437,7 @@ Available directive keys (when enabled):
- `model` (OpenAI TTS model, ElevenLabs model id, or MiniMax model) or `google_model` (Google TTS model)
- `stability`, `similarityBoost`, `style`, `speed`, `useSpeakerBoost`
- `vol` / `volume` (MiniMax volume, 0-10)
- `pitch` (MiniMax pitch, -12 to 12)
- `pitch` (MiniMax integer pitch, -12 to 12; fractional values are truncated before the MiniMax request)
- `applyTextNormalization` (`auto|on|off`)
- `languageCode` (ISO 639-1)
- `seed`
@@ -456,7 +493,7 @@ These override `messages.tts.*` for that host.
- 48kHz / 64kbps is a good voice message tradeoff.
- **Other channels**: MP3 (`mp3_44100_128` from ElevenLabs, `mp3` from OpenAI).
- 44.1kHz / 128kbps is the default balance for speech clarity.
- **MiniMax**: MP3 (`speech-2.8-hd` model, 32kHz sample rate). Voice-note format not natively supported; use OpenAI or ElevenLabs for guaranteed Opus voice messages.
- **MiniMax**: MP3 (`speech-2.8-hd` model, 32kHz sample rate) for normal audio attachments. For voice-note targets such as Feishu and Telegram, OpenClaw transcodes the MiniMax MP3 to 48kHz Opus with `ffmpeg` before delivery.
- **Google Gemini**: Gemini API TTS returns raw 24kHz PCM. OpenClaw wraps it as WAV for audio attachments and returns PCM directly for Talk/telephony. Native Opus voice-note format is not supported by this path.
- **Gradium**: WAV for audio attachments, Opus for voice-note targets, and `ulaw_8000` at 8 kHz for telephony.
- **xAI**: MP3 by default; `responseFormat` may be `mp3`, `wav`, `pcm`, `mulaw`, or `alaw`. OpenClaw uses xAI's batch REST TTS endpoint and returns a complete audio attachment; xAI's streaming TTS WebSocket is not used by this provider path. Native Opus voice-note format is not supported by this path.

View File

@@ -154,6 +154,10 @@ Cron jobs panel notes:
- `chat.history` responses are size-bounded for UI safety. When transcript entries are too large, Gateway may truncate long text fields, omit heavy metadata blocks, and replace oversized messages with a placeholder (`[chat.history omitted: message too large]`).
- Assistant/generated images are persisted as managed media references and served back through authenticated Gateway media URLs, so reloads do not depend on raw base64 image payloads staying in the chat history response.
- `chat.history` also strips display-only inline directive tags from visible assistant text (for example `[[reply_to_*]]` and `[[audio_as_voice]]`), plain-text tool-call XML payloads (including `<tool_call>...</tool_call>`, `<function_call>...</function_call>`, `<tool_calls>...</tool_calls>`, `<function_calls>...</function_calls>`, and truncated tool-call blocks), and leaked ASCII/full-width model control tokens, and omits assistant entries whose whole visible text is only the exact silent token `NO_REPLY` / `no_reply`.
- During an active send and the final history refresh, the chat view keeps local
optimistic user/assistant messages visible if `chat.history` briefly returns
an older snapshot; the canonical transcript replaces those local messages once
the Gateway history catches up.
- `chat.inject` appends an assistant note to the session transcript and broadcasts a `chat` event for UI-only updates (no agent run, no channel delivery).
- The chat header model and thinking pickers patch the active session immediately through `sessions.patch`; they are persistent session overrides, not one-turn-only send options.
- When fresh Gateway session usage reports show high context pressure, the chat
@@ -314,7 +318,7 @@ Trusted-proxy note:
device identity
- this does **not** extend to node-role Control UI sessions
- same-host loopback reverse proxies still do not satisfy trusted-proxy auth; see
[Trusted Proxy Auth](/gateway/trusted-proxy-auth)
[Trusted proxy auth](/gateway/trusted-proxy-auth)
See [Tailscale](/gateway/tailscale) for HTTPS setup guidance.

View File

@@ -133,7 +133,7 @@
},
"probeAgent": {
"label": "Health Probe Agent",
"help": "Agent id used for the embedded ACP runtime health probe. Defaults to the runtime built-in probe agent (codex). Set this to another configured ACP agent id (for example `opencode` or `claude`) when the default probe agent is not installed or not authenticated, so the whole embedded ACP backend does not get marked unavailable.",
"help": "Agent id used for the embedded ACP runtime health probe. Defaults to the first `acp.allowedAgents` entry when that allowlist is set, otherwise to the runtime built-in probe agent (codex). Set this explicitly (for example `opencode` or `claude`) when the default probe agent is not installed or not authenticated, so the whole embedded ACP backend does not get marked unavailable.",
"advanced": true
},
"mcpServers": {

View File

@@ -176,6 +176,61 @@ describe("createAcpxRuntimeService", () => {
await service.stop?.(ctx);
});
it("uses the first allowed ACP agent as the default probe agent", async () => {
const workspaceDir = await makeTempDir();
const ctx = createServiceContext(workspaceDir);
ctx.config = {
acp: {
allowedAgents: [" OpenCode ", "codex"],
},
};
const runtime = createMockRuntime();
const runtimeFactory = vi.fn(() => runtime as never);
const service = createAcpxRuntimeService({
runtimeFactory,
});
await service.start(ctx);
expect(runtimeFactory).toHaveBeenCalledWith(
expect.objectContaining({
pluginConfig: expect.objectContaining({
probeAgent: "opencode",
}),
}),
);
await service.stop?.(ctx);
});
it("keeps explicit probeAgent ahead of acp.allowedAgents", async () => {
const workspaceDir = await makeTempDir();
const ctx = createServiceContext(workspaceDir);
ctx.config = {
acp: {
allowedAgents: ["opencode"],
},
};
const runtime = createMockRuntime();
const runtimeFactory = vi.fn(() => runtime as never);
const service = createAcpxRuntimeService({
pluginConfig: { probeAgent: "codex" },
runtimeFactory,
});
await service.start(ctx);
expect(runtimeFactory).toHaveBeenCalledWith(
expect.objectContaining({
pluginConfig: expect.objectContaining({
probeAgent: "codex",
}),
}),
);
await service.stop?.(ctx);
});
it("warns when legacy compatibility config is explicitly ignored", async () => {
const workspaceDir = await makeTempDir();
const ctx = createServiceContext(workspaceDir);
@@ -221,6 +276,32 @@ describe("createAcpxRuntimeService", () => {
await service.stop?.(ctx);
});
it("formats non-string doctor details without losing object payloads", async () => {
const workspaceDir = await makeTempDir();
const ctx = createServiceContext(workspaceDir);
const runtime = createMockRuntime({
doctor: async () => ({
ok: false,
message: "probe failed",
details: [{ code: "ACP_CLOSED", agent: "codex" }, new Error("stdin closed")],
}),
isHealthy: () => false,
});
const service = createAcpxRuntimeService({
runtimeFactory: () => runtime as never,
});
await service.start(ctx);
await vi.waitFor(() => {
expect(ctx.logger.warn).toHaveBeenCalledWith(
'embedded acpx runtime backend probe failed: probe failed ({"code":"ACP_CLOSED","agent":"codex"}; stdin closed)',
);
});
await service.stop?.(ctx);
});
it("can skip the embedded runtime backend via env", async () => {
process.env.OPENCLAW_SKIP_ACPX_RUNTIME = "1";
const workspaceDir = await makeTempDir();

View File

@@ -1,4 +1,5 @@
import fs from "node:fs/promises";
import { inspect } from "node:util";
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
import type {
AcpRuntime,
@@ -79,11 +80,54 @@ function warnOnIgnoredLegacyCompatibilityConfig(params: {
);
}
function formatDoctorFailureMessage(report: { message: string; details?: string[] }): string {
const detailText = report.details?.filter(Boolean).join("; ").trim();
function formatDoctorDetail(detail: unknown): string | null {
if (!detail) {
return null;
}
if (typeof detail === "string") {
return detail.trim() || null;
}
if (detail instanceof Error) {
return formatErrorMessage(detail);
}
if (typeof detail === "object") {
try {
return JSON.stringify(detail) ?? inspect(detail, { breakLength: Infinity, depth: 3 });
} catch {
return inspect(detail, { breakLength: Infinity, depth: 3 });
}
}
if (
typeof detail === "number" ||
typeof detail === "boolean" ||
typeof detail === "bigint" ||
typeof detail === "symbol"
) {
return detail.toString();
}
return inspect(detail, { breakLength: Infinity, depth: 3 });
}
function formatDoctorFailureMessage(report: { message: string; details?: unknown[] }): string {
const detailText = report.details?.map(formatDoctorDetail).filter(Boolean).join("; ").trim();
return detailText ? `${report.message} (${detailText})` : report.message;
}
function normalizeProbeAgent(value: string | undefined): string | undefined {
const normalized = value?.trim().toLowerCase();
return normalized ? normalized : undefined;
}
function resolveAllowedAgentsProbeAgent(ctx: OpenClawPluginServiceContext): string | undefined {
for (const agent of ctx.config.acp?.allowedAgents ?? []) {
const normalized = normalizeProbeAgent(agent);
if (normalized) {
return normalized;
}
}
return undefined;
}
export function createAcpxRuntimeService(
params: CreateAcpxRuntimeServiceParams = {},
): OpenClawPluginService {
@@ -102,8 +146,12 @@ export function createAcpxRuntimeService(
rawConfig: params.pluginConfig,
workspaceDir: ctx.workspaceDir,
});
const effectiveBasePluginConfig: ResolvedAcpxPluginConfig = {
...basePluginConfig,
probeAgent: basePluginConfig.probeAgent ?? resolveAllowedAgentsProbeAgent(ctx),
};
const pluginConfig = await prepareAcpxCodexAuthConfig({
pluginConfig: basePluginConfig,
pluginConfig: effectiveBasePluginConfig,
stateDir: ctx.stateDir,
logger: ctx.logger,
});

View File

@@ -566,6 +566,7 @@ describe("active-memory plugin", () => {
},
},
},
cleanupBundleMcpOnRunEnd: true,
});
});
@@ -653,6 +654,9 @@ describe("active-memory plugin", () => {
"You receive conversation context, including the user's latest message.",
);
expect(runParams?.prompt).toContain("Use only memory_search and memory_get.");
expect(runParams?.prompt).toContain(
"When searching for preference or habit recall, use a permissive memory_search threshold before deciding that no useful memory exists.",
);
expect(runParams?.prompt).toContain(
"If the user is directly asking about favorites, preferences, habits, routines, or personal facts, treat that as a strong recall signal.",
);

View File

@@ -787,6 +787,7 @@ function buildRecallPrompt(params: {
"Your job is to search memory and return only the most relevant memory context for that model.",
"You receive conversation context, including the user's latest message.",
"Use only memory_search and memory_get.",
"When searching for preference or habit recall, use a permissive memory_search threshold before deciding that no useful memory exists.",
"Do not answer the user directly.",
`Prompt style: ${params.config.promptStyle}.`,
...buildPromptStyleLines(params.config.promptStyle),
@@ -1684,6 +1685,7 @@ async function runRecallSubagent(params: {
thinkLevel: params.config.thinking,
reasoningLevel: "off",
silentExpected: true,
cleanupBundleMcpOnRunEnd: true,
abortSignal: params.abortSignal,
});
if (params.abortSignal?.aborted) {

View File

@@ -0,0 +1,65 @@
import { describe, expect, it, vi } from "vitest";
import { hasAwsCredentials } from "./embedding-provider.js";
describe("hasAwsCredentials", () => {
it("accepts static AWS key credentials without loading the credential chain", async () => {
const loadCredentialProvider = vi.fn();
await expect(
hasAwsCredentials(
{
AWS_ACCESS_KEY_ID: "access-key",
AWS_SECRET_ACCESS_KEY: "secret-key",
},
loadCredentialProvider,
),
).resolves.toBe(true);
expect(loadCredentialProvider).not.toHaveBeenCalled();
});
it("accepts the Bedrock bearer token without loading the credential chain", async () => {
const loadCredentialProvider = vi.fn();
await expect(
hasAwsCredentials(
{
AWS_BEARER_TOKEN_BEDROCK: "bearer-token",
},
loadCredentialProvider,
),
).resolves.toBe(true);
expect(loadCredentialProvider).not.toHaveBeenCalled();
});
it("requires AWS profile credentials to resolve through the credential chain", async () => {
const loadCredentialProvider = vi.fn().mockResolvedValue({
defaultProvider: () => async () => ({ accessKeyId: "resolved-access-key" }),
});
await expect(hasAwsCredentials({ AWS_PROFILE: "work" }, loadCredentialProvider)).resolves.toBe(
true,
);
expect(loadCredentialProvider).toHaveBeenCalledOnce();
});
it("rejects AWS profile markers when the credential chain cannot resolve", async () => {
const loadCredentialProvider = vi.fn().mockResolvedValue({
defaultProvider: () => async () => {
throw new Error("Could not load credentials from any providers");
},
});
await expect(
hasAwsCredentials({ AWS_PROFILE: "missing" }, loadCredentialProvider),
).resolves.toBe(false);
});
it("returns false when the AWS credential provider package is unavailable", async () => {
const loadCredentialProvider = vi.fn().mockResolvedValue(null);
await expect(hasAwsCredentials({}, loadCredentialProvider)).resolves.toBe(false);
});
});

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