Compare commits

..

230 Commits

Author SHA1 Message Date
Peter Steinberger
17a5e7b5fd fix: satisfy cockpit lint 2026-05-05 08:37:25 +01:00
Peter Steinberger
ad0b6713a0 feat: refresh control ui cockpit 2026-05-05 08:33:24 +01:00
Chunyue Wang
121ac44fa8 docs(changelog): relocate #77046 and #77280 entries from 2026.5.3 to Unreleased (#77728)
Merged via squash.

Prepared head SHA: 1bd228f6b6
Co-authored-by: openperf <80630709+openperf@users.noreply.github.com>
Co-authored-by: openperf <80630709+openperf@users.noreply.github.com>
Reviewed-by: @openperf
2026-05-05 15:18:20 +08:00
Peter Steinberger
1b81ed6675 docs: reorder unreleased changelog 2026-05-05 08:14:38 +01:00
Ayaan Zaidi
5e9258d0a8 fix: expose ollama thinking profile before activation (#77617) (thanks @yfge) 2026-05-05 12:43:52 +05:30
yfge
7a9efc1389 fix: expose ollama thinking profile before activation
Fixes openclaw/openclaw#77612
2026-05-05 12:43:52 +05:30
Vincent Koc
b8f9137d31 test(gateway): preserve dispatch timers in waiter 2026-05-05 00:07:42 -07:00
Vincent Koc
e2eb5649d1 test(gateway): keep startup context timer live 2026-05-05 00:04:02 -07:00
Peter Steinberger
18405c1acf docs: document cache-friendly activity helper 2026-05-05 08:03:14 +01:00
Peter Steinberger
c2f86598a3 ci: install ffmpeg for Mantis media previews 2026-05-05 08:03:01 +01:00
Ayaan Zaidi
11d2bb19dc fix: avoid impossible device token rotation advice (#77688) (thanks @Conan-Scott) 2026-05-05 12:32:23 +05:30
Clawdbot
eee69aa1bf docs(changelog): note doctor device pairing advice fix 2026-05-05 12:32:23 +05:30
Clawdbot
f5f11b8d0e fix(doctor): avoid impossible device token rotation advice 2026-05-05 12:32:23 +05:30
Peter Steinberger
cbcca6e55f ci: use Crabbox media previews for Mantis 2026-05-05 07:53:47 +01:00
Peter Steinberger
0e7073ad89 docs: filter maintainer-owned triage noise 2026-05-05 07:52:29 +01:00
Peter Steinberger
ecbf9f06e9 test: cover GitHub activity helper 2026-05-05 07:48:29 +01:00
Chunyue Wang
043cb32aab fix(session-file-repair): drop null-role message entries instead of preserving them (#77288)
type:"message" entries with a null, missing, or blank role cannot be
replayed to any provider — every router branches on message.role. The
auto-repair pass was passing them through unchanged, relocating the
corruption from the original file into the post-repair file (#77228
reported 935+ null-role entries surviving the pass).

Add isStructurallyInvalidMessageEntry ahead of the existing rewrite
predicates. Invalid message envelopes are counted as droppedLines and
skipped; non-message envelope types (summary, custom, …) are unaffected.
The .bak-* backup preserves the original bytes for postmortem before any
entries are dropped.

Tests:
- pnpm test src/agents/session-file-repair.test.ts
- pnpm exec oxfmt --check --threads=1 CHANGELOG.md src/agents/session-file-repair.ts src/agents/session-file-repair.test.ts
- pnpm check:changed

Refs #77228
2026-05-05 14:46:42 +08:00
Peter Steinberger
ea791b3792 fix: prune orphan session artifacts 2026-05-05 07:40:09 +01:00
Peter Steinberger
27b96ae049 perf: reduce GitHub activity cache misses 2026-05-05 07:30:56 +01:00
Ayaan Zaidi
eab494ca6a fix: cache session list model resolution (#77650) (thanks @ragesaq) 2026-05-05 11:56:49 +05:30
Peter Steinberger
7f27c42ebd ci: embed Mantis desktop previews 2026-05-05 07:23:21 +01:00
Chunyue Wang
24bd0b212f fix(replay-history): drop trailing stream-error placeholder before provider send (#77287)
normalizeAssistantReplayContent rewrites empty assistant error turns into
a STREAM_ERROR_FALLBACK_TEXT sentinel to satisfy Bedrock Converse's
non-empty ContentBlock requirement for non-trailing turns. When that
sentinel is the trailing entry, prefill-strict providers reject the
request with "400 This model does not support assistant message prefill.
The conversation must end with a user message." and the session cannot
recover on its own.

Add a post-loop tail trim that drops trailing assistant turns whose
content is empty with stopReason "error" or zero-usage empty stop, or
carries only the sentinel text with the same synthetic provenance. A real
model reply whose content happens to equal the sentinel string is
preserved by requiring zero usage or stopReason "error" before dropping.
The trim catches both the in-memory rewrite shape and the sentinel
persisted to disk by session-file-repair.

Tests:
- pnpm test src/agents/pi-embedded-runner/replay-history.test.ts
- pnpm exec oxfmt --check --threads=1 CHANGELOG.md src/agents/pi-embedded-runner/replay-history.ts
  src/agents/pi-embedded-runner/replay-history.test.ts
- pnpm check:changed

Refs #77228
2026-05-05 14:14:01 +08:00
clawsweeper[bot]
cd66854b66 feat(cron): add agentId filtering to cron list (#77602)
Summary:
- This PR adds optional `agentId` filtering to `cron.list`, auto-fills it for agent tool calls, exposes `openclaw cron list --agent`, updates generated protocol clients, docs, changelog, tests, and prompt fixtures.
- Reproducibility: yes. The motivating behavior is source-reproducible on current main because cron tool, CLI, ... e list paths do not accept or apply `agentId`; the PR diff adds that path with focused regression coverage.

Automerge notes:
- Ran the ClawSweeper repair loop before final review.
- Included post-review commit in the final squash: chore: regenerate protocol schema after adding agentId to CronListParams
- Included post-review commit in the final squash: feat(cron): add agentId filtering to cron list

Validation:
- ClawSweeper review passed for head 35b692bc97.
- Required merge gates passed before the squash merge.

Prepared head SHA: 35b692bc97
Review: https://github.com/openclaw/openclaw/pull/77602#issuecomment-4375631700

Co-authored-by: zhanggttry <zhanggttry@163.com>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
2026-05-05 06:06:24 +00:00
Peter Steinberger
b489a62a06 docs: add GitHub activity helper to maintainer skill 2026-05-05 07:04:16 +01:00
Vincent Koc
a17d4371d1 feat(status): show uptime in chat status
Show compact Gateway process and host system uptime in chat /status output.
2026-05-04 22:52:00 -07:00
Kevin Lin
5a8ccb6fe0 fix: recover Slack channel restart after stop timeout (#77686)
* fix: recover Slack channel restart after stop timeout

* fix: keep recovery restart cancellable
2026-05-04 22:47:30 -07:00
Peter Steinberger
123f7a697d docs: update video generation timeout notes 2026-05-05 06:47:07 +01:00
Peter Steinberger
a34d4ef9d9 fix: normalize video generation fallbacks 2026-05-05 06:47:07 +01:00
Peter Steinberger
b4ff3aa73b fix: record full Mantis desktop smoke videos 2026-05-05 06:43:47 +01:00
Peter Steinberger
42a7d8485f docs: surface GitHub opener identity in maintainer skill 2026-05-05 06:39:41 +01:00
Chunyue Wang
31da1fe5b0 fix(auth-profiles): exclude format rejections from profile cooldown (#77280)
Merged via squash.

Prepared head SHA: f4188b4dc3
Co-authored-by: openperf <80630709+openperf@users.noreply.github.com>
Co-authored-by: openperf <80630709+openperf@users.noreply.github.com>
Reviewed-by: @openperf
2026-05-05 13:35:41 +08:00
Peter Steinberger
1c924c3c12 ci: link Mantis status reaction videos 2026-05-05 06:31:05 +01:00
Tianworld
478138e288 docs(windows): update GitHub connectivity notes (#53788)
Co-authored-by: Brad Groux <3053586+BradGroux@users.noreply.github.com>
2026-05-05 00:31:03 -05:00
Vincent Koc
177167c846 fix(video): recover generation parameter fallbacks 2026-05-04 22:26:58 -07:00
Iroh
f126f72d63 fix(windows): resolve Gmail helper PATHEXT shims
Resolve Gmail setup and watcher helper binaries through Windows PATH/PATHEXT before spawning, without executing where.exe during lookup. Cover gcloud, gog, and tailscale, including the documented CLI Gmail run path, and route long-lived gog .cmd/.bat shims through a pinned cmd.exe wrapper.

Co-authored-by: Iroh <175496729+Angfr95@users.noreply.github.com>
Co-authored-by: Brad Groux <3053586+BradGroux@users.noreply.github.com>
2026-05-05 00:21:34 -05:00
Peter Steinberger
6c8974f3f5 fix: harden async media completion delivery 2026-05-05 06:13:33 +01:00
Sally O'Malley
349ce0056d fix: rebuild sandbox skill prompts from sandbox workspace (#77661)
Signed-off-by: sallyom <somalley@redhat.com>
2026-05-05 01:13:21 -04:00
Vincent Koc
a52010be7d test(doctor): preserve facade loader mock exports 2026-05-04 22:08:43 -07:00
Vincent Koc
557c5bf705 test(live): soften OpenAI cache telemetry floor 2026-05-04 22:01:08 -07:00
Val Alexander
1d6de8da9f fix(ui): hide heartbeat acknowledgements
Suppress assistant HEARTBEAT_OK acknowledgements at the Control UI live-event and persisted-history render boundaries.

The persisted transcript case can include hidden thinking/reasoning blocks plus a final HEARTBEAT_OK text block, so the display filter now ignores hidden reasoning while preserving turns with visible non-text content.

Validation:
- pnpm test ui/src/ui/controllers/chat.test.ts ui/src/ui/chat/build-chat-items.test.ts
- pnpm exec oxfmt --check --threads=1 CHANGELOG.md docs/web/control-ui.md ui/src/ui/chat/build-chat-items.test.ts ui/src/ui/chat/build-chat-items.ts ui/src/ui/chat/heartbeat-display.ts ui/src/ui/controllers/chat.test.ts ui/src/ui/controllers/chat.ts
- git diff --check
- pnpm check:changelog-attributions
- Testbox: pnpm check:changed
- In-app browser preview confirmed HEARTBEAT_OK count 0 in the astra chat DOM
2026-05-04 23:59:28 -05:00
Vincent Koc
2de0113608 test(update): cover authenticated restart updates 2026-05-04 21:58:47 -07:00
pashpashpash
70f34bf177 Require real behavior proof for external PRs (#77622)
* ci: require real behavior proof for external PRs

* fix: tighten real behavior proof heuristics

* fix: reject test-only real behavior proof labels

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-05 05:45:30 +01:00
6607changchun
d02fbc6116 fix(sandbox): support Windows drive-letter bind sources
Accept drive-absolute Windows sandbox Docker bind sources in config and runtime validation while keeping blocked-path and allowed-root comparisons case-insensitive for Windows drive paths.

Also remove a stale WhatsApp setup import that blocked extension lint after the rebase.

Co-authored-by: 6607changchun <84566142+6607changchun@users.noreply.github.com>
Co-authored-by: Brad Groux <3053586+BradGroux@users.noreply.github.com>
2026-05-04 23:39:56 -05:00
Peter Steinberger
35266879de feat: add Mantis visual task video QA 2026-05-05 05:35:12 +01:00
Vincent Koc
b0f841ef37 fix(plugins): honor beta channel for auto installs 2026-05-04 21:33:46 -07:00
Vincent Koc
e03fe1e289 fix(telegram): reuse preview for long text finals (#77658)
* fix(telegram): reuse preview for long text finals

* test(qa): cover long telegram finals

* fix(qa): satisfy extension lint

* fix(qa): keep telegram long final fixture to two chunks

* test(telegram): cover three chunk finals

* fix(telegram): force long final preview boundary
2026-05-04 21:19:44 -07:00
Vincent Koc
3290cba1a6 docs: clarify local upgrade baseline specs 2026-05-04 21:15:12 -07:00
Vincent Koc
1c3b27718f ci: shard package upgrade survivor baselines 2026-05-04 21:15:12 -07:00
Peter Steinberger
a91c17c426 fix: explain missing git during plugin install 2026-05-05 05:14:09 +01:00
Bek
cf3ce08b91 fix: slack mention-gating thread participation 2026-05-05 05:11:56 +01:00
pickaxe
712aa96a8f docs: note plugin peer-link update repair 2026-05-05 05:04:22 +01:00
pickaxe
2e8761c5c1 fix(plugins): repair missing openclaw peer links on update 2026-05-05 05:04:22 +01:00
Peter Steinberger
0eb06caae3 fix: start configured generation providers 2026-05-05 05:03:54 +01:00
Vincent Koc
68a500c465 fix(whatsapp): normalize onboarding allowlist numbers
Normalize WhatsApp onboarding allowlist entries to digit-only WhatsApp IDs and reject invalid owner-phone inputs during prompt validation.
2026-05-04 20:59:01 -07:00
Vincent Koc
b546aa91e1 fix(update): authenticate restart health probes 2026-05-04 20:14:02 -07:00
Kelaw - Keshav's Agent
a373468d82 fix: recover missing Codex bound threads 2026-05-05 04:04:58 +01:00
Peter Steinberger
761e668acf fix: recover stuck diagnostic sessions safely 2026-05-05 04:01:37 +01:00
兰之
c739088d62 fix(agents): enforce exact skill path from <available_skills> [AI-assisted] (#74161)
Summary:
- The PR updates agents skill prompt guidance to require exact `<location>` paths for single- and multi-skill selection, adds prompt assertions, and records the fix in the changelog.
- Reproducibility: yes. Static source reproduction is enough: current main lacks the exact-`<location>` guard  ... illsSection()`, while the PR diff adds it to both selection branches and asserts the resulting prompt text.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix: enforce exact skill paths for all skill matches

Validation:
- ClawSweeper review passed for head 743c9840c1.
- Required merge gates passed before the squash merge.

Prepared head SHA: 743c9840c1
Review: https://github.com/openclaw/openclaw/pull/74161#issuecomment-4341488109

Co-authored-by: tianguicheng <tianguicheng@xiaomi.com>
Co-authored-by: sallyom <somalley@redhat.com>
2026-05-05 02:51:31 +00:00
Bek
58c4f9e190 fix: slack keep resumed sends in thread (#77620)
carry agent thread context into the message tool so resumed Slack parent sends inherit the ambient thread when no explicit threadId is provided
2026-05-04 22:39:46 -04:00
saram ali
978bc53e80 fix(gateway): skip IPv6 loopback binding on Windows (#69701)
Bind the default loopback gateway listener only to `127.0.0.1` on Windows so libuv dual-stack `::1` behavior cannot wedge localhost HTTP requests.

Also keeps non-Windows dual-loopback behavior covered, replaces the redundant Windows passthrough test with guard coverage, and adds the required changelog entry.

Fixes #69674.

Tests:
- pnpm exec oxfmt --check --threads=1 CHANGELOG.md src/gateway/net.ts src/gateway/net.test.ts
- pnpm test src/gateway/net.test.ts
- pnpm check:changed
- GitHub required checks: green

Thanks @SARAMALI15792.

Co-authored-by: saram ali <140950904+SARAMALI15792@users.noreply.github.com>
Co-authored-by: Brad Groux <3053586+BradGroux@users.noreply.github.com>
2026-05-04 20:45:01 -05:00
Vincent Koc
30bb88d80e test(live): prefer stable OpenAI cache model 2026-05-04 18:40:23 -07:00
Juan Flores
46a04099a4 fix(infra): skip POSIX tmp path on Windows (#73533)
Skip the POSIX `/tmp/openclaw` preferred path on Windows so temp files land under the trusted `os.tmpdir()`/`%TEMP%`-based `openclaw-<uid>` path instead of `C:\tmp\openclaw`.

Add regression coverage for Windows path selection and the WhatsApp media temp directory integration, plus a changelog entry.

Fixes #60713.

Tests:
- pnpm exec oxfmt --check --threads=1 CHANGELOG.md src/infra/tmp-openclaw-dir.ts src/infra/tmp-openclaw-dir.test.ts extensions/whatsapp/src/media.test.ts
- pnpm test src/infra/tmp-openclaw-dir.test.ts extensions/whatsapp/src/media.test.ts
- pnpm check:changed

Thanks @juan-flores077.

Co-authored-by: Juan Flores <112629487+juan-flores077@users.noreply.github.com>
Co-authored-by: Brad Groux <3053586+BradGroux@users.noreply.github.com>
2026-05-04 20:32:36 -05:00
Vincent Koc
04b7e4894d docs(channels): add Socket vs HTTP comparison and explain shared URL fields
The Slack docs jumped straight from intro into the Quick Setup tabs
without telling readers when to pick each transport. Add a Choosing
Socket Mode or HTTP Request URLs section above Quick Setup with a
concern-by-concern table (public URL, outbound network, tokens, dev
laptops, scaling, multi-account, slash command transport, signing,
recovery) plus a Note pointing at the right default for each shape.

Also add an Info block under the HTTP Quick Setup manifest explaining
why the manifest carries three url fields (slash_commands[].url,
event_subscriptions.request_url, interactivity.request_url) — Slack's
manifest schema requires them spelled out separately even though
OpenClaw routes by payload type, and slash commands silently no-op
without their url field in HTTP mode.
2026-05-04 18:30:29 -07:00
Peter Steinberger
25b30c9520 fix: honor embedded runtime tool allowlists (#77609)
* fix: honor embedded runtime tool allowlists

* fix: preserve plugin allowlist filtering

* fix: gate bundled lsp allowlists
2026-05-05 02:29:36 +01:00
Peter Steinberger
c84b7cbffc ci(release): speed up focused release reruns 2026-05-05 02:28:22 +01:00
Vincent Koc
0131343db8 docs(doctor): clarify configured plugin repair (#77613) 2026-05-04 18:16:29 -07:00
Vincent Koc
b062bb670d docs(channels): inline Slack manifest into Quick Setup with Recommended/Minimal variants
The Quick Setup steps in docs/channels/slack.md previously sent users to
the `#manifest-and-scope-checklist` anchor lower on the page to copy the
manifest, breaking the copy-paste flow. Pull the manifest inline as a
Mintlify <CodeGroup> for both Socket Mode and HTTP Request URLs tabs and
add a Minimal variant for workspaces that restrict scopes (drops
files:*, reactions:*, pins:*, mpim:*, emoji:read, usergroups:read while
keeping DMs, channel/group history, mentions, App Home, and slash
commands). Recommended matches extensions/slack/src/setup-shared.ts.
Existing Manifest and scope checklist section stays as the canonical
per-scope reference.

Cross-link from docs/concepts/qa-e2e-automation.md so QA maintainers see
the production manifest reference, while keeping the QA Driver/SUT pair
of manifests inline (the lane intentionally needs two distinct apps so
its shape is different from a single-app production install).
2026-05-04 18:16:15 -07:00
Peter Steinberger
a78bb7832c chore(config): refresh merged baseline hash 2026-05-05 02:15:42 +01:00
Peter Steinberger
7168896fdf fix(agents): abort post-compaction loops out-of-band 2026-05-05 02:15:42 +01:00
Peter Steinberger
5dfaed1846 refactor(agents): use loop detection switch for post-compaction guard 2026-05-05 02:15:42 +01:00
Peter Steinberger
dbb2299e38 chore(config): refresh post-compaction guard labels 2026-05-05 02:15:42 +01:00
Peter Steinberger
ed4b223cf2 fix(agents): honor scoped post-compaction guard config 2026-05-05 02:15:42 +01:00
Peter Steinberger
1af6855bb0 refactor(agents): thread post-compaction guard observer 2026-05-05 02:15:42 +01:00
Peter Steinberger
e0fafdcc1d fix(agents): observe post-compaction guard live 2026-05-05 02:15:42 +01:00
Peter Steinberger
0d3b74e45a test(agents): avoid redundant guard scope spread 2026-05-05 02:15:42 +01:00
Peter Steinberger
3ba0f588ad fix(agents): observe matched post-compaction tool outcomes 2026-05-05 02:15:42 +01:00
Eduardo Piva
4c4825679b fix(agents): make post-compaction guard config valid + observation trim-resilient
Two correctness fixes from code review.

1. Zod schema (src/config/zod-schema.agent-runtime.ts) was strict and
   rejected tools.loopDetection.postCompactionGuard.* keys at validation
   time, making the guard's documented configurability inaccessible at
   gateway startup. Adds ToolLoopPostCompactionGuardSchema with both
   optional fields and wires it into ToolLoopDetectionSchema.

2. The runner observation cursor in pi-embedded-runner/run.ts used
   absolute indices into state.toolCallHistory, but that array is
   trimmed at historySize (default 30). Once the buffer was full, new
   records shifted out from under the cursor and the guard silently
   missed every loop in long-running sessions. Replaces the index
   cursor with a monotonic toolOutcomeSeq on SessionState that
   recordToolCallOutcome bumps on each observable push (unmatched
   branch only, mirroring the prior cursor's effective semantics).
   The runner now reads the most recent (currentSeq - lastSeq) entries
   from the tail of toolCallHistory, which is trim-resilient.

Adds zod parse tests for the new config keys (valid, empty, unknown
key, non-positive, non-integer) and a runner regression test that
seeds toolCallHistory at the trim cap before triggering a
post-compaction loop, asserting the abort still fires.

Refs #77474
2026-05-05 02:15:42 +01:00
Eduardo Piva
2a702f927f feat(agents): wire post-compaction loop guard into pi-embedded-runner
Arms the guard at each of the three compaction-success points in
run.ts and observes tool-call outcomes from the diagnostic session
state's toolCallHistory after each attempt. Aborts with
PostCompactionLoopPersistedError when the same (tool, args, result)
triple repeats windowSize times within the post-compaction window.

Refs #77474
2026-05-05 02:15:42 +01:00
Eduardo Piva
5b863c719e fix(agents): address review feedback on post-compaction loop guard
- Add PostCompactionLoopPersistedError.fromVerdict factory.
- Add unit tests for the error class + fromVerdict adapter.
- Disabled guard is now truly dormant (no state mutation when enabled=false).
- Tighten help text for postCompactionGuard.enabled.

Refs #77474
2026-05-05 02:15:42 +01:00
Eduardo Piva
96e7461c81 feat(agents): add post-compaction loop guard module + config
Pure module with unit tests; not yet wired into runner. The guard arms
after auto-compaction-retry and aborts when the same (tool, args, result)
triple repeats within the configured window.

Refs #77474
2026-05-05 02:15:42 +01:00
Vincent Koc
7295f19fbc test(live): use low reasoning for cache probes 2026-05-04 18:13:05 -07:00
Vincent Koc
16f9e83657 test(live): scale gateway profile timeout 2026-05-04 18:10:27 -07:00
Peter Steinberger
0747e63006 test(plugins): align metadata snapshot policy fixtures 2026-05-05 02:09:50 +01:00
Elliot Drel
3e4f076723 fix(lsp): resolve Windows .cmd shims (#75343)
Resolve Windows npm .cmd shim startup failures for bundled LSP servers by routing LSP process spawning through the shared Windows spawn resolver with a sanitized child environment.

The change reuses existing PATH/PATHEXT and .cmd shim handling, keeps non-Windows behavior unchanged, and adds focused regression coverage for resolver wiring, env sanitization, and spawn materialization.

Fixes #75352.

Tests:
- pnpm test src/agents/pi-bundle-lsp-runtime.windows-spawn.test.ts src/agents/pi-bundle-lsp-runtime.test.ts
- pnpm check:changed

Thanks @ElliotDrel.

Co-authored-by: Elliot Drel <156480527+ElliotDrel@users.noreply.github.com>
Co-authored-by: Brad Groux <3053586+BradGroux@users.noreply.github.com>
2026-05-04 20:08:00 -05:00
李claw
25db482cc6 fix(media): use r+ for Windows media fsync (#76593)
Fix Windows media offload failures by opening saved attachment temp files read/write before fsync, preserving the non-truncating temp-file write path while allowing Windows FlushFileBuffers to succeed.

Also adds the required changelog entry.

Tests:
- pnpm test src/media/store.test.ts src/gateway/chat-attachments.test.ts
- pnpm check:changed

Thanks @qq230849622-a11y.

Co-authored-by: 李claw <264894741+qq230849622-a11y@users.noreply.github.com>
Co-authored-by: Brad Groux <3053586+BradGroux@users.noreply.github.com>
2026-05-04 19:58:31 -05:00
Peter Steinberger
f7f5050252 docs: require live proof before landing 2026-05-05 01:55:54 +01:00
Peter Steinberger
47411f7c52 build: bump axios override 2026-05-05 01:51:23 +01:00
Peter Steinberger
2f3a9629d8 test: use latest kitchen sink canary 2026-05-05 01:51:23 +01:00
Peter Steinberger
b17bb63b9e fix: repair stale session route state in doctor 2026-05-05 01:51:23 +01:00
Brandon
e2e0908055 fix(secrets): preserve auth profile key refs during provider scrub (#77489)
* fix(secrets): preserve auth profile key refs during provider scrub

* Add changelog for secrets apply fix

* Seed auth profile ref for scrub regression

* fix(secrets): guard auth profile ref scrub

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-05-04 19:50:39 -05:00
Vincent Koc
b378a91257 test(live): retry cache probe text misses 2026-05-04 17:44:37 -07:00
Vincent Koc
967c0981e3 docs(help,security): cross-reference auth list and trusted-env-proxy
Two missing cross-references uncovered by the 24-hour doc audit:

- docs/help/faq-models.md: link to `openclaw models auth list` from the
  "What is an auth profile?" accordion. The command was added in
  23eb44b045 but the FAQ never pointed users at it.
- docs/security/network-proxy.md: list `tools.web.fetch.useTrustedEnvProxy`
  in Related Proxy Terms. The opt-in is fully documented in
  docs/tools/web-fetch.md but the proxy reference page omitted the
  cross-reference, leaving the page incomplete for proxy-state triage.
2026-05-04 17:43:09 -07:00
Peter Steinberger
2d8fa23447 fix(agents): mediate async media completions 2026-05-05 01:42:50 +01:00
Peter Steinberger
48ff390953 perf(plugins): reuse compatible metadata snapshots
Reuse compatible workspace-scoped plugin metadata snapshots for unscoped model catalog and manifest-contract readers while preserving env/config/workspace compatibility checks.

Also updates the stale kitchen-sink prerelease canary assertion to the current script default.

Fixes #77519.
Related #77532.
2026-05-05 01:39:34 +01:00
Vincent Koc
b38e674c9f docs(cli): document gateway restart --safe in command options
The `gateway restart` Command-options accordion only listed `--force`,
`--wait`, and `--json` even though `--safe` is a fully-supported flag
(documented in the prose at line 112 and rejected by lifecycle.ts when
combined with --force/--wait). Add --safe to the option list and a
Lifecycle-behavior bullet that explains the preflight-defer behavior
plus its mutual exclusion with --force and --wait, matching
src/cli/daemon-cli/lifecycle.ts:153-156.
2026-05-04 17:38:32 -07:00
Sarah Fortune
d6e991db49 Add instructions for how to setup slack for QA tests (#77606) 2026-05-04 17:38:16 -07:00
Peter Steinberger
5e0b890a5b docs: thank Discord reply fix contributor (#77596) 2026-05-05 01:33:22 +01:00
Patrick Erichsen
9e97cdb213 fix(discord): fail dropped final reply delivery 2026-05-05 01:33:22 +01:00
Vincent Koc
1a4c078399 test(plugins): update kitchen sink prerelease canary version 2026-05-04 17:30:55 -07:00
Vincent Koc
7e591a624f docs(changelog): credit @pashpashpash for Codex usage-limit reset surfacing
#77557 added user-facing surfacing of Codex app-server usage-limit reset
details and OpenClaw-owned runtime failure notices through tool-only
source-reply mode, but the entry landed without contributor attribution.
Add the merging PR ref and credit the human contributor @pashpashpash
per CLAUDE.md changelog-attribution rules.
2026-05-04 17:28:16 -07:00
Peter Steinberger
b156829505 fix(acpx): resolve plugin manifest from bundled runtime 2026-05-05 01:21:42 +01:00
Vincent Koc
44a10ceea2 test(live): run cache probe with node 2026-05-04 17:16:47 -07:00
Vincent Koc
da0a977678 test(plugins): refresh kitchen sink docker fixture 2026-05-04 17:15:47 -07:00
Vincent Koc
2cb03ee7b5 fix(openai): route Codex audio to transcription model 2026-05-04 17:14:08 -07:00
Vincent Koc
a491090b48 fix(release): refresh plugin sdk api gate
Refresh release baseline hashes and raise the Plugin SDK API baseline heap cap so release preflight reports real drift instead of OOMing.
2026-05-04 17:13:47 -07:00
pashpashpash
b2c3202a15 Surface Codex usage-limit reset details in chat replies (#77557)
* fix(codex): surface usage limit reset details

* fix(codex): satisfy extension lint

* fix: surface codex runtime failures in tool-only replies
2026-05-05 09:00:39 +09:00
Vincent Koc
306a582294 test(agents): remove redundant payload casts 2026-05-04 16:56:05 -07:00
Vincent Koc
d253392ea2 fix(plugins): keep explicit web providers on fast path 2026-05-04 16:56:05 -07:00
Josh Lehman
6dae3c273d fix: keep runtime prompt context out of system prompt (#77521) 2026-05-05 00:54:16 +01:00
Kevin Lin
cb9824d6b4 test: add slack onboarding channel smoke (#77575) 2026-05-04 16:51:34 -07:00
Vincent Koc
cf1bd30509 test(plugins): add kitchen sink rpc walk 2026-05-04 16:48:02 -07:00
Vincent Koc
0a62c1e665 fix(telegram): derive media placeholders from MIME
Fixes #69793.

Verification:
- repro before fix: `pnpm test:serial extensions/telegram/src/bot-message-context.body.test.ts -- --reporter=verbose` failed 3 new cases with `<media:image>` returned for non-image/mixed saved media
- `pnpm test:serial extensions/telegram/src/bot-message-context.body.test.ts -- --reporter=verbose` passed 9 tests after fix
- `pnpm exec oxfmt --check --threads=1 extensions/telegram/src/bot-message-context.body.ts extensions/telegram/src/bot-message-context.body.test.ts`
- `git diff --check`
- `OPENCLAW_TESTBOX=1 pnpm testbox:run --id tbx_01kqtnnhpg6rk1225tbb7109kf -- "pnpm check:changed"` passed
2026-05-04 16:46:58 -07:00
Peter Steinberger
d522a18971 fix: sync Codex app-server protocol (#77578)
* fix: sync codex app-server protocol

* docs: add codex protocol changelog

* fix: refresh codex protocol schemas
2026-05-05 00:43:07 +01:00
praveen9354
0677a4f8b3 fix(dashboard): guide manual token auth fallback
Summary:
- Add a redaction-safe dashboard fallback hint when tokenized URL delivery fails.
- Document the manual auth path and update the changelog.

Verification:
- PR CI exact head 48ccb97c08 green for relevant CI/security checks.
- pnpm test src/commands/dashboard.links.test.ts src/commands/dashboard.test.ts
- pnpm exec oxfmt --check --threads=1 src/commands/dashboard.ts src/commands/dashboard.links.test.ts
- pnpm format:docs:check
- pnpm docs:check-mdx
- pnpm docs:check-i18n-glossary
- targeted markdownlint for docs/cli/dashboard.md and docs/web/dashboard.md
2026-05-04 18:39:25 -05:00
Peter Steinberger
29eb47e736 docs(changelog): credit codex replay fix 2026-05-05 00:33:23 +01:00
Peter Steinberger
15d3fd83bb fix(openai-codex): match codex replay identity 2026-05-05 00:33:23 +01:00
mkdev11
27e467ad23 fix(openai-codex): avoid stale Responses replay state 2026-05-05 00:33:23 +01:00
Vincent Koc
8285786c22 fix(plugins): include json5 in memory runtime deps
* fix(plugins): include json5 in memory runtime deps

* chore(ci): allow bundled runtime json5 dep
2026-05-04 16:31:49 -07:00
Vincent Koc
ae142cad7c fix(doctor): repair allow-only official plugins 2026-05-04 16:31:41 -07:00
Penchan
d0c7f91ed1 fix(plugins): recover managed-npm external plugins after package-manager upgrade
Co-authored-by: pingu <pingu@penchan.co>
2026-05-04 16:31:35 -07:00
Vincent Koc
9eed48fde5 test(docker): align published upgrade timeout 2026-05-04 16:17:51 -07:00
Vincent Koc
edb697e389 test(extensions): refresh dependency-backed assertions 2026-05-04 16:17:51 -07:00
Peter Steinberger
ab032675ce fix: simplify gateway model startup modes 2026-05-05 00:07:11 +01:00
Shubhankar Tripathy
34f805a012 fix(docker): pin container-side workspace and config dirs in compose
Fixes #77436
2026-05-04 16:03:51 -07:00
Peter Steinberger
03f7e26d54 fix: log gateway model mode defaults 2026-05-04 23:53:05 +01:00
Peter Steinberger
c3c7c2df6f fix(discord): prefer IPv4 for gateway startup 2026-05-04 23:52:45 +01:00
Peter Steinberger
d362879282 fix(plugins): normalize compat allowlist aliases 2026-05-04 23:50:03 +01:00
Peter Steinberger
40e0844133 fix(plugins): preserve bundled allowlist edges 2026-05-04 23:50:03 +01:00
Peter Steinberger
fdbfabf9f9 test(plugins): make loader compat contract explicit 2026-05-04 23:50:03 +01:00
Peter Steinberger
369d83f04c test(doctor): preserve bundled discovery literal type 2026-05-04 23:50:03 +01:00
Peter Steinberger
b2096d19ec fix(plugins): default bundled discovery to allowlist 2026-05-04 23:50:03 +01:00
Peter Steinberger
55df2d4598 docs(config): refresh bundled discovery baseline 2026-05-04 23:50:03 +01:00
Peter Steinberger
41257a5f6f fix(plugins): rename bundled allowlist discovery policy 2026-05-04 23:50:03 +01:00
Peter Steinberger
76e0bcd2de test(plugins): type bundled public artifact mock 2026-05-04 23:50:03 +01:00
Peter Steinberger
3ed569ac3c fix(plugins): respect allowlist for web provider fallback 2026-05-04 23:50:03 +01:00
dougbtv
f738663c79 fix(plugins): add bundledMode to gate runtime provider discovery by allowlist
When plugins.bundledMode is set to "respect-allow", runtime provider
discovery paths honor plugins.allow for bundled plugins instead of
force-loading all providers. Default "compat" preserves existing behavior.

Closes #75575
2026-05-04 23:50:03 +01:00
dougbtv
81035e651b fix(config): register bundledMode in zod schema and help text
Addresses review feedback: adds bundledMode to the strict plugins zod
object so the config option passes validation, and adds schema.help
documentation for the field.
2026-05-04 23:50:03 +01:00
Vincent Koc
eef623671e Merge branch 'main' of https://github.com/openclaw/openclaw
* 'main' of https://github.com/openclaw/openclaw:
  test(doctor): mock bundled channel ids
  fix(gateway): route watch trace spam to artifacts
2026-05-04 15:42:51 -07:00
Vincent Koc
68d4921392 test(doctor): mock bundled channel ids 2026-05-04 15:41:43 -07:00
Peter Steinberger
a167acee67 fix(gateway): route watch trace spam to artifacts 2026-05-04 23:41:14 +01:00
Vincent Koc
5658332fa7 Merge branch 'main' of https://github.com/openclaw/openclaw
* 'main' of https://github.com/openclaw/openclaw:
  fix: repair release validation checks
2026-05-04 15:40:17 -07:00
Vincent Koc
ce8bc1a3e3 fix(lint): cover diagnostic phase events 2026-05-04 15:40:00 -07:00
Peter Steinberger
864b1be1b3 fix: repair release validation checks 2026-05-04 23:39:37 +01:00
Vincent Koc
e39d3b4223 fix(mattermost): clarify model picker runtime behavior 2026-05-04 15:38:14 -07:00
Peter Steinberger
f8e080386d fix(gateway): quiet benchmark watch output 2026-05-04 23:36:18 +01:00
Vincent Koc
f042b53782 fix(channels): preserve channel aliases in plugin probes
Key package-state probes, env/config presence, and read-only command defaults by channel id instead of manifest plugin id so alias-owned channel plugins keep setup/native-command detection working.
2026-05-04 15:29:47 -07:00
Kevin Lin
592998ae0e fix: clean up orphaned child processes (#77481)
* fix: forward launcher respawn signals

* docs: explain respawn signal exit timer

* fix: centralize launcher respawn supervision

* fix: include respawn helper in duplicate scan

* fix: keep launcher respawn bridge local
2026-05-04 15:28:49 -07:00
Peter Steinberger
9f2c8a6ab6 fix: clarify slack socket retry errors 2026-05-04 23:26:25 +01:00
Peter Steinberger
d82992f0ae docs: add gateway diagnostics changelog 2026-05-04 23:26:25 +01:00
Peter Steinberger
d6917edc53 fix: preserve gateway watch trace overrides 2026-05-04 23:26:25 +01:00
Peter Steinberger
35e48a049b fix: enable sync io tracing in gateway watch 2026-05-04 23:26:25 +01:00
Peter Steinberger
e84d4b27f4 feat: add gateway stall diagnostics 2026-05-04 23:26:25 +01:00
Peter Steinberger
358cd87ff3 ci(release): split release soak validation 2026-05-04 23:25:06 +01:00
Vincent Koc
ac3cd1a0ca Harden Codex harness control surfaces (#77459)
* fix(scripts): find codex protocol source from worktrees

* fix(test): keep codex harness docker caches writable

* fix(test): relax live codex cache mount permissions

* test(codex): add live docker harness debug output

* fix(test): detect numeric ci env in codex docker harness

* fix(codex): skip duplicate agent-command telemetry

* fix(tooling): skip sparse-missing oxlint tsconfig

* fix(tooling): route changed checks through testbox

* fix(qa): keep coverage json source-clean

* fix(test): preflight codex docker auth

* fix(codex): validate bind option values

* fix(codex): parse quoted command arguments

* fix(codex): reject extra control args

* fix(codex): use content for blank bound prompts

* fix(codex): decode local image file urls

* fix(codex): treat local media urls as images

* fix(codex): keep windows media paths local

* fix(codex): reject malformed diagnostics confirmations

* fix(codex): reject malformed resume commands

* fix(codex): reject malformed thread actions

* fix(codex): reject malformed turn controls

* fix(codex): reject malformed model controls

* fix(codex): resolve empty user input prompts

* fix(codex): enforce user input options

* fix(codex): reject ambiguous computer-use actions

* fix(codex): ignore stale bound turn notifications

* test(gateway): close task registries in gateway harness

* test(gateway): route cleanup through task seams

* fix(codex): describe current permission approvals

* fix(codex): disclose command approval amendments

* fix(codex): preserve approval detail under truncation

* fix(codex): propagate dynamic tool failures

* test(codex): align dynamic tool block contract

* fix(codex): reject extra read-only command operands

* fix(codex): escape command readout fields

* fix(codex): escape status probe errors

* fix(codex): narrow formatted thread details

* fix(codex): escape successful status summaries

* fix(codex): escape bound control replies

* fix(codex): escape user input prompts

* fix(codex): escape control failure replies

* fix(codex): escape approval prompt text

* test(codex): narrow escaped reply assertions

* test(codex): complete strict reply fixtures

* test(codex): preserve account fixture literals

* test(codex): align status probe fixtures

* fix(codex): satisfy sanitizer regex lint

* fix(codex): harden command readouts

* fix(codex): harden bound image inputs

* fix(codex): sanitize command failure replies

* test(codex): complete rate limit fixture

* test(tooling): isolate postinstall compile cache fixture

* fix(codex): keep app-server event ownership explicit

---------

Co-authored-by: pashpashpash <nik@vault77.ai>
2026-05-05 07:23:41 +09:00
hcl
b3e42bf327 fix(plugins): emit actionable install hint for externalized channel plugins (#77502)
Fixes #77483.\n\n- Suggest catalog-backed install commands for missing official external plugins in config validation.\n- Preserve stale/remove wording for non-catalog missing plugins.\n- Add regression coverage for plugins.entries and plugins.allow warnings.\n\nVerification:\n- pnpm exec oxfmt --check --threads=1 CHANGELOG.md src/config/validation.ts src/config/config.plugin-validation.test.ts\n- pnpm test src/config/config.plugin-validation.test.ts src/commands/doctor/shared/missing-configured-plugin-install.test.ts\n- pnpm crabbox:run -- --provider blacksmith-testbox ... pnpm check:changed\n- GitHub CI green on d1b1b10444
2026-05-04 15:22:15 -07:00
Chunyue Wang
14aa98827a fix(codex/app-server): stable mirror idempotency to prevent transcript loss (#77046)
* fix(codex/app-server): stable mirror idempotency to prevent transcript loss

* Changelog: note codex/app-server transcript mirror dedupe stabilization (#77046)
2026-05-04 23:17:00 +01:00
Peter Steinberger
be6543caf8 fix(doctor): preserve active auth profile metadata 2026-05-04 23:16:43 +01:00
Vincent Koc
a07d8cbf8a fix(docker): normalize plugin build args 2026-05-04 15:16:22 -07:00
Vincent Koc
0908f3d538 docs(changelog): note model runtime switch repair UX 2026-05-04 15:15:32 -07:00
Vincent Koc
57ca91ff38 fix(telegram): clarify model picker runtime scope 2026-05-04 15:15:32 -07:00
Vincent Koc
e091d912ce fix(model): guide runtime allowlist repairs 2026-05-04 15:15:32 -07:00
Brandon
70b1c17ae0 fix(config): prefer plugin ids for built-in channel claims
Prefer the manifest plugin id when auto-allowlisting configured built-in channel aliases, with regression coverage for alias/id split plugins and same-name official channel plugins.
2026-05-04 15:15:18 -07:00
Peter Steinberger
683d892eed test: make global install shell test portable 2026-05-04 23:13:58 +01:00
Peter Steinberger
a7263de258 fix(agents): preserve workspace metadata reuse
Pass the resolved agent workspace through hot model refresh paths so workspace-scoped plugin metadata snapshots can be reused.

Refs #77519.
Refs #77532.
2026-05-04 23:13:32 +01:00
Peter Steinberger
343f859b90 fix: preserve visible Discord labeled replies 2026-05-04 23:13:18 +01:00
Peter Steinberger
31edc4ee1a fix: clean up startup readiness PR docs 2026-05-04 23:13:18 +01:00
Satoshi
5572c8137c fix(discord): preserve non-text payloads in reply scrub 2026-05-04 23:13:18 +01:00
Satoshi
e259938e96 fix: harden startup readiness and discord replies
(cherry picked from commit 3956672106b3387d42427a485a9ca01e77f3b78f)
2026-05-04 23:13:18 +01:00
Vincent Koc
7e229f0d3d fix(docker): prune external plugin dist (#77547) 2026-05-04 15:11:14 -07:00
Peter Steinberger
8ee08b2b77 chore: update dependencies 2026-05-04 23:07:09 +01:00
Mogglemoss
43b5df7295 fix(secretrefs): resolve external channel contracts in dist/ sidecars (#77421)
* fix(secretrefs): resolve external channel contracts in dist/ sidecars

Externalized channel plugins published to npm (e.g. @openclaw/discord
since 2026.5.2) keep their compiled secret-contract-api artifact under
<rootDir>/dist/, per the package.json `openclaw.runtimeExtensions`
convention. The runtime contract loader added in #76449 only searched
the rootDir, so npm-installed plugins silently dropped their channel
SecretRef contracts: the runtime snapshot left `channels.<id>.token`
as an unresolved SecretRef, the plugin's `isConfigured` check then
returned false, and the gateway recorded `error: not configured`
without firing the usual channel startup logs.

Look in `<rootDir>/dist/` as well as `<rootDir>/`, preferring dist
when running from a built openclaw artifact and rootDir when running
from source. The new `loads dist/ secret-contract-api sidecars …`
test in channel-contract-api.external.test.ts mirrors the real
npm-package layout and fails without this change.

Refs #76371. Fixes #77416.

* docs: credit changelog contributor

---------

Co-authored-by: Magpie <magpie@local>
Co-authored-by: joshavant <830519+joshavant@users.noreply.github.com>
2026-05-04 16:57:28 -05:00
Pnant
a7b665cfed fix(telegram): honor topic requireMention precedence
Telegram forum-topic requireMention config now takes precedence over persisted activation state, with focused regression coverage.\n\nFixes #49864.\nThanks @Panniantong.
2026-05-04 22:53:06 +01:00
hcl
d0cae0d950 fix(active-memory): skip sub-agent gracefully when no memory tools registered (#77506) (#77515)
* fix(active-memory): skip sub-agent gracefully when no memory tools registered (#77506)

When memory-core and memory-lancedb are both absent, the embedded
memory sub-agent would throw 'No callable tools remain after resolving
explicit tool allowlist', which propagated as a noisy warning through
the before_prompt_build hook. Catch this specific error in
runActiveMemorySubAgent and return an empty NONE result so the
gateway log stays clean and the sub-agent run is skipped without
disrupting the parent session.

* fix(active-memory): skip missing memory-tool subagent runs

* fix(active-memory): match inherited missing memory tool errors

* fix(active-memory): preserve policy-filtered memory errors

---------

Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-05-04 16:47:38 -05:00
Penchan
1c52447f0b fix(plugins): treat CalVer correction versions as compatible with plugin API ranges (#77450)
* fix(plugins): accept CalVer correction plugin API hosts

Fixes #77293

* docs(changelog): credit plugin api calver fix pr

---------

Co-authored-by: pingu <pingu@penchan.co>
2026-05-04 14:46:29 -07:00
Vincent Koc
a4f2bf273a fix(openai): default direct responses to sse 2026-05-04 14:37:07 -07:00
Peter Steinberger
5005f5b22e docs(changelog): note npm script shell update fix 2026-05-04 22:34:30 +01:00
Peter Steinberger
4556707cb7 test(browser): mirror route URL guard in existing-session helper 2026-05-04 22:29:13 +01:00
Peter Steinberger
0909df1a4f refactor: centralize reply followup drain lifecycle 2026-05-04 22:25:16 +01:00
Peter Steinberger
86385f72e9 fix(update): use absolute npm script shell 2026-05-04 22:24:34 +01:00
Peter Steinberger
828b6be39d fix(cli): bound sessions list output 2026-05-04 22:18:25 +01:00
Peter Steinberger
14b5f73e2a fix(agents): avoid duplicate generated media attachments 2026-05-04 22:14:43 +01:00
Vincent Koc
29a3e71106 Merge branch 'main' of https://github.com/openclaw/openclaw
* 'main' of https://github.com/openclaw/openclaw:
  fix(update): isolate plugin sync failures
2026-05-04 14:07:57 -07:00
Vincent Koc
ed1089f822 test(plugins): source Testbox auth for kitchen sink live 2026-05-04 14:07:03 -07:00
Vincent Koc
7c0f5463a5 fix(update): isolate plugin sync failures
Disable and skip plugins that fail package-update plugin sync so broken plugin packages do not fail an otherwise successful OpenClaw update.
2026-05-04 14:06:44 -07:00
Vincent Koc
fdaa5a0c3d fix(update): exit post-core resume without result path 2026-05-04 14:06:18 -07:00
Sally O'Malley
02ac7dc5a6 fix(openrouter): keep DeepSeek V4 reasoning effort valid (#77423)
Summary:
- The PR removes `max` from OpenRouter DeepSeek V4 thinking profiles, maps stale OpenRouter `max` overrides to `xhigh`, preserves direct DeepSeek behavior, and updates docs, tests, and changelog.
- Reproducibility: yes. Source inspection on current main shows OpenRouter DeepSeek V4 advertises `max` and se ... ffort: "max"`, matching the linked 400 logs; I did not need a live OpenRouter request for this assist pass.

Automerge notes:
- Ran the ClawSweeper repair loop before final review.
- Addressed earlier ClawSweeper review findings before merge.
- Included post-review commit in the final squash: docs(changelog): credit OpenRouter duplicate fix
- Included post-review commit in the final squash: fix(openrouter): keep DeepSeek V4 reasoning effort valid

Validation:
- ClawSweeper review passed for head becdea4223.
- Required merge gates passed before the squash merge.

Prepared head SHA: becdea4223
Review: https://github.com/openclaw/openclaw/pull/77423#issuecomment-4372880583

Co-authored-by: sallyom <somalley@redhat.com>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
2026-05-04 21:05:05 +00:00
Peter Steinberger
a9817a5f97 fix(gateway): clear reply run before followup drain 2026-05-04 22:04:32 +01:00
Vincent Koc
e2eb8e3cfe test(plugins): harden kitchen sink live gauntlet 2026-05-04 14:01:59 -07:00
Vincent Koc
a71f906837 fix(browser): guard existing-session screenshots 2026-05-04 13:56:33 -07:00
Vincent Koc
59b5058cdb fix(active-memory): stabilize timeout partial recovery 2026-05-04 13:56:12 -07:00
Peter Steinberger
4820b701a5 fix(plugins): fall back from invalid beta npm updates 2026-05-04 21:55:08 +01:00
Josh Lehman
0fc8afeac9 test(package): cover stale source plugin shadows
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-05-04 21:55:08 +01:00
Vincent Koc
112924b113 fix(update): keep plugin install runtime aliases stable 2026-05-04 21:55:08 +01:00
Vincent Koc
b63336186a fix(update): stage npm-prefix package updates cleanly
Co-authored-by: Josh Lehman <josh@martian.engineering>
2026-05-04 21:55:08 +01:00
Brad
be8b4dc845 fix(agents): honor hook bootstrap content (#77501)
* Problem: `agent:bootstrap` hooks can inject `BOOTSTRAP.md` content, but embedded-runner bootstrap routing decided whether bootstrap was pending before hook-adjusted files were considered.
* Fix: preload hook-adjusted bootstrap files before routing, treat non-empty hook-provided `BOOTSTRAP.md` as pending and accessible bootstrap content, and reuse the preloaded files when building Project Context.
* Tests: added routing + context-engine regression coverage for hook-injected bootstrap content.

Co-authored-by: ificator <8387253+ificator@users.noreply.github.com>
Co-authored-by: galiniliev <galini@microsoft.com>
2026-05-04 13:48:40 -07:00
Vincent Koc
7b86481c94 fix(plugins): trust chat catalog installs 2026-05-04 13:46:11 -07:00
Peter Steinberger
06056926a0 fix(plugins): trust official diagnostics installs (#77516) 2026-05-04 13:39:23 -07:00
Peter Steinberger
021373a454 ci(release): recover Windows packaged update no-restart timeout 2026-05-04 21:34:24 +01:00
Devin Robison
982d123b80 Harden Windows command wrapper resolution (#77472)
* Harden Windows command wrapper resolution

* clawsweeper: route Windows cmd.exe wrapper through getWindowsInstallRoots

Replace the local SystemRoot/windir/SYSTEMROOT/WINDIR scan in
resolveTrustedWindowsCmdExe with the shared getWindowsInstallRoots()
resolver from src/infra/windows-install-roots.ts. The shared resolver
already rejects UNC paths, root-relative values, semicolon-delimited
path-lists, and missing-drive-letter roots, and prefers registry-derived
roots over env, so the wrapper-launch trust boundary now matches the
existing Windows install-root boundary on main.

Tests:
- _resetWindowsInstallRootsForTests in beforeEach so cached roots track
  per-test process.env mutations
- expectedTrustedCmdExe helper now joins the resolved systemRoot, so the
  expected wrapper executable matches the production resolver on Linux
  CI (where it falls back to DEFAULT_WINDOWS_SYSTEM_ROOT)
- new "rejects unsafe Windows root values" test covers UNC,
  semicolon-delimited path-list, root-relative, and bare-relative
  SystemRoot inputs

* Add CHANGELOG entry for #77472 Windows command wrapper hardening

* clawsweeper: stub registry probe in Windows wrapper tests

On real Windows CI runners getWindowsInstallRoots() reads the canonical
SystemRoot from the registry (e.g. C:\WINDOWS) before falling back to
process.env, which shadowed the env-only setup in the ComSpec-poisoning
and unsafe-root tests and produced casing mismatches like
"C:\WINDOWS\System32\cmd.exe" vs the expected "C:\Windows\...". Pass a
queryRegistryValue stub returning null in beforeEach (and inside the
unsafe-root loop) so install-root resolution is fully driven by the
test's process.env setup on every platform.

* clawsweeper: overwrite WINDIR alongside SystemRoot in unsafe-root test

Real Windows runners did not honor `delete process.env.windir`, so the
unsafe-root iteration's WINDIR fallback still resolved to the canonical
`C:\WINDOWS` and produced a casing mismatch against the expected default
`C:\Windows\System32\cmd.exe`. Set both `SystemRoot` and `WINDIR` to the
unsafe payload so every install-root env source is rejected by
`normalizeWindowsInstallRoot` and the resolver falls through to
`DEFAULT_WINDOWS_SYSTEM_ROOT`.
2026-05-04 14:33:18 -06:00
Vincent Koc
4fab34a63b docs(changelog): note update and slack fixes 2026-05-04 13:19:31 -07:00
Vincent Koc
3af3fcfebe fix(update): exit post-core package child 2026-05-04 13:16:02 -07:00
Vincent Koc
3fb8c405ed fix(update): finish post-core package updates 2026-05-04 13:10:24 -07:00
Agustin Rivera
ef0dbcf49d Guard current browser tab exports (#75731)
* fix(browser): guard current tab exports

* fix(browser): expand tab guard coverage

* fix(browser): guard tab reads

* fix(browser): guard screenshot route

* changelog: PR #75731

---------

Co-authored-by: Devin Robison <drobison@nvidia.com>
2026-05-04 14:07:17 -06:00
Syu
f2efe33afc Fix Active Memory memory-only recall latency (#75200)
Summary:
- The PR adds a bounded latest-message search-query section to Active Memory recall prompts, regression coverage for metadata stripping, a changelog entry, and pending-final-delivery session slot reservations.
- Reproducibility: yes. for a source-level reproduction path: an eligible interactive turn reaches Active Memo ... om current releases, but I did not run a live gateway/provider reproduction under the read-only constraint.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(plugins): reserve final delivery session slots

Validation:
- ClawSweeper review passed for head 24bf408e75.
- Required merge gates passed before the squash merge.

Prepared head SHA: 24bf408e75
Review: https://github.com/openclaw/openclaw/pull/75200#issuecomment-4354978044

Co-authored-by: SYU8384 <zhuqimo@gmail.com>
2026-05-04 20:05:15 +00:00
Devin Robison
8b2bf7b2e9 Harden update environment path resolution (#77470)
* Harden update environment path resolution

* docs(changelog): credit windows update env path hardening

Adds the user-facing Unreleased Fixes entry for the workspace LOCALAPPDATA
blocklist + portable Git path-prepend hardening change in this PR.
2026-05-04 13:51:09 -06:00
Peter Steinberger
f368201790 docs: credit Codex context PR (#76824) 2026-05-04 20:48:51 +01:00
VACInc
8cf1800ee9 fix codex thread continuity 2026-05-04 20:48:51 +01:00
Peter Steinberger
5de7f99801 ci(release): fix ClawHub runtime preflight command
(cherry picked from commit 954b25e129)
2026-05-04 20:45:37 +01:00
Peter Steinberger
94f8f1914e test(release): match versioned Windows upgrade tarballs
(cherry picked from commit b70dbe32d0)
2026-05-04 20:45:37 +01:00
Peter Steinberger
2e399e6f1a test(release): recover known Windows packaged upgrade timeout
(cherry picked from commit 8f7399e9e9)
2026-05-04 20:45:37 +01:00
Peter Steinberger
3921e1b0b7 fix(process): kill Windows command trees on timeout
(cherry picked from commit 9cc3ae100b)
2026-05-04 20:44:27 +01:00
Vincent Koc
a3f6f24b79 ci: gate slack live qa credentials 2026-05-04 12:13:43 -07:00
Peter Steinberger
2d849bbafa docs(changelog): credit group config migration fix
Credit @scoootscooob for #77465.
2026-05-04 20:02:13 +01:00
scoootscooob
ee314e4236 fix(doctor): restore group config drift migrations (#77465) 2026-05-04 12:00:05 -07:00
Vincent Koc
de4903ec7a fix(agents): refresh deferred subagent delivery text 2026-05-04 11:54:36 -07:00
Devin Robison
9aad2b82c3 Use trusted Windows browser helper root (#77469) 2026-05-04 12:51:26 -06:00
Vincent Koc
8c7ec5d1f9 docs(changelog): credit @NikolaFC and @MertBasar0 for gateway and main-session fixes
#76923 (Satoshi F. / @NikolaFC) added user-facing `gateway.restart.safe`
preflight alignment and #75280 (Mert Başar / @MertBasar0) added
user-facing main-session pending-delivery marker preservation, but both
entries landed without contributor attribution. Add the merging PR refs
and credit the human contributors per CLAUDE.md changelog-attribution
rules.
2026-05-04 11:49:02 -07:00
Devin Robison
edddb07f20 fix(qqbot): preserve framework command authorization (#77453)
* fix(qqbot): preserve framework command authorization

* Add changelog entry for PR #77453
2026-05-04 12:38:51 -06:00
hcl
dff437a1cb fix(active-memory): skip colon-containing session-store channels to prevent crash with QQ c2c agent IDs (#77402)
Summary:
- The PR filters colon-containing store-derived Active Memory channel values before embedded recall resolution, adds a QQ c2c regression test, and records the user-facing changelog entry.
- Reproducibility: yes. Source inspection on current main shows a stored colon-containing `lastChannel` or `ch ... come the strong embedded recall channel, and the downstream bundled-plugin directory validator rejects `:`.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fixup! fix(active-memory): add changelog contributor credit (clawswee…
- PR branch already contained follow-up commit before automerge: fix(active-memory): skip colon-containing session-store channels

Validation:
- ClawSweeper review passed for head 4bf00dd6ac.
- Required merge gates passed before the squash merge.

Prepared head SHA: 4bf00dd6ac
Review: https://github.com/openclaw/openclaw/pull/77402#issuecomment-4372618783

Co-authored-by: HCL <chenglunhu@gmail.com>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
2026-05-04 18:37:05 +00:00
Vincent Koc
417660b662 docs(plugins): explain catalog install trust 2026-05-04 11:31:29 -07:00
Vincent Koc
daefb5e341 fix(plugins): trust catalog package installs 2026-05-04 11:30:36 -07:00
Vincent Koc
9dc38f37ea chore: ignore crabbox artifacts 2026-05-04 11:30:36 -07:00
Vincent Koc
841eb81baf chore: better explicit message on whatsapp 2026-05-04 11:30:36 -07:00
Vincent Koc
fc7e2a10c8 fix(plugins): reserve pending delivery session slots 2026-05-04 11:21:59 -07:00
Vincent Koc
2511be5244 test(release): skip restart in package upgrade lane 2026-05-04 11:21:59 -07:00
stain lu
74ab62c6a2 fix: pass claude cli thinking effort (#77410)
Summary:
- Adds a plugin-owned CLI backend argument rewrite hook and wires Anthropic `claude-cli` to translate non-off `/think` levels into Claude Code `--effort`, with docs, changelog, API baseline, and tests.
- Reproducibility: yes. Current main has a high-confidence source reproduction: choose `claude-cli`, set a non ... builds argv from backend args that contain no `--effort` even though `thinkLevel` exists on the run params.

Automerge notes:
- No ClawSweeper repair was needed after automerge opt-in.

Validation:
- ClawSweeper review passed for head be17754009.
- Required merge gates passed before the squash merge.

Prepared head SHA: be17754009
Review: https://github.com/openclaw/openclaw/pull/77410#issuecomment-4372812685

Co-authored-by: stainlu <stainlu@newtype-ai.org>
2026-05-04 18:13:53 +00:00
Satoshi F.
103cdd9d96 fix(gateway): add safe restart coordinator (#76923)
Add a safe restart coordinator that preflights active Gateway work before restart.

- expose gateway.restart.preflight and gateway.restart.request RPC methods
- add explicit openclaw gateway restart --safe / openclaw daemon restart --safe path
- narrow restart blockers to running non-ended tasks so queued records no longer block indefinitely
- keep existing restart behavior unchanged; --force remains the immediate override

Co-authored-by: NikolaFC <54186359+NikolaFC@users.noreply.github.com>
Co-authored-by: galiniliev <5711535+galiniliev@users.noreply.github.com>
2026-05-04 10:58:36 -07:00
Pavan Kumar Gondhi
0e702f1063 fix(gateway): clamp unbound websocket auth scopes [AI] (#77413)
* fix: clamp unapproved trusted proxy websocket scopes

* addressing claude review

* addressing claude review

* addressing ci

* addressing ci

* docs: add changelog entry for PR merge
2026-05-04 23:16:07 +05:30
Mert Başar
c240e718e9 Feat/main session durable delivery pr (#75280)
* feat: generalize pending-final-delivery for subagents and main session

(cherry picked from commit 677fcbfaf87c8cd6de8b5bd02099b29b7d49e916)

* feat(agents): implement Phase 2 durable final delivery for main sessions

(cherry picked from commit b4e39f0ddf6dbd3f0d3b9226df8e714ad722f751)

* fix(agents): narrow heartbeat deferral to pending final delivery

* fix(agents): clear final delivery after dispatch

* fix(agents): gate durable delivery retry capture

---------

Co-authored-by: Mert Basar <MertBasar0@users.noreply.github.com>
2026-05-05 01:44:11 +08:00
Michael Appel
7b8315d18e fix: block SystemRoot/WINDIR in workspace .env and harden reg.exe path resolution [AI-assisted] (#74454)
* fix: address issue

* fix: address PR review feedback

* Add changelog entry for PR #74454

---------

Co-authored-by: Devin Robison <drobison@nvidia.com>
2026-05-04 11:39:00 -06:00
842 changed files with 36855 additions and 6257 deletions

View File

@@ -266,6 +266,52 @@ It should include `broker.url`, `broker.token`, and usually `provider: aws`
for owned-cloud lanes. Do not let that config override the OpenClaw default
when Blacksmith proof is requested; pass `--provider blacksmith-testbox`.
### OpenClaw Control UI WebVNC
When Peter asks to show the OpenClaw app UI in a Crabbox desktop/WebVNC session,
keep the OpenClaw setup as agent-local ceremony and delegate the generic desktop
bridge to Crabbox:
```sh
lease=<lease-slug-or-id>
# If no lease exists yet:
../crabbox/bin/crabbox warmup --provider aws --target linux --desktop --browser \
--class beast --market on-demand --idle-timeout 90m --ttl 240m --timing-json
../crabbox/bin/crabbox run --provider aws --target linux --id "$lease" \
--desktop --browser --keep --idle-timeout 90m --ttl 240m --timing-json \
--shell -- 'set -euxo pipefail
if ! command -v node >/dev/null || ! node -e "process.exit(Number(process.versions.node.split(\".\")[0]) >= 22 ? 0 : 1)"; then
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt-get install -y nodejs
fi
sudo apt-get update
sudo apt-get install -y build-essential python3
sudo corepack enable
corepack prepare pnpm@10.33.2 --activate
pnpm install --frozen-lockfile
pnpm --dir ui build
if [ -f /tmp/openclaw-ui.pid ] && kill -0 "$(cat /tmp/openclaw-ui.pid)" 2>/dev/null; then
kill "$(cat /tmp/openclaw-ui.pid)" || true
fi
nohup pnpm --dir ui dev --host 0.0.0.0 --port 3001 > /tmp/openclaw-ui.log 2>&1 &
echo $! > /tmp/openclaw-ui.pid
for _ in $(seq 1 90); do
curl -fsS http://127.0.0.1:3001/ >/tmp/openclaw-ui.html && exit 0
sleep 1
done
tail -80 /tmp/openclaw-ui.log >&2 || true
exit 1'
../crabbox/bin/crabbox desktop launch --provider aws --target linux --id "$lease" \
--browser --url http://127.0.0.1:3001/ --webvnc --open
```
Do not add an OpenClaw-specific helper under repo `scripts/` for this. If the
demo needs a connected app, start a throwaway gateway inside the Crabbox lease;
do not touch Peter's Mac Studio gateway unless he explicitly asks.
## Diagnostics
```sh

View File

@@ -24,6 +24,60 @@ gitcrawl search openclaw/openclaw --query "<scope or title keywords>" --mode hyb
gitcrawl cluster-detail openclaw/openclaw --id <cluster-id> --member-limit 20 --body-chars 280 --json
```
## Surface opener identity
- For every reviewed, triaged, closed, or landed issue/PR, show the opener's human name when available, GitHub login, and account age.
- Get the login from `gh issue view` / `gh pr view` (`author.login`), then fetch profile metadata once with `gh api users/<login> --jq '{login,name,created_at,type}'`.
- Report account age as created date plus rough age, for example `Opened by Jane Doe (@jane, account created 2021-04-03, ~5y old)`.
- Also show recent GitHub activity when it informs maintainer risk: OpenClaw PRs, issues, and commits in the last 12 months; for linked issue-fixing PRs, include both the PR author and issue opener when they differ.
- Prefer the bundled helper for activity lookups:
```bash
.agents/skills/openclaw-pr-maintainer/scripts/github-activity.sh <login> [other-login...]
.agents/skills/openclaw-pr-maintainer/scripts/github-activity.sh --global <login>
```
- The helper reports repo-local activity first and can fetch public GitHub contribution totals for the same window with `--global`.
- The helper is intentionally cache-friendly for gitcrawl-backed `gh`: it rounds repo-local windows to the UTC day, rounds global contribution windows to the UTC hour, and counts PRs/issues from one paginated issues response before fetching commits separately. Prefer reusing the helper instead of hand-rolling several `gh api` loops.
- Report activity compactly, for example `OpenClaw last 12mo: 4 PRs, 2 issues, 11 commits; GitHub public last 12mo: 86 commits, 9 PRs, 3 issues, 12 reviews`.
- If `name` is empty, use the login only. If profile lookup is rate-limited or unavailable, say `account age unknown` rather than omitting the opener.
- Use identity and activity as triage signal, not proof by itself: new, low-activity, or bot-like accounts can raise review caution, but code, repro, and CI evidence still decide.
## Suppress top-maintainer items in issue triage
When Peter asks for issue triage, hot issues, pressing bugs, Discord-correlated issues, or "what is still open", do not surface issues or PRs authored by top maintainers by default. He wants external/user-reported hot issues and external PRs, not maintainer-owned work queues.
Suppress by default when the opener/author is one of:
- `@vincentkoc`
- `@Takhoffman`
- `@gumadeiras`
- `@obviyus`
- `@shakkernerd`
- `@mbelinky`
- `@joshavant`
- `@ngutman`
- `@vignesh07`
- `@huntharo`
Also suppress lower-priority maintainer-owned noise from the broader keep/top-maintainer group unless it is directly relevant:
- `@thewilloftheshadow`
- `@onutc` / `@osolmaz`
- `@jacobtomlinson`
- `@tyler6204`
- `@velvet-shark`
- `@jalehman`
- `@frankekn`
- `@ImLukeF`
- `@mcaxtr`
Exceptions:
- Show maintainer-authored items when Peter explicitly asks for maintainer PRs/issues, PR landing candidates, release-blocking maintainer work, or a specific PR/issue number.
- Show a maintainer-authored item when it is the canonical fix for an external hot issue, but frame it as the fix path rather than as a user-facing issue candidate.
- Do not close, label, or deprioritize solely because an item is maintainer-authored; this section only controls what appears in triage shortlists.
## Apply close and triage labels correctly
- If an issue or PR matches an auto-close reason, apply the label and let `.github/workflows/auto-response.yml` handle the comment/close/lock flow.

View File

@@ -0,0 +1,178 @@
#!/usr/bin/env bash
set -euo pipefail
repo="openclaw/openclaw"
months="12"
include_global="0"
usage() {
printf 'Usage: %s [--repo owner/repo] [--months N] [--global] <github-login> [login...]\n' "$0"
}
die() {
printf 'error: %s\n' "$*" >&2
exit 1
}
need() {
command -v "$1" >/dev/null 2>&1 || die "missing required command: $1"
}
date_utc_relative_months() {
local count="$1"
if date -u -v-"${count}"m +%Y-%m-%dT00:00:00Z >/dev/null 2>&1; then
date -u -v-"${count}"m +%Y-%m-%dT00:00:00Z
return
fi
date -u -d "${count} months ago" +%Y-%m-%dT00:00:00Z
}
date_to_epoch() {
local value="$1"
if date -u -j -f '%Y-%m-%dT%H:%M:%SZ' "$value" +%s >/dev/null 2>&1; then
date -u -j -f '%Y-%m-%dT%H:%M:%SZ' "$value" +%s
return
fi
date -u -d "$value" +%s
}
rough_age() {
local created_at="$1"
local now_s created_s days
now_s=$(date -u +%s)
created_s=$(date_to_epoch "$created_at")
days=$(( (now_s - created_s) / 86400 ))
if (( days < 120 )); then
printf '~%dd old' "$days"
return
fi
awk -v days="$days" 'BEGIN { printf "~%.1fy old", days / 365.2425 }'
}
thread_kinds() {
local login="$1"
local since_ts="$2"
gh api --paginate "repos/${repo}/issues?state=all&creator=${login}&since=${since_ts}&per_page=100" \
--jq ".[] | select(.created_at >= \"${since_ts}\") | if has(\"pull_request\") then \"pr\" else \"issue\" end"
}
count_kind_lines() {
local kind="$1"
local lines="$2"
grep -cx "$kind" <<<"$lines" 2>/dev/null || true
}
count_commits() {
local login="$1"
local since_ts="$2"
gh api --paginate "repos/${repo}/commits?author=${login}&since=${since_ts}&per_page=100" \
--jq '.[].sha' | wc -l | tr -d '[:space:]'
}
global_activity() {
local login="$1"
local since_ts="$2"
local now_ts="$3"
# shellcheck disable=SC2016
gh api graphql \
-f login="$login" \
-f from="$since_ts" \
-f to="$now_ts" \
-f query='
query($login: String!, $from: DateTime!, $to: DateTime!) {
user(login: $login) {
contributionsCollection(from: $from, to: $to) {
totalCommitContributions
totalIssueContributions
totalPullRequestContributions
totalPullRequestReviewContributions
}
}
}' \
--jq '.data.user.contributionsCollection // empty'
}
while [[ $# -gt 0 ]]; do
case "$1" in
--repo)
[[ $# -ge 2 ]] || die "--repo requires owner/repo"
repo="$2"
shift 2
;;
--months)
[[ $# -ge 2 ]] || die "--months requires a positive integer"
months="$2"
[[ "$months" =~ ^[0-9]+$ && "$months" != "0" ]] || die "--months must be a positive integer"
shift 2
;;
--global)
include_global="1"
shift
;;
-h|--help)
usage
exit 0
;;
--)
shift
break
;;
-*)
die "unknown option: $1"
;;
*)
break
;;
esac
done
[[ $# -gt 0 ]] || {
usage >&2
exit 2
}
need gh
need jq
since_ts=$(date_utc_relative_months "$months")
now_ts=$(date -u +%Y-%m-%dT%H:00:00Z)
for login in "$@"; do
profile=$(gh api "users/${login}" --jq '{login,name,created_at,type}')
display_login=$(jq -r '.login' <<<"$profile")
name=$(jq -r '.name // empty' <<<"$profile")
created_at=$(jq -r '.created_at' <<<"$profile")
type=$(jq -r '.type' <<<"$profile")
created_day=${created_at%%T*}
kinds=$(thread_kinds "$display_login" "$since_ts")
prs=$(count_kind_lines pr "$kinds")
issues=$(count_kind_lines issue "$kinds")
commits=$(count_commits "$display_login" "$since_ts")
if [[ -n "$name" ]]; then
printf '%s (@%s, %s, account created %s, %s)\n' \
"$name" "$display_login" "$type" "$created_day" "$(rough_age "$created_at")"
else
printf '@%s (%s, account created %s, %s)\n' \
"$display_login" "$type" "$created_day" "$(rough_age "$created_at")"
fi
printf '%s last %smo: %s PRs, %s issues, %s commits\n' "$repo" "$months" "$prs" "$issues" "$commits"
if [[ "$include_global" == "1" ]]; then
if global_json=$(global_activity "$display_login" "$since_ts" "$now_ts" 2>/dev/null); then
if [[ -n "$global_json" ]]; then
global_commits=$(jq -r '.totalCommitContributions' <<<"$global_json")
global_issues=$(jq -r '.totalIssueContributions' <<<"$global_json")
global_prs=$(jq -r '.totalPullRequestContributions' <<<"$global_json")
global_reviews=$(jq -r '.totalPullRequestReviewContributions' <<<"$global_json")
printf 'GitHub public last %smo: %s commits, %s PRs, %s issues, %s reviews\n' \
"$months" "$global_commits" "$global_prs" "$global_issues" "$global_reviews"
else
printf 'GitHub public last %smo: unavailable\n' "$months"
fi
else
printf 'GitHub public last %smo: unavailable\n' "$months"
fi
fi
done

View File

@@ -35,6 +35,18 @@ If this PR fixes a plugin beta-release blocker, title it `fix(<plugin-id>): beta
- Related #
- [ ] This PR fixes a bug or regression
## Real behavior proof (required for external PRs)
External contributors must show after-fix evidence from a real OpenClaw setup. Unit tests, mocks, lint, typechecks, snapshots, and CI are supplemental only. Screenshots are encouraged even for CLI, console, text, or log changes; terminal screenshots and copied live output count.
- Behavior or issue addressed:
- Real environment tested:
- Exact steps or command run after this patch:
- Evidence after fix (screenshot, recording, terminal capture, console output, redacted runtime log, linked artifact, or copied live output):
- Observed result after fix:
- What was not tested:
- Before evidence (optional but encouraged):
## Root Cause (if applicable)
For bug fixes or regressions, explain why this happened, not just what changed. Otherwise write `N/A`. If the cause is unclear, write `Unknown`.

View File

@@ -6,7 +6,7 @@ on:
issue_comment:
types: [created]
pull_request_target: # zizmor: ignore[dangerous-triggers] maintainer-owned label automation; trusted base checkout only, no untrusted PR code execution
types: [opened, edited, synchronize, reopened, labeled]
types: [opened, edited, synchronize, reopened, labeled, unlabeled]
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"

View File

@@ -35,6 +35,11 @@ on:
- minimum
- stable
- full
run_release_soak:
description: Run exhaustive live/Docker and upgrade-survivor soak lanes; forced on for release_profile=full
required: false
default: false
type: boolean
rerun_group:
description: Validation group to run
required: false
@@ -58,6 +63,11 @@ on:
required: false
default: ""
type: string
cross_os_suite_filter:
description: Optional focused cross-OS suite filter, e.g. windows/packaged-upgrade or packaged-fresh
required: false
default: ""
type: string
npm_telegram_package_spec:
description: Optional published package spec for the package Telegram E2E lane
required: false
@@ -136,8 +146,10 @@ jobs:
EVIDENCE_PACKAGE_SPEC: ${{ inputs.evidence_package_spec }}
PACKAGE_ACCEPTANCE_PACKAGE_SPEC: ${{ inputs.package_acceptance_package_spec }}
RELEASE_PROFILE: ${{ inputs.release_profile }}
RUN_RELEASE_SOAK: ${{ inputs.run_release_soak || inputs.release_profile == 'full' }}
RERUN_GROUP: ${{ inputs.rerun_group }}
LIVE_SUITE_FILTER: ${{ inputs.live_suite_filter }}
CROSS_OS_SUITE_FILTER: ${{ inputs.cross_os_suite_filter }}
run: |
{
echo "## Full release validation"
@@ -145,10 +157,14 @@ jobs:
echo "- Target ref: \`${TARGET_REF}\`"
echo "- Target SHA: \`${TARGET_SHA}\`"
echo "- Child workflow ref: \`${CHILD_WORKFLOW_REF}\`"
echo "- Release soak lanes: \`${RUN_RELEASE_SOAK}\`"
echo "- Rerun group: \`${RERUN_GROUP}\`"
if [[ -n "${LIVE_SUITE_FILTER// }" ]]; then
echo "- Live suite filter: \`${LIVE_SUITE_FILTER}\`"
fi
if [[ -n "${CROSS_OS_SUITE_FILTER// }" ]]; then
echo "- Cross-OS suite filter: \`${CROSS_OS_SUITE_FILTER}\`"
fi
if [[ "$RERUN_GROUP" == "all" || "$RERUN_GROUP" == "ci" ]]; then
echo "- Normal CI: \`CI\` with \`target_ref=${TARGET_SHA}\`"
else
@@ -206,7 +222,7 @@ jobs:
local workflow="$1"
shift
local before_json dispatch_output run_id status conclusion url
local before_json dispatch_output run_id status conclusion url poll_count
before_json="$(gh run list --workflow "$workflow" --event workflow_dispatch --limit 100 --json databaseId --jq '[.[].databaseId]')"
dispatch_output="$(gh workflow run "$workflow" --ref "$CHILD_WORKFLOW_REF" "$@" 2>&1)"
@@ -246,11 +262,17 @@ jobs:
}
trap cancel_child EXIT INT TERM
poll_count=0
while true; do
status="$(gh run view "$run_id" --json status --jq '.status')"
if [[ "$status" == "completed" ]]; then
break
fi
poll_count=$((poll_count + 1))
if (( poll_count % 10 == 0 )); then
echo "Still waiting on ${workflow}: https://github.com/${GITHUB_REPOSITORY}/actions/runs/${run_id}"
gh run view "$run_id" --json jobs --jq '.jobs[] | select(.status != "completed") | {name, status, url}' || true
fi
sleep 30
done
trap - EXIT INT TERM
@@ -299,7 +321,7 @@ jobs:
local workflow="$1"
shift
local before_json dispatch_output run_id status conclusion url
local before_json dispatch_output run_id status conclusion url poll_count
before_json="$(gh run list --workflow "$workflow" --event workflow_dispatch --limit 100 --json databaseId --jq '[.[].databaseId]')"
dispatch_output="$(gh workflow run "$workflow" --ref "$CHILD_WORKFLOW_REF" "$@" 2>&1)"
@@ -339,11 +361,17 @@ jobs:
}
trap cancel_child EXIT INT TERM
poll_count=0
while true; do
status="$(gh run view "$run_id" --json status --jq '.status')"
if [[ "$status" == "completed" ]]; then
break
fi
poll_count=$((poll_count + 1))
if (( poll_count % 10 == 0 )); then
echo "Still waiting on ${workflow}: https://github.com/${GITHUB_REPOSITORY}/actions/runs/${run_id}"
gh run view "$run_id" --json jobs --jq '.jobs[] | select(.status != "completed") | {name, status, url}' || true
fi
sleep 30
done
trap - EXIT INT TERM
@@ -388,8 +416,10 @@ jobs:
PROVIDER: ${{ inputs.provider }}
MODE: ${{ inputs.mode }}
RELEASE_PROFILE: ${{ inputs.release_profile }}
RUN_RELEASE_SOAK: ${{ inputs.run_release_soak || inputs.release_profile == 'full' }}
RERUN_GROUP: ${{ inputs.rerun_group }}
LIVE_SUITE_FILTER: ${{ inputs.live_suite_filter }}
CROSS_OS_SUITE_FILTER: ${{ inputs.cross_os_suite_filter }}
PACKAGE_ACCEPTANCE_PACKAGE_SPEC: ${{ inputs.package_acceptance_package_spec }}
run: |
set -euo pipefail
@@ -398,7 +428,7 @@ jobs:
local workflow="$1"
shift
local before_json dispatch_output run_id status conclusion url
local before_json dispatch_output run_id status conclusion url poll_count
before_json="$(gh run list --workflow "$workflow" --event workflow_dispatch --limit 100 --json databaseId --jq '[.[].databaseId]')"
dispatch_output="$(gh workflow run "$workflow" --ref "$CHILD_WORKFLOW_REF" "$@" 2>&1)"
@@ -438,11 +468,17 @@ jobs:
}
trap cancel_child EXIT INT TERM
poll_count=0
while true; do
status="$(gh run view "$run_id" --json status --jq '.status')"
if [[ "$status" == "completed" ]]; then
break
fi
poll_count=$((poll_count + 1))
if (( poll_count % 10 == 0 )); then
echo "Still waiting on ${workflow}: https://github.com/${GITHUB_REPOSITORY}/actions/runs/${run_id}"
gh run view "$run_id" --json jobs --jq '.jobs[] | select(.status != "completed") | {name, status, url}' || true
fi
sleep 30
done
trap - EXIT INT TERM
@@ -465,10 +501,14 @@ jobs:
echo "- Provider: \`${PROVIDER}\`"
echo "- Cross-OS mode: \`${MODE}\`"
echo "- Release profile: \`${RELEASE_PROFILE}\`"
echo "- Release soak lanes: \`${RUN_RELEASE_SOAK}\`"
echo "- Rerun group: \`${RERUN_GROUP}\`"
if [[ -n "${LIVE_SUITE_FILTER// }" ]]; then
echo "- Live suite filter: \`${LIVE_SUITE_FILTER}\`"
fi
if [[ -n "${CROSS_OS_SUITE_FILTER// }" ]]; then
echo "- Cross-OS suite filter: \`${CROSS_OS_SUITE_FILTER}\`"
fi
if [[ -n "${PACKAGE_ACCEPTANCE_PACKAGE_SPEC// }" ]]; then
echo "- Package Acceptance package spec: \`${PACKAGE_ACCEPTANCE_PACKAGE_SPEC}\`"
fi
@@ -485,11 +525,15 @@ jobs:
-f provider="$PROVIDER"
-f mode="$MODE"
-f release_profile="$RELEASE_PROFILE"
-f run_release_soak="$RUN_RELEASE_SOAK"
-f rerun_group="$child_rerun_group"
)
if [[ -n "${LIVE_SUITE_FILTER// }" ]]; then
args+=(-f live_suite_filter="$LIVE_SUITE_FILTER")
fi
if [[ -n "${CROSS_OS_SUITE_FILTER// }" ]]; then
args+=(-f cross_os_suite_filter="$CROSS_OS_SUITE_FILTER")
fi
if [[ -n "${PACKAGE_ACCEPTANCE_PACKAGE_SPEC// }" ]]; then
args+=(-f package_acceptance_package_spec="$PACKAGE_ACCEPTANCE_PACKAGE_SPEC")
fi
@@ -640,11 +684,17 @@ jobs:
}
trap cancel_child EXIT INT TERM
poll_count=0
while true; do
status="$(gh run view "$run_id" --json status --jq '.status')"
if [[ "$status" == "completed" ]]; then
break
fi
poll_count=$((poll_count + 1))
if (( poll_count % 10 == 0 )); then
echo "Still waiting on npm-telegram-beta-e2e.yml: https://github.com/${GITHUB_REPOSITORY}/actions/runs/${run_id}"
gh run view "$run_id" --json jobs --jq '.jobs[] | select(.status != "completed") | {name, status, url}' || true
fi
sleep 30
done
trap - EXIT INT TERM

View File

@@ -401,11 +401,38 @@ jobs:
)
pnpm "${args[@]}"
cp "$desktop_dir/desktop-browser-smoke.png" "$root/$lane/discord-status-reactions-tool-only-desktop.png"
cp "$desktop_dir/desktop-browser-smoke.mp4" "$root/$lane/discord-status-reactions-tool-only-desktop.mp4"
}
capture_desktop_lane baseline
capture_desktop_lane candidate
make_desktop_preview() {
local lane="$1"
local input="$root/$lane/discord-status-reactions-tool-only-desktop.mp4"
local output="$root/$lane/discord-status-reactions-tool-only-desktop-preview.gif"
local clip="$root/$lane/discord-status-reactions-tool-only-desktop-change.mp4"
local metadata="$root/$lane/discord-status-reactions-tool-only-desktop-preview.json"
crabbox media preview \
--input "$input" \
--output "$output" \
--trimmed-video-output "$clip" \
--json > "$metadata"
}
if ! command -v ffmpeg >/dev/null 2>&1 || ! command -v ffprobe >/dev/null 2>&1; then
sudo apt-get update && sudo apt-get install -y ffmpeg || true
fi
if ! make_desktop_preview baseline || ! make_desktop_preview candidate; then
rm -f "$root/baseline/discord-status-reactions-tool-only-desktop-preview.gif"
rm -f "$root/candidate/discord-status-reactions-tool-only-desktop-preview.gif"
rm -f "$root/baseline/discord-status-reactions-tool-only-desktop-change.mp4"
rm -f "$root/candidate/discord-status-reactions-tool-only-desktop-change.mp4"
rm -f "$root/baseline/discord-status-reactions-tool-only-desktop-preview.json"
rm -f "$root/candidate/discord-status-reactions-tool-only-desktop-preview.json"
echo "::warning::Could not generate motion-trimmed desktop previews; continuing with screenshots and full MP4 links."
fi
baseline_status="$(jq -r '.scenarios[0].status' "$root/baseline/discord-qa-summary.json")"
candidate_status="$(jq -r '.scenarios[0].status' "$root/candidate/discord-qa-summary.json")"
@@ -431,6 +458,20 @@ jobs:
echo "- Candidate screenshot: \`candidate/discord-status-reactions-tool-only-timeline.png\`"
echo "- Baseline desktop screenshot: \`baseline/discord-status-reactions-tool-only-desktop.png\`"
echo "- Candidate desktop screenshot: \`candidate/discord-status-reactions-tool-only-desktop.png\`"
if [[ -f "$root/baseline/discord-status-reactions-tool-only-desktop-preview.gif" ]]; then
echo "- Baseline desktop preview: \`baseline/discord-status-reactions-tool-only-desktop-preview.gif\`"
fi
if [[ -f "$root/candidate/discord-status-reactions-tool-only-desktop-preview.gif" ]]; then
echo "- Candidate desktop preview: \`candidate/discord-status-reactions-tool-only-desktop-preview.gif\`"
fi
if [[ -f "$root/baseline/discord-status-reactions-tool-only-desktop-change.mp4" ]]; then
echo "- Baseline desktop change clip: \`baseline/discord-status-reactions-tool-only-desktop-change.mp4\`"
fi
if [[ -f "$root/candidate/discord-status-reactions-tool-only-desktop-change.mp4" ]]; then
echo "- Candidate desktop change clip: \`candidate/discord-status-reactions-tool-only-desktop-change.mp4\`"
fi
echo "- Baseline desktop video: \`baseline/discord-status-reactions-tool-only-desktop.mp4\`"
echo "- Candidate desktop video: \`candidate/discord-status-reactions-tool-only-desktop.mp4\`"
} > "$root/mantis-report.md"
cat "$root/mantis-report.md" >> "$GITHUB_STEP_SUMMARY"
@@ -467,7 +508,7 @@ jobs:
permission-issues: write
permission-pull-requests: write
- name: Comment PR with inline QA screenshots
- name: Comment PR with inline QA evidence
if: ${{ always() && needs.resolve_request.outputs.pr_number != '' && steps.run_mantis.outputs.output_dir != '' }}
env:
GH_TOKEN: ${{ steps.mantis_app_token.outputs.token }}
@@ -491,7 +532,9 @@ jobs:
"$root/baseline/discord-status-reactions-tool-only-timeline.png" \
"$root/candidate/discord-status-reactions-tool-only-timeline.png" \
"$root/baseline/discord-status-reactions-tool-only-desktop.png" \
"$root/candidate/discord-status-reactions-tool-only-desktop.png"
"$root/candidate/discord-status-reactions-tool-only-desktop.png" \
"$root/baseline/discord-status-reactions-tool-only-desktop.mp4" \
"$root/candidate/discord-status-reactions-tool-only-desktop.mp4"
do
if [[ ! -f "$required" ]]; then
echo "Missing required QA evidence file: $required" >&2
@@ -519,14 +562,30 @@ jobs:
cp "$root/candidate/discord-status-reactions-tool-only-timeline.png" "$artifacts_worktree/$artifact_root/candidate.png"
cp "$root/baseline/discord-status-reactions-tool-only-desktop.png" "$artifacts_worktree/$artifact_root/baseline-desktop.png"
cp "$root/candidate/discord-status-reactions-tool-only-desktop.png" "$artifacts_worktree/$artifact_root/candidate-desktop.png"
has_desktop_previews="false"
if [[ -f "$root/baseline/discord-status-reactions-tool-only-desktop-preview.gif" && -f "$root/candidate/discord-status-reactions-tool-only-desktop-preview.gif" ]]; then
cp "$root/baseline/discord-status-reactions-tool-only-desktop-preview.gif" "$artifacts_worktree/$artifact_root/baseline-desktop-preview.gif"
cp "$root/candidate/discord-status-reactions-tool-only-desktop-preview.gif" "$artifacts_worktree/$artifact_root/candidate-desktop-preview.gif"
cp "$root/baseline/discord-status-reactions-tool-only-desktop-preview.json" "$artifacts_worktree/$artifact_root/baseline-desktop-preview.json"
cp "$root/candidate/discord-status-reactions-tool-only-desktop-preview.json" "$artifacts_worktree/$artifact_root/candidate-desktop-preview.json"
has_desktop_previews="true"
fi
has_change_clips="false"
if [[ -f "$root/baseline/discord-status-reactions-tool-only-desktop-change.mp4" && -f "$root/candidate/discord-status-reactions-tool-only-desktop-change.mp4" ]]; then
cp "$root/baseline/discord-status-reactions-tool-only-desktop-change.mp4" "$artifacts_worktree/$artifact_root/baseline-desktop-change.mp4"
cp "$root/candidate/discord-status-reactions-tool-only-desktop-change.mp4" "$artifacts_worktree/$artifact_root/candidate-desktop-change.mp4"
has_change_clips="true"
fi
cp "$root/baseline/discord-status-reactions-tool-only-desktop.mp4" "$artifacts_worktree/$artifact_root/baseline-desktop.mp4"
cp "$root/candidate/discord-status-reactions-tool-only-desktop.mp4" "$artifacts_worktree/$artifact_root/candidate-desktop.mp4"
cp "$root/comparison.json" "$artifacts_worktree/$artifact_root/comparison.json"
cp "$root/mantis-report.md" "$artifacts_worktree/$artifact_root/mantis-report.md"
git -C "$artifacts_worktree" add "$artifact_root"
if git -C "$artifacts_worktree" diff --cached --quiet; then
echo "No QA screenshot artifact changes to publish."
echo "No QA screenshot/video artifact changes to publish."
else
git -C "$artifacts_worktree" commit --quiet -m "qa: publish Mantis Discord screenshots for PR ${TARGET_PR}"
git -C "$artifacts_worktree" commit --quiet -m "qa: publish Mantis Discord evidence for PR ${TARGET_PR}"
git -C "$artifacts_worktree" push --quiet origin HEAD:qa-artifacts
fi
@@ -535,6 +594,26 @@ jobs:
baseline_status="$(jq -r '.baseline.status' "$root/comparison.json")"
candidate_status="$(jq -r '.candidate.status' "$root/comparison.json")"
pass="$(jq -r '.pass' "$root/comparison.json")"
preview_section=""
if [[ "$has_desktop_previews" == "true" ]]; then
preview_section="$(cat <<EOF
| Baseline motion preview | Candidate motion preview |
| --- | --- |
| <img src="${raw_base}/baseline-desktop-preview.gif" width="420" alt="Animated baseline desktop preview"> | <img src="${raw_base}/candidate-desktop-preview.gif" width="420" alt="Animated candidate desktop preview"> |
EOF
)"
fi
change_clip_section=""
if [[ "$has_change_clips" == "true" ]]; then
change_clip_section="$(cat <<EOF
Motion-trimmed clips:
- [Baseline change MP4](${raw_base}/baseline-desktop-change.mp4)
- [Candidate change MP4](${raw_base}/candidate-desktop-change.mp4)
EOF
)"
fi
comment_file="$(mktemp)"
cat > "$comment_file" <<EOF
<!-- mantis-discord-status-reactions -->
@@ -557,6 +636,12 @@ jobs:
| Baseline desktop/VNC browser | Candidate desktop/VNC browser |
| --- | --- |
| <img src="${raw_base}/baseline-desktop.png" width="420" alt="Baseline Mantis desktop browser screenshot"> | <img src="${raw_base}/candidate-desktop.png" width="420" alt="Candidate Mantis desktop browser screenshot"> |
${preview_section}
${change_clip_section}
Full videos:
- [Baseline desktop MP4](${raw_base}/baseline-desktop.mp4)
- [Candidate desktop MP4](${raw_base}/candidate-desktop.mp4)
Raw QA files: https://github.com/${GITHUB_REPOSITORY}/tree/qa-artifacts/${artifact_root}
EOF
@@ -571,13 +656,13 @@ jobs:
comment_payload="$(mktemp)"
jq -n --rawfile body "$comment_file" '{ body: $body }' > "$comment_payload"
if gh api --method PATCH "repos/${GITHUB_REPOSITORY}/issues/comments/${comment_id}" --input "$comment_payload" >/dev/null; then
echo "Updated Mantis QA screenshot comment on PR #${TARGET_PR}."
echo "Updated Mantis QA evidence comment on PR #${TARGET_PR}."
else
echo "::warning::Could not update existing Mantis QA screenshot comment ${comment_id}; creating a new one."
echo "::warning::Could not update existing Mantis QA evidence comment ${comment_id}; creating a new one."
gh pr comment "$TARGET_PR" --body-file "$comment_file"
echo "Created Mantis QA screenshot comment on PR #${TARGET_PR}."
echo "Created Mantis QA evidence comment on PR #${TARGET_PR}."
fi
else
gh pr comment "$TARGET_PR" --body-file "$comment_file"
echo "Created Mantis QA screenshot comment on PR #${TARGET_PR}."
echo "Created Mantis QA evidence comment on PR #${TARGET_PR}."
fi

View File

@@ -31,6 +31,11 @@ on:
- fresh
- upgrade
- both
suite_filter:
description: Optional focused cross-OS suite filter, e.g. windows/packaged-upgrade or packaged-fresh
required: false
default: ""
type: string
previous_version:
description: Optional baseline version for installer/dev-update and packaged upgrade
required: false
@@ -100,6 +105,11 @@ on:
description: Which release-check lanes to run
required: true
type: string
suite_filter:
description: Optional focused cross-OS suite filter, e.g. windows/packaged-upgrade or packaged-fresh
required: false
default: ""
type: string
previous_version:
description: Optional baseline version for the upgrade lane (defaults to npm latest)
required: false
@@ -482,6 +492,7 @@ jobs:
env:
INPUT_REF: ${{ inputs.ref }}
INPUT_MODE: ${{ inputs.mode }}
INPUT_SUITE_FILTER: ${{ inputs.suite_filter }}
INPUT_UBUNTU_RUNNER: ${{ inputs.ubuntu_runner }}
INPUT_WINDOWS_RUNNER: ${{ inputs.windows_runner }}
INPUT_MACOS_RUNNER: ${{ inputs.macos_runner }}
@@ -493,6 +504,7 @@ jobs:
--resolve-matrix \
--ref "${INPUT_REF}" \
--mode "${INPUT_MODE}" \
--suite-filter "${INPUT_SUITE_FILTER}" \
--ubuntu-runner "${INPUT_UBUNTU_RUNNER}" \
--windows-runner "${INPUT_WINDOWS_RUNNER}" \
--macos-runner "${INPUT_MACOS_RUNNER}")"

View File

@@ -34,7 +34,7 @@ on:
default: 1
type: number
published_upgrade_survivor_baseline:
description: Published OpenClaw package baseline for the published-upgrade-survivor/update-migration Docker lane
description: Published OpenClaw package baseline for the published-upgrade-survivor/update-migration Docker lanes
required: false
default: openclaw@latest
type: string
@@ -129,7 +129,7 @@ on:
default: 1
type: number
published_upgrade_survivor_baseline:
description: Published OpenClaw package baseline for the published-upgrade-survivor/update-migration Docker lane
description: Published OpenClaw package baseline for the published-upgrade-survivor/update-restart-auth/update-migration Docker lanes
required: false
default: openclaw@latest
type: string
@@ -489,7 +489,18 @@ jobs:
fi
- name: Verify live prompt cache floors
run: pnpm test:live:cache
run: |
set -euo pipefail
for attempt in 1 2 3; do
echo "live-cache attempt ${attempt}/3"
if pnpm test:live:cache; then
exit 0
fi
if [[ "$attempt" == "3" ]]; then
exit 1
fi
sleep $((attempt * 15))
done
validate_repo_e2e:
needs: validate_selected_ref
@@ -850,36 +861,24 @@ jobs:
runs-on: blacksmith-4vcpu-ubuntu-2404
timeout-minutes: 5
outputs:
groups_json: ${{ steps.plan.outputs.groups_json }}
groups_json: ${{ steps.groups.outputs.groups_json }}
steps:
- name: Plan targeted Docker lane groups
id: plan
- name: Checkout trusted release harness
uses: actions/checkout@v6
with:
ref: ${{ github.sha }}
fetch-depth: 1
- name: Build targeted Docker lane groups
id: groups
shell: bash
env:
LANES: ${{ inputs.docker_lanes }}
GROUP_SIZE: ${{ inputs.targeted_docker_lane_group_size }}
OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPECS: ${{ inputs.published_upgrade_survivor_baselines }}
run: |
set -euo pipefail
groups_json="$(
LANES="$LANES" GROUP_SIZE="$GROUP_SIZE" node <<'NODE'
const lanes = [...new Set(String(process.env.LANES || "").split(/[,\s]+/u).map((lane) => lane.trim()).filter(Boolean))];
if (lanes.length === 0) {
throw new Error("docker_lanes is required when planning targeted Docker lane groups.");
}
const rawGroupSize = Number.parseInt(process.env.GROUP_SIZE || "1", 10);
const groupSize = Number.isFinite(rawGroupSize) && rawGroupSize > 0 ? rawGroupSize : 1;
const sanitize = (lane) => lane.replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "targeted";
const groups = [];
for (let index = 0; index < lanes.length; index += groupSize) {
const groupLanes = lanes.slice(index, index + groupSize);
const first = sanitize(groupLanes[0]);
const last = sanitize(groupLanes[groupLanes.length - 1]);
const label = groupLanes.length === 1 ? first : `${first}--${last}`;
groups.push({ label, docker_lanes: groupLanes.join(" ") });
}
process.stdout.write(JSON.stringify(groups));
NODE
)"
groups_json="$(node scripts/plan-targeted-docker-lane-groups.mjs)"
echo "groups_json=${groups_json}" >> "$GITHUB_OUTPUT"
validate_docker_lanes:
@@ -946,7 +945,7 @@ jobs:
OPENCLAW_DOCKER_E2E_SELECTED_SHA: ${{ needs.validate_selected_ref.outputs.selected_sha }}
OPENCLAW_CURRENT_PACKAGE_TGZ: .artifacts/docker-e2e-package/openclaw-current.tgz
OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPEC: ${{ inputs.published_upgrade_survivor_baseline }}
OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPECS: ${{ inputs.published_upgrade_survivor_baselines }}
OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPECS: ${{ matrix.group.published_upgrade_survivor_baselines || inputs.published_upgrade_survivor_baselines }}
OPENCLAW_UPGRADE_SURVIVOR_SCENARIOS: ${{ inputs.published_upgrade_survivor_scenarios }}
OPENCLAW_SKIP_DOCKER_BUILD: "1"
INCLUDE_OPENWEBUI: ${{ inputs.include_openwebui }}
@@ -987,6 +986,7 @@ jobs:
shell: bash
env:
LANES: ${{ matrix.group.docker_lanes }}
GROUP_LABEL: ${{ matrix.group.label }}
INCLUDE_OPENWEBUI: ${{ inputs.include_openwebui }}
INCLUDE_RELEASE_PATH_SUITES: ${{ inputs.include_release_path_suites }}
run: |
@@ -1006,7 +1006,7 @@ jobs:
plan_path=".artifacts/docker-tests/targeted-plan.json"
node .release-harness/scripts/test-docker-all.mjs --plan-json > "$plan_path"
node .release-harness/scripts/docker-e2e.mjs github-outputs "$plan_path" >> "$GITHUB_OUTPUT"
suffix="$(printf '%s' "$LANES" | tr ',[:space:]' '-' | tr -cd 'A-Za-z0-9._-' | sed -E 's/-+/-/g; s/^-//; s/-$//')"
suffix="$(printf '%s' "${GROUP_LABEL:-$LANES}" | tr ',[:space:]' '-' | tr -cd 'A-Za-z0-9._-' | sed -E 's/-+/-/g; s/^-//; s/-$//')"
echo "artifact_suffix=${suffix:-targeted}" >> "$GITHUB_OUTPUT"
echo "plan_json=$plan_path" >> "$GITHUB_OUTPUT"

View File

@@ -39,6 +39,11 @@ on:
- minimum
- stable
- full
run_release_soak:
description: Run exhaustive live/Docker and upgrade-survivor soak lanes; forced on for release_profile=full
required: false
default: false
type: boolean
rerun_group:
description: Release check group to run
required: false
@@ -58,6 +63,11 @@ on:
required: false
default: ""
type: string
cross_os_suite_filter:
description: Optional focused cross-OS suite filter, e.g. windows/packaged-upgrade or packaged-fresh
required: false
default: ""
type: string
package_acceptance_package_spec:
description: Optional published package spec for Package Acceptance; blank uses the prepared release artifact
required: false
@@ -86,8 +96,10 @@ jobs:
provider: ${{ steps.inputs.outputs.provider }}
mode: ${{ steps.inputs.outputs.mode }}
release_profile: ${{ steps.inputs.outputs.release_profile }}
run_release_soak: ${{ steps.inputs.outputs.run_release_soak }}
rerun_group: ${{ steps.inputs.outputs.rerun_group }}
live_suite_filter: ${{ steps.inputs.outputs.live_suite_filter }}
cross_os_suite_filter: ${{ steps.inputs.outputs.cross_os_suite_filter }}
qa_live_matrix_enabled: ${{ steps.inputs.outputs.qa_live_matrix_enabled }}
qa_live_telegram_enabled: ${{ steps.inputs.outputs.qa_live_telegram_enabled }}
qa_live_slack_enabled: ${{ steps.inputs.outputs.qa_live_slack_enabled }}
@@ -206,14 +218,32 @@ jobs:
RELEASE_PROVIDER_INPUT: ${{ inputs.provider }}
RELEASE_MODE_INPUT: ${{ inputs.mode }}
RELEASE_PROFILE_INPUT: ${{ inputs.release_profile }}
RELEASE_RUN_RELEASE_SOAK_INPUT: ${{ inputs.run_release_soak }}
RELEASE_RERUN_GROUP_INPUT: ${{ inputs.rerun_group }}
RELEASE_LIVE_SUITE_FILTER_INPUT: ${{ inputs.live_suite_filter }}
RELEASE_CROSS_OS_SUITE_FILTER_INPUT: ${{ inputs.cross_os_suite_filter }}
RELEASE_QA_SLACK_LIVE_CI_ENABLED: ${{ vars.OPENCLAW_QA_SLACK_LIVE_CI_ENABLED || 'false' }}
RELEASE_PACKAGE_ACCEPTANCE_PACKAGE_SPEC_INPUT: ${{ inputs.package_acceptance_package_spec }}
run: |
set -euo pipefail
qa_live_matrix_enabled=true
qa_live_telegram_enabled=true
qa_live_slack_enabled=false
qa_live_slack_ci_enabled="$(printf '%s' "$RELEASE_QA_SLACK_LIVE_CI_ENABLED" | tr '[:upper:]' '[:lower:]')"
if [[ "$qa_live_slack_ci_enabled" != "true" && "$qa_live_slack_ci_enabled" != "1" && "$qa_live_slack_ci_enabled" != "yes" ]]; then
qa_live_slack_ci_enabled=false
else
qa_live_slack_ci_enabled=true
fi
run_release_soak="$(printf '%s' "$RELEASE_RUN_RELEASE_SOAK_INPUT" | tr '[:upper:]' '[:lower:]')"
if [[ "$run_release_soak" != "true" && "$run_release_soak" != "1" && "$run_release_soak" != "yes" ]]; then
run_release_soak=false
else
run_release_soak=true
fi
if [[ "$RELEASE_PROFILE_INPUT" == "full" ]]; then
run_release_soak=true
fi
filter="$(printf '%s' "$RELEASE_LIVE_SUITE_FILTER_INPUT" | tr '[:upper:]' '[:lower:]')"
if [[ -n "${filter// }" ]]; then
@@ -249,7 +279,7 @@ jobs:
;;
qa-live-slack|qa-slack|slack)
qa_filter_seen=true
echo "Slack live QA is disabled; ignoring ${token}." >&2
slack_selected="$qa_live_slack_ci_enabled"
;;
esac
done
@@ -266,8 +296,10 @@ jobs:
printf 'provider=%s\n' "$RELEASE_PROVIDER_INPUT"
printf 'mode=%s\n' "$RELEASE_MODE_INPUT"
printf 'release_profile=%s\n' "$RELEASE_PROFILE_INPUT"
printf 'run_release_soak=%s\n' "$run_release_soak"
printf 'rerun_group=%s\n' "$RELEASE_RERUN_GROUP_INPUT"
printf 'live_suite_filter=%s\n' "$RELEASE_LIVE_SUITE_FILTER_INPUT"
printf 'cross_os_suite_filter=%s\n' "$RELEASE_CROSS_OS_SUITE_FILTER_INPUT"
printf 'qa_live_matrix_enabled=%s\n' "$qa_live_matrix_enabled"
printf 'qa_live_telegram_enabled=%s\n' "$qa_live_telegram_enabled"
printf 'qa_live_slack_enabled=%s\n' "$qa_live_slack_enabled"
@@ -282,8 +314,10 @@ jobs:
RELEASE_PROVIDER: ${{ inputs.provider }}
RELEASE_MODE: ${{ inputs.mode }}
RELEASE_PROFILE: ${{ inputs.release_profile }}
RUN_RELEASE_SOAK: ${{ steps.inputs.outputs.run_release_soak }}
RELEASE_RERUN_GROUP: ${{ inputs.rerun_group }}
RELEASE_LIVE_SUITE_FILTER: ${{ inputs.live_suite_filter }}
RELEASE_CROSS_OS_SUITE_FILTER: ${{ inputs.cross_os_suite_filter }}
PACKAGE_ACCEPTANCE_PACKAGE_SPEC: ${{ inputs.package_acceptance_package_spec }}
run: |
{
@@ -295,17 +329,25 @@ jobs:
echo "- Cross-OS provider: \`${RELEASE_PROVIDER}\`"
echo "- Cross-OS mode: \`${RELEASE_MODE}\`"
echo "- Release profile: \`${RELEASE_PROFILE}\`"
echo "- Release soak lanes: \`${RUN_RELEASE_SOAK}\`"
echo "- Rerun group: \`${RELEASE_RERUN_GROUP}\`"
if [[ -n "${RELEASE_LIVE_SUITE_FILTER// }" ]]; then
echo "- Live suite filter: \`${RELEASE_LIVE_SUITE_FILTER}\`"
fi
echo "- QA live lanes: Matrix \`${{ steps.inputs.outputs.qa_live_matrix_enabled }}\`, Telegram \`${{ steps.inputs.outputs.qa_live_telegram_enabled }}\`, Slack \`${{ steps.inputs.outputs.qa_live_slack_enabled }}\` (disabled)"
if [[ -n "${RELEASE_CROSS_OS_SUITE_FILTER// }" ]]; then
echo "- Cross-OS suite filter: \`${RELEASE_CROSS_OS_SUITE_FILTER}\`"
fi
echo "- QA live lanes: Matrix \`${{ steps.inputs.outputs.qa_live_matrix_enabled }}\`, Telegram \`${{ steps.inputs.outputs.qa_live_telegram_enabled }}\`, Slack \`${{ steps.inputs.outputs.qa_live_slack_enabled }}\`"
if [[ -n "${PACKAGE_ACCEPTANCE_PACKAGE_SPEC// }" ]]; then
echo "- Package Acceptance package spec: \`${PACKAGE_ACCEPTANCE_PACKAGE_SPEC}\`"
else
echo "- Package Acceptance package spec: prepared release artifact"
fi
echo "- This run will execute cross-OS release validation, install smoke, QA Lab parity, Matrix, Telegram, and Slack lanes, and the non-Parallels Docker/live/openwebui coverage from the CI migration plan."
if [[ "$RUN_RELEASE_SOAK" == "true" ]]; then
echo "- This run will execute blocking release validation plus exhaustive live/Docker soak coverage."
else
echo "- This run will execute blocking release validation. Exhaustive live/Docker soak lanes are skipped unless \`run_release_soak=true\`, \`release_profile=full\`, or \`rerun_group=live-e2e\` is selected."
fi
} >> "$GITHUB_STEP_SUMMARY"
prepare_release_package:
@@ -400,6 +442,7 @@ jobs:
ref: ${{ needs.resolve_target.outputs.revision }}
provider: ${{ needs.resolve_target.outputs.provider }}
mode: ${{ needs.resolve_target.outputs.mode }}
suite_filter: ${{ needs.resolve_target.outputs.cross_os_suite_filter }}
candidate_artifact_name: ${{ needs.prepare_release_package.outputs.artifact_name }}
candidate_file_name: openclaw-current.tgz
candidate_version: ${{ needs.prepare_release_package.outputs.package_version }}
@@ -416,7 +459,7 @@ jobs:
live_repo_e2e_release_checks:
name: Run repo/live E2E validation
needs: [resolve_target]
if: contains(fromJSON('["all","live-e2e"]'), needs.resolve_target.outputs.rerun_group)
if: needs.resolve_target.outputs.rerun_group == 'live-e2e' || (needs.resolve_target.outputs.rerun_group == 'all' && needs.resolve_target.outputs.run_release_soak == 'true')
permissions:
actions: read
contents: read
@@ -481,7 +524,7 @@ jobs:
docker_e2e_release_checks:
name: Run Docker release-path validation
needs: [resolve_target, prepare_release_package]
if: contains(fromJSON('["all","live-e2e"]'), needs.resolve_target.outputs.rerun_group) && needs.resolve_target.outputs.live_suite_filter == ''
if: (needs.resolve_target.outputs.rerun_group == 'live-e2e' || (needs.resolve_target.outputs.rerun_group == 'all' && needs.resolve_target.outputs.run_release_soak == 'true')) && needs.resolve_target.outputs.live_suite_filter == ''
permissions:
actions: read
contents: read
@@ -515,9 +558,9 @@ jobs:
artifact_name: ${{ needs.prepare_release_package.outputs.artifact_name }}
package_sha256: ${{ needs.prepare_release_package.outputs.package_sha256 }}
suite_profile: custom
docker_lanes: doctor-switch update-channel-switch upgrade-survivor published-upgrade-survivor plugins-offline plugin-update
published_upgrade_survivor_baselines: all-since-2026.4.23
published_upgrade_survivor_scenarios: reported-issues
docker_lanes: doctor-switch update-channel-switch upgrade-survivor published-upgrade-survivor update-restart-auth plugins-offline plugin-update
published_upgrade_survivor_baselines: ${{ needs.resolve_target.outputs.run_release_soak == 'true' && 'last-stable-4 2026.4.23 2026.5.2 2026.4.15' || '' }}
published_upgrade_survivor_scenarios: ${{ needs.resolve_target.outputs.run_release_soak == 'true' && 'reported-issues' || '' }}
telegram_mode: mock-openai
telegram_scenarios: telegram-help-command,telegram-commands-command,telegram-tools-compact-command,telegram-whoami-command,telegram-context-command,telegram-current-session-status-tool,telegram-mention-gating
secrets:
@@ -573,6 +616,7 @@ jobs:
name: Run QA Lab parity lane (${{ matrix.lane }})
needs: [resolve_target]
if: contains(fromJSON('["all","qa","qa-parity"]'), needs.resolve_target.outputs.rerun_group)
continue-on-error: true
runs-on: blacksmith-8vcpu-ubuntu-2404
timeout-minutes: 30
permissions:
@@ -657,6 +701,7 @@ jobs:
name: Run QA Lab parity report
needs: [resolve_target, qa_lab_parity_lane_release_checks]
if: contains(fromJSON('["all","qa","qa-parity"]'), needs.resolve_target.outputs.rerun_group)
continue-on-error: true
runs-on: blacksmith-8vcpu-ubuntu-2404
timeout-minutes: 20
permissions:
@@ -713,6 +758,7 @@ jobs:
name: Run QA Lab live Matrix lane
needs: [resolve_target]
if: contains(fromJSON('["all","qa","qa-live"]'), needs.resolve_target.outputs.rerun_group) && needs.resolve_target.outputs.qa_live_matrix_enabled == 'true'
continue-on-error: true
runs-on: blacksmith-8vcpu-ubuntu-2404
timeout-minutes: 60
permissions:
@@ -790,6 +836,7 @@ jobs:
name: Run QA Lab live Telegram lane
needs: [resolve_target]
if: contains(fromJSON('["all","qa","qa-live"]'), needs.resolve_target.outputs.rerun_group) && needs.resolve_target.outputs.qa_live_telegram_enabled == 'true'
continue-on-error: true
runs-on: blacksmith-8vcpu-ubuntu-2404
timeout-minutes: 60
permissions:
@@ -882,7 +929,8 @@ jobs:
qa_live_slack_release_checks:
name: Run QA Lab live Slack lane
needs: [resolve_target]
if: ${{ false }}
if: contains(fromJSON('["all","qa","qa-live"]'), needs.resolve_target.outputs.rerun_group) && needs.resolve_target.outputs.qa_live_slack_enabled == 'true' && vars.OPENCLAW_QA_SLACK_LIVE_CI_ENABLED == 'true'
continue-on-error: true
runs-on: blacksmith-8vcpu-ubuntu-2404
timeout-minutes: 60
permissions:
@@ -1012,6 +1060,10 @@ jobs:
name="${item%%=*}"
result="${item#*=}"
if [[ "$result" != "success" && "$result" != "skipped" ]]; then
if [[ "$name" == qa_* ]]; then
echo "::warning::${name} ended with ${result}; QA release-check lanes are advisory and do not block release validation."
continue
fi
echo "::error::${name} ended with ${result}"
failed=1
fi

View File

@@ -70,7 +70,7 @@ on:
default: openclaw@latest
type: string
published_upgrade_survivor_baselines:
description: Optional baseline list for published-upgrade-survivor/update-migration; use all-since-2026.4.23, release-history, or exact versions
description: Optional baseline list for published-upgrade-survivor/update-migration; use last-stable-4, all-since-2026.4.23, release-history, or exact versions
required: false
default: ""
type: string
@@ -150,7 +150,7 @@ on:
default: openclaw@latest
type: string
published_upgrade_survivor_baselines:
description: Optional baseline list for published-upgrade-survivor/update-migration; use all-since-2026.4.23, release-history, or exact versions
description: Optional baseline list for published-upgrade-survivor/update-migration; use last-stable-4, all-since-2026.4.23, release-history, or exact versions
required: false
default: ""
type: string
@@ -386,10 +386,10 @@ jobs:
docker_lanes="npm-onboard-channel-agent gateway-network config-reload"
;;
package)
docker_lanes="npm-onboard-channel-agent doctor-switch update-channel-switch upgrade-survivor published-upgrade-survivor plugins-offline plugin-update"
docker_lanes="npm-onboard-channel-agent doctor-switch update-channel-switch upgrade-survivor published-upgrade-survivor update-restart-auth plugins-offline plugin-update"
;;
product)
docker_lanes="npm-onboard-channel-agent doctor-switch update-channel-switch upgrade-survivor published-upgrade-survivor plugins plugin-update mcp-channels cron-mcp-cleanup openai-web-search-minimal openwebui"
docker_lanes="npm-onboard-channel-agent doctor-switch update-channel-switch upgrade-survivor published-upgrade-survivor update-restart-auth plugins plugin-update mcp-channels cron-mcp-cleanup openai-web-search-minimal openwebui"
include_openwebui=true
;;
full)
@@ -442,7 +442,7 @@ jobs:
fi
releases_json=""
npm_versions_json=""
if [[ "$REQUESTED_BASELINES" == *"release-history"* || "$REQUESTED_BASELINES" == *"all-since-"* ]]; then
if [[ "$REQUESTED_BASELINES" == *"release-history"* || "$REQUESTED_BASELINES" == *"all-since-"* || "$REQUESTED_BASELINES" == *"last-stable-"* ]]; then
releases_json=".artifacts/package-candidate-input/openclaw-releases.json"
npm_versions_json=".artifacts/package-candidate-input/openclaw-npm-versions.json"
mkdir -p "$(dirname "$releases_json")"

View File

@@ -241,7 +241,7 @@ jobs:
echo "$RUNNER_TEMP" >> "$GITHUB_PATH"
- name: Verify package-local runtime build
run: pnpm release:plugins:npm:runtime:check --package "${{ matrix.plugin.packageDir }}"
run: node scripts/check-plugin-npm-runtime-builds.mjs --package "${{ matrix.plugin.packageDir }}"
- name: Preview publish command
env:

View File

@@ -562,7 +562,7 @@ jobs:
run_live_slack:
name: Run Slack live QA lane with Convex leases
needs: [authorize_actor, validate_selected_ref]
if: ${{ false }}
if: vars.OPENCLAW_QA_SLACK_LIVE_CI_ENABLED == 'true'
runs-on: blacksmith-8vcpu-ubuntu-2404
timeout-minutes: 60
environment: qa-live-shared

View File

@@ -0,0 +1,29 @@
name: Real behavior proof
on:
pull_request_target: # zizmor: ignore[dangerous-triggers] trusted base checkout only; no untrusted PR code execution
types: [opened, edited, synchronize, reopened, ready_for_review, labeled, unlabeled]
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref || github.run_id }}
cancel-in-progress: true
permissions: {}
jobs:
real-behavior-proof:
name: Real behavior proof
permissions:
contents: read
pull-requests: read
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.base.sha }}
persist-credentials: false
- name: Check real behavior proof
run: node scripts/github/real-behavior-proof-check.mjs

1
.gitignore vendored
View File

@@ -219,3 +219,4 @@ extensions/**/.openclaw-runtime-deps-stamp.json
# Output dir for scripts/run-opengrep.sh (local opengrep scans)
/.opengrep-out/
/.crabbox-artifacts

View File

@@ -56,6 +56,7 @@ Telegraph style. Root rules only. Read scoped `AGENTS.md` before subtree work.
- Formatting: use `oxfmt`, not Prettier. Prefer `pnpm format:check` / `pnpm format`; for targeted files use `pnpm exec oxfmt --check --threads=1 <files...>` or `pnpm exec oxfmt --write --threads=1 <files...>`.
- Linting: use repo wrappers (`pnpm lint:*`, `scripts/run-oxlint.mjs`); do not invoke generic JS formatters/lints unless a repo script uses them.
- Heavy checks: `OPENCLAW_LOCAL_CHECK=1`, mode `OPENCLAW_LOCAL_CHECK_MODE=throttled|full`; CI/shared use `OPENCLAW_LOCAL_CHECK=0`.
- Crabbox: preferred live scenario runner when available. It has Linux, Windows, and macOS workers/targets; pick the OS that matches the bug. If unavailable, use the local system, Docker, Parallels, or CI live lane that proves the same behavior.
- Blacksmith/Testbox: on maintainer machines with Blacksmith access, broad/shared validation defaults to Testbox. This includes `pnpm check`, `pnpm check:changed`, `pnpm test`, `pnpm test:changed`, Docker/E2E/live/package/build gates, and any command likely to fan out across many Vitest projects. Do not start those broad gates locally unless the user explicitly asks for local proof or sets `OPENCLAW_LOCAL_CHECK_MODE=throttled|full`.
- Local validation: targeted edit loops only, such as `pnpm test <specific-file>`, targeted formatter checks, and small lint/type probes. If a local command expands beyond targeted proof, stop it and move the broad gate to Testbox.
- Testbox use: run from repo root, pre-warm early with `blacksmith testbox warmup ci-check-testbox.yml --ref main --idle-timeout 90`, reuse the returned `tbx_...` id for all `run`/`download` commands, and stop boxes you created before handoff. Timeout bins: `90` minutes default, `240` multi-hour, `720` all-day, `1440` overnight; anything above `1440` needs explicit approval and cleanup.
@@ -107,7 +108,8 @@ Telegraph style. Root rules only. Read scoped `AGENTS.md` before subtree work.
full checks only if conflict resolution, upstream overlap, generated drift,
dependency/config changes, or touched-file content changes make the prior
result stale.
- Landing on `main`: verify touched surface near landing. Default feasible bar: `pnpm check` + `pnpm test`.
- Before shipping commits or landing PRs to `main`: live-prove the reported issue when feasible. Prefer a Crabbox scenario that reproduces the failure on the right OS, then proves the candidate fix. If Crabbox is unavailable, use the closest real system, Docker, Parallels, CI live lane, or maintained E2E smoke; if blocked, say what proof is missing and why.
- Landing on `main`: verify touched surface near landing. Default feasible bar: issue live proof + `pnpm check` + `pnpm test`.
- Hard build gate: `pnpm build` before push if build output, packaging, lazy/module boundaries, or published surfaces can change.
- Do not land related failing format/lint/type/build/tests. If unrelated on latest `origin/main`, say so with scoped proof.
- Generated/API drift: `pnpm check:architecture`, `pnpm config:docs:gen/check`, `pnpm plugin-sdk:api:gen/check`. Track `docs/.generated/*.sha256`; full JSON ignored.

View File

@@ -10,135 +10,71 @@ Docs: https://docs.openclaw.ai
### Changes
- Control UI: refresh the app shell into a denser cockpit layout with session navigation, live runtime cards, and a right-side skills/jobs/hooks inspector.
- Telegram: accept plugin-owned numeric forum-topic targets in the agent message tool and keep reply-dispatch provider chunks behind a real stable runtime alias during in-place package updates. Fixes #77137. Thanks @richardmqq.
- Channels/WhatsApp: support explicit WhatsApp Channel/Newsletter `@newsletter` outbound message targets with channel session metadata instead of DM routing. Fixes #13417; carries forward the narrow outbound target idea from #13424. Thanks @vincentkoc and @agentz-manfred.
- TTS/telephony: honor provider voice/model overrides in telephony synthesis providers so Google Meet agent speech logs match the backend that actually produced the audio. Thanks @vincentkoc.
- Voice Call/realtime: bound the paced Twilio audio queue and close overloaded realtime streams before provider audio can pile up behind the websocket backpressure guard. Thanks @vincentkoc.
- Google Meet: preserve `realtime.introMessage: ""` so realtime Chrome joins can stay silent instead of restoring the default spoken intro. Thanks @vincentkoc.
- OpenAI/Codex media: advertise Codex audio transcription in runtime and manifest metadata and route active Codex chat models to the OpenAI transcription default instead of sending chat model ids to audio transcription. Thanks @vincentkoc.
- Models/auth: add `openclaw models auth list [--provider <id>] [--json]` so users can inspect saved per-agent auth profiles without dumping secrets or hitting the old “too many arguments” path. Thanks @vincentkoc.
- QA/Slack: disable the Slack live QA lane in scheduled all-lanes and release-check workflows while leaving the source CLI available for manual recovery.
- Control UI/header: show the active agent name in dashboard breadcrumbs without adding the current session key, keeping non-chat views oriented without crowding the topbar.
- Control UI/cron: make the New Job sidebar collapsible so the jobs list can reclaim space while keeping the form one click away. Thanks @BunsDev.
- Gateway/startup: keep model-catalog test helpers, run-session lookup code, QR pairing helpers, and TypeBox memory-tool schema construction out of hot startup import paths, reducing default gateway benchmark plugin-load and memory pressure.
- Control UI/performance: record browser long animation frame or long task entries in the debug event log when supported, making slow dashboard renders easier to attribute from the UI.
- Cron CLI: add `openclaw cron list --agent <id>`, normalize the requested agent id, and include jobs without a stored agent id under the configured default agent while keeping `cron list` unfiltered when no agent is supplied. Fixes #77118. Thanks @zhanggttry.
- Status: show compact Gateway process uptime and host system uptime in `/status`, making restart and host-lifetime checks visible from chat. Thanks @vincentkoc.
- Discord/status: add degraded Discord transport and gateway event-loop starvation signals to `openclaw channels status`, `openclaw status --deep`, and fetch-timeout logs so intermittent socket resets do not look like a healthy running channel. (#76327) Thanks @joshavant.
- Gateway/Windows: bind the default loopback gateway listener only to `127.0.0.1` on Windows so libuv's dual-stack `::1` behavior cannot wedge localhost HTTP requests. (#69701, fixes #69674) Thanks @SARAMALI15792.
- Slack/streaming: add `streaming.progress.render: "rich"` for Block Kit progress drafts backed by structured progress line data.
- Slack/streaming: keep the newest rich progress lines when Block Kit limits trim long progress drafts. Thanks @vincentkoc.
- Channels/streaming: cap progress-draft tool lines by default so edited progress boxes avoid jumpy reflow from long wrapped lines.
- Agents/verbose: use compact explain-mode tool summaries for `/verbose` and progress drafts by default, with `agents.defaults.toolProgressDetail: "raw"` and per-agent overrides for debugging raw command/detail output.
- Control UI/chat: add an agent-first filter to the chat session picker, keep chat controls/composer responsive across phone/tablet/desktop widths, keep desktop chat controls on one row, avoid duplicate avatar refreshes during initial chat load, and hide that row while scrolling down the transcript. Thanks @BunsDev.
- Control UI/chat: collapse consecutive duplicate text messages into one bubble with a count so no-op heartbeat acknowledgements stay compact without hiding nearby context.
- Agents/subagents: preserve every grouped child result when direct completion fallback has to bypass the requester-agent announce turn. Thanks @vincentkoc.
- TTS/telephony: honor provider voice/model overrides in telephony synthesis providers so Google Meet agent speech logs match the backend that actually produced the audio. Thanks @vincentkoc.
- Voice Call/realtime: bound the paced Twilio audio queue and close overloaded realtime streams before provider audio can pile up behind the websocket backpressure guard. Thanks @vincentkoc.
- Docs: clarify that IRC uses raw TCP/TLS sockets outside operator-managed forward proxy routing, so direct IRC egress should be explicitly approved before enabling IRC. Thanks @jesse-merhi.
- Gateway/performance: defer non-readiness sidecars until after the ready signal, avoid hot-path channel plugin barrel imports, and fast-path trusted bundled plugin metadata during Gateway startup.
- Gateway/performance: avoid importing `jiti` on native-loadable plugin startup paths, so compiled bundled plugin surfaces do not pay source-transform loader cost unless fallback loading is actually needed.
- Plugins/loader: preserve real compiled plugin module evaluation errors on the native fast path instead of treating every thrown `.js` module as a source-transform fallback miss. Thanks @vincentkoc.
- QA/Mantis: add `pnpm openclaw qa mantis slack-desktop-smoke` to run Slack live QA inside a Crabbox VNC desktop, open Slack Web, and capture desktop screenshots beside the Slack QA artifacts.
- QA/Mantis: pass the runtime env through desktop-browser Crabbox and artifact-copy child commands, so embedded Mantis callers can provide Crabbox credentials without mutating the parent process. Thanks @vincentkoc.
- QA/Mantis: return the copied Slack desktop screenshot path even when remote Slack QA fails, so the CLI still prints the failure screenshot artifact. Thanks @vincentkoc.
- QA/Mantis: accept Blacksmith Testbox `tbx_...` lease ids from desktop smoke warmup, so provider overrides do not fail before inspect/run. Thanks @vincentkoc.
- Control UI/chat: collapse consecutive duplicate text messages into one bubble with a count so repeated text-only messages stay compact without hiding nearby context.
- Control UI/cron: make the New Job sidebar collapsible so the jobs list can reclaim space while keeping the form one click away. Thanks @BunsDev.
- Control UI/header: show the active agent name in dashboard breadcrumbs without adding the current session key, keeping non-chat views oriented without crowding the topbar.
- Plugins/migration: emit catalog-backed install hints when `plugins.entries` or `plugins.allow` references an official external plugin that is not installed, so upgraded configs point operators to `openclaw plugins install <spec>` instead of telling them to remove valid plugin config. (#77483) Thanks @hclsys.
- Plugins/ClawHub: annotate 429 errors from ClawHub with the reset window from `RateLimit-Reset`/`Retry-After` and append a `Sign in for higher rate limits.` hint when the request was unauthenticated, so users can see when downloads will recover and how to lift the cap. Thanks @romneyda.
- Secrets/external channel contracts: also look in `<rootDir>/dist/` when resolving the `secret-contract-api` sidecar, so npm-published externalized channel plugins (e.g. `@openclaw/discord` since 2026.5.2) whose compiled artifacts live under `dist/` actually contribute their channel SecretRef contracts to the runtime snapshot. Without this, env-backed `channels.discord.token` SecretRefs silently failed to resolve at gateway start on 2026.5.3, leaving the channel `not configured` even though #76449 had landed the generic external-contract loader. Thanks @mogglemoss.
- Secrets/apply: preserve auth-profile `keyRef` and `tokenRef` fields when scrubbing provider-target secrets, so the canonical SecretRef metadata survives `secrets apply` without keeping plaintext values. Thanks @Beandon13.
- Config/plugin auto-enable: prefer the claiming plugin manifest id over a built-in channel alias when auto-allowlisting a configured channel, so WeCom/Yuanbao-style aliases resolve to the installed plugin id. Thanks @Beandon13.
- Plugins/update: treat official externalized bundled npm migrations and ClawHub-to-npm fallbacks as trusted source-linked installs, so prerelease-only official plugin packages can migrate from bundled builds without being rejected as unsafe prerelease resolutions. Thanks @vincentkoc.
- Plugins/update: move ClawHub-preferred externalized plugin installs back to ClawHub after an earlier npm fallback once the ClawHub package becomes available. Thanks @vincentkoc.
- Plugins/update: clean stale bundled load paths for already-externalized pinned npm and ClawHub plugin installs, so release-channel sync does not leave removed bundled paths ahead of the installed external package. Thanks @vincentkoc.
- Telegram: accept plugin-owned numeric forum-topic targets in the agent message tool and keep reply-dispatch provider chunks behind a real stable runtime alias during in-place package updates. Fixes #77137. Thanks @richardmqq.
- Google Meet: preserve `realtime.introMessage: ""` so realtime Chrome joins can stay silent instead of restoring the default spoken intro. Thanks @vincentkoc.
- Plugins/SDK: add bounded `before_agent_finalize` retry instructions so workflow plugins can request one more model pass. Thanks @100yenadmin.
- Discord/status: add degraded Discord transport and gateway event-loop starvation signals to `openclaw channels status`, `openclaw status --deep`, and fetch-timeout logs so intermittent socket resets do not look like a healthy running channel. (#76327) Thanks @joshavant.
- Plugins/update: make package upgrades swap pnpm/npm-prefix installs cleanly, keep legacy plugin install runtime chunks working, and on the beta channel fall back default-line npm plugins to default/latest when plugin beta releases are missing or fail install validation. Thanks @vincentkoc and @joshavant.
- Plugins/active-memory: skip session-store channel entries that contain `:` when resolving the recall subagent's channel, so QQ c2c agent IDs (e.g. `c2c:10D4F7C2…`) and other scoped conversation IDs do not reach bundled-plugin `dirName` validation and crash the recall run. The same guard already applied to explicit `channelId` params (#76704); this extends it to store-derived channels. (#77396) Thanks @hclsys.
- Sandbox/Windows: accept drive-absolute Docker bind sources while keeping sandbox blocked-path and allowed-root policy comparisons Windows-case-insensitive. (#42174) Thanks @6607changchun.
- Agents/subagents: preserve every grouped child result when direct completion fallback has to bypass the requester-agent announce turn. Thanks @vincentkoc.
- Agents/verbose: use compact explain-mode tool summaries for `/verbose` and progress drafts by default, with `agents.defaults.toolProgressDetail: "raw"` and per-agent overrides for debugging raw command/detail output.
- Gateway/startup: keep model-catalog test helpers, run-session lookup code, QR pairing helpers, and TypeBox memory-tool schema construction out of hot startup import paths, reducing default gateway benchmark plugin-load and memory pressure.
- Gateway/performance: defer non-readiness sidecars until after the ready signal, avoid hot-path channel plugin barrel imports, and fast-path trusted bundled plugin metadata during Gateway startup.
- Gateway/performance: avoid importing `jiti` on native-loadable plugin startup paths, so compiled bundled plugin surfaces do not pay source-transform loader cost unless fallback loading is actually needed.
- Plugins/loader: preserve real compiled plugin module evaluation errors on the native fast path instead of treating every thrown `.js` module as a source-transform fallback miss. Thanks @vincentkoc.
- Providers/OpenRouter: add opt-in response caching params that send OpenRouter's `X-OpenRouter-Cache`, `X-OpenRouter-Cache-TTL`, and cache-clear headers only on verified OpenRouter routes. Thanks @vincentkoc.
- Providers/OpenRouter: expand app-attribution categories so OpenClaw advertises coding, programming, writing, chat, and personal-agent usage on verified OpenRouter routes. Thanks @vincentkoc.
- Agents/performance: pass the resolved workspace through BTW, compaction, embedded-run model generation, and PDF model setup so explicit agent-dir model refreshes can reuse the current workspace-scoped plugin metadata snapshot instead of falling back to cold plugin metadata scans. (#77519, #77532)
- Plugins/performance: let unscoped model catalog and manifest-contract readers reuse the current workspace-compatible plugin metadata snapshot, avoiding repeated cold plugin metadata scans on hot control-plane paths while preserving env/config/workspace compatibility checks. (#77519, #77532)
- Agents/sandbox: store sandbox container and browser registry entries as per-runtime shard files, reducing unrelated session lock contention while `openclaw doctor --fix` migrates legacy monolithic registry files. (#74831) Thanks @luckylhb90.
- Plugins/runtime state: add `registerIfAbsent` for atomic keyed-store dedupe claims that return whether a plugin successfully claimed a key without overwriting an existing live value. Thanks @amknight.
- Exec approvals: add a tree-sitter-backed shell command explainer for future approval and command-review surfaces. (#75004) Thanks @jesse-merhi.
- Control UI/performance: record browser long animation frame or long task entries in the debug event log when supported, making slow dashboard renders easier to attribute from the UI.
- Gateway/diagnostics: add startup phase spans, active work labels, stale terminal bridge markers, and default sync-I/O tracing in `pnpm gateway:watch` so slow Gateway turns are easier to attribute from logs and stability diagnostics.
- QA/Codex harness: add targeted live Docker/Testbox diagnostics, auth preflight checks, cache mount fixes, and app-server protocol checkout discovery so maintainer harness failures are easier to reproduce. Thanks @vincentkoc.
- QA/Mantis: add `pnpm openclaw qa mantis slack-desktop-smoke` to run Slack live QA inside a Crabbox VNC desktop, open Slack Web, and capture desktop screenshots beside the Slack QA artifacts.
- QA/Mantis: add visual desktop tasks with Crabbox MP4 recording, screenshot capture, and optional image-understanding assertions, and preserve video artifacts in Mantis before/after reports.
- QA/Mantis: pass the runtime env through desktop-browser Crabbox and artifact-copy child commands, so embedded Mantis callers can provide Crabbox credentials without mutating the parent process. Thanks @vincentkoc.
- QA/Mantis: return the copied Slack desktop screenshot path even when remote Slack QA fails, so the CLI still prints the failure screenshot artifact. Thanks @vincentkoc.
- QA/Mantis: accept Blacksmith Testbox `tbx_...` lease ids from desktop smoke warmup, so provider overrides do not fail before inspect/run. Thanks @vincentkoc.
- Plugins/SDK: add bounded `before_agent_finalize` retry instructions so workflow plugins can request one more model pass. Thanks @100yenadmin.
- Plugin SDK: add plugin-owned `SessionEntry` slot projection and scoped trusted-policy session extension reads. (#75609; replaces part of #73384/#74483) Thanks @100yenadmin.
- Docs: clarify that IRC uses raw TCP/TLS sockets outside operator-managed forward proxy routing, so direct IRC egress should be explicitly approved before enabling IRC. Thanks @jesse-merhi.
- Dependencies: refresh runtime and provider packages including Pi 0.73.0, ACPX adapters, OpenAI, Anthropic, Slack, and TypeScript native preview, while keeping the Bedrock runtime installer override pinned below the Windows ARM Node 24 npm resolver failure.
- Contributor PRs: require external pull requests to include after-fix real behavior proof from a real OpenClaw setup, with terminal screenshots, console output, redacted runtime logs, linked artifacts, and copied live output treated as valid evidence while unit tests, mocks, lint, typechecks, snapshots, and CI remain supplemental only.
### Fixes
- Gate zalouser startup name matching [AI]. (#77411) Thanks @pgondhi987.
- fix(device-pair): require pairing scope for pair command [AI]. (#76377) Thanks @pgondhi987.
- fix(qqbot): keep private commands off framework surface [AI]. (#77212) Thanks @pgondhi987.
- Memory/wiki: preserve representation from both corpora in `corpus=all` searches while backfilling unused result capacity, so memory hits are not starved by numerically higher wiki integer scores. Fixes #77337. Thanks @hclsys.
- Telegram: clean up tool-only draft previews after assistant message boundaries so transient `Surfacing...` tool-status bubbles do not linger when no matching final preview arrives. Thanks @BunsDev.
- Cron: surface failed isolated-run diagnostics in `cron show`, status, and run history when requested tools are unavailable, so blocked cron runs report the actual tool-policy failure instead of a misleading green result. Fixes #75763. Thanks @RyanSandoval.
- TUI/escape abort: track the in-flight runId after `chat.send` resolves so pressing Esc during the gap before the first gateway event aborts the run instead of repeatedly printing `no active run`. Fixes #1296. Thanks @Lukavyi and @romneyda.
- TUI/render: stop the long-token sanitizer from injecting literal spaces inside inline code spans, fenced code blocks, table borders, and bare hyphenated/dotted identifiers, so copied package names, entity IDs, and shell line-continuations stay byte-for-byte intact while narrow-terminal protection still chunks unidentifiable long prose tokens. Fixes #48432, #39505. Thanks @DocOellerson, @xeusoc, @CCcassiusdjs, @akramcodez, @brokemac79, @romneyda.
- Plugin skills: publish plugin-declared skills through the generated plugin skills directory (`~/.openclaw/plugin-skills/`) while keeping direct prompt loading intact, so agent file-based discovery paths find plugin skill `SKILL.md` files and inactive plugin links are cleaned up. Fixes #77296. (#77328) Thanks @zhangguiping-xydt.
- Gateway/status: label Linux managed gateway services as `systemd user`, making status output explicit about the user-service scope instead of implying a system-level unit. Thanks @vincentkoc.
- Plugins/install: remove the previous managed plugin directory when a reinstall switches sources, so stale ClawHub and npm copies no longer keep duplicate plugin ids in discovery after the new install wins. Thanks @vincentkoc.
- Plugins/install: let official plugin reinstall recovery repair source-only installed runtime shadows, so `openclaw plugins install npm:@openclaw/discord --force` can replace the bad package instead of stopping at stale config validation. Thanks @vincentkoc.
- Plugins/commands: allow the official ClawHub Codex plugin package to keep reserved `/codex` command ownership, matching the existing npm-managed Codex package behavior. Thanks @vincentkoc.
- Auth/OpenAI Codex: rewrite invalidated per-agent Codex auth-order and session profile overrides toward a healthy relogin profile, so revoked OAuth accounts do not stay pinned after signing in again. Thanks @BunsDev.
- Plugins/commands: scope QQBot framework slash commands to the QQBot channel so `/bot-*` command handlers and native specs do not leak onto unrelated chat surfaces. Thanks @vincentkoc.
- fix: harden backend message action gateway routing [AI]. (#76374) Thanks @pgondhi987.
- Gate QQBot streaming command auth [AI]. (#76375) Thanks @pgondhi987.
- Plugins/discovery: ignore managed npm plugin packages that only expose TypeScript source entries without compiled runtime output, so stale/broken installs cannot hide a working bundled or reinstallable channel plugin during setup. Thanks @vincentkoc.
- CLI/update: treat OpenClaw stable correction versions like `2026.5.3-1` as newer than their base stable release, so package updates no longer ask for downgrade confirmation. Thanks @vincentkoc.
- Plugins/install: suppress dangerous-pattern scanner warnings for trusted official OpenClaw npm installs, so installing `@openclaw/discord` no longer prints credential-harvesting warnings for the official package. Thanks @vincentkoc.
- Plugins/release: make the published npm runtime verifier reject blank `openclaw.runtimeExtensions` entries instead of treating them as absent and passing via inferred outputs. Thanks @vincentkoc.
- Plugins/security: ignore inline and block comments when matching source-rule context in plugin install scans, so comment-only `fetch`/`post` references near environment defaults do not block clean plugins. Thanks @vincentkoc.
- Doctor/plugins: remove stale managed install records for bundled plugins even when the bundled plugin is not explicitly configured, so doctor cleanup cannot leave orphaned install metadata behind. Thanks @vincentkoc.
- Web fetch: scope provider fallback cache entries by the selected fetch provider so config reloads cannot reuse another provider's cached fallback payload. Thanks @vincentkoc.
- Web search: honor late-bound `tools.web.search.enabled: false` during tool execution so config reloads cannot leave an already-created `web_search` tool runnable. Thanks @vincentkoc.
- Plugins/packages: reject inferred built runtime entries that exist but fail package-boundary checks instead of falling back to TypeScript source for installed packages. Thanks @vincentkoc.
- Plugins/loader: do not retry native-loaded JavaScript plugin modules through the source transformer after native evaluation has already reached a missing dependency, avoiding duplicate top-level side effects. Thanks @vincentkoc.
- Plugins/packages: reject blank `openclaw.runtimeExtensions` entries instead of silently ignoring them and falling back to inferred TypeScript runtime entries. Thanks @vincentkoc.
- Doctor/plugins: remove stale managed npm plugin shadow entries from the managed package lock as well as `package.json` and `node_modules`, so future npm operations do not keep referencing repaired bundled-plugin shadows. Thanks @vincentkoc.
- Plugins/runtime state: keep the key being registered when namespace eviction runs in the same millisecond as existing entries, so `register` and `registerIfAbsent` do not report success while evicting their own fresh value. Thanks @vincentkoc.
- Control UI/Talk: make failed Talk startup errors dismissable and clear the stale Talk error state when dismissed, so missing realtime voice provider configuration does not leave a permanent chat banner. Fixes #77071. Thanks @ijoshdavis.
- Control UI/Talk: stop and clear failed realtime Talk sessions when dismissing runtime error banners, so the next Talk click starts a fresh session instead of only stopping the stale one. Thanks @vincentkoc.
- Control UI/Talk: retry from a failed realtime Talk session on the next Talk click instead of requiring a separate stale-session stop click first. Thanks @vincentkoc.
- Canvas host: preserve the Gateway TLS scheme in browser canvas host URLs and startup mount logs, so direct HTTPS gateways do not advertise insecure canvas links. Thanks @vincentkoc.
- WhatsApp/login: route login success and failure messages through the injected runtime, so setup/onboarding surfaces capture all login output instead of only the QR. Thanks @vincentkoc.
- Google Chat: create an isolated Google auth transport per auth client, so google-auth-library interceptor mutations do not accumulate across webhook verification and access-token clients. Thanks @vincentkoc.
- Doctor/plugins: remove orphaned or recovered managed npm copies of bundled `@openclaw/*` plugins during `doctor --fix`, so stale package manifests cannot shadow the current bundled plugin config schema.
- Control UI/performance: cap long-task and long-animation-frame diagnostics in the shared event log, so slow-render telemetry does not evict gateway/plugin events from the Debug and Overview views. Thanks @vincentkoc.
- Gateway/startup: log the canvas host mount only after the HTTP server has bound, so startup logs no longer report the canvas host as mounted before it can serve requests.
- Control UI/i18n: render the Sessions active filter tooltip with the configured minute count in every locale and make the i18n check reject placeholder drift. Thanks @BunsDev.
- Web fetch: late-bind `web_fetch` config and provider fallback metadata from the active runtime snapshot, matching `web_search` so long-lived tools do not use stale fetch provider settings. Thanks @vincentkoc.
- Discord: clear stale startup probe bot/application status when the async bot probe throws, not just when it returns a degraded probe result. Thanks @vincentkoc.
- Web search: scope explicit bundled `web_search` provider runtime loading through manifest ownership, so selecting DuckDuckGo/Gemini/etc. does not import unrelated bundled providers or log their optional dependency failures. Thanks @vincentkoc.
- Plugins/discovery: demote the source-only TypeScript runtime check on already-installed `origin: "global"` plugin packages from a config-blocking error to a warning and let the runtime fall through to the TypeScript source via jiti, so a single broken installed package no longer blocks `plugins install` for unrelated plugins; install-time rejection of newly-installed source-only packages is unchanged. Thanks @romneyda.
- Providers/OpenAI Codex: stop the OAuth progress spinner before showing the manual redirect paste prompt, so callback timeouts do not spam `Browser callback did not finish` across terminals.
- Providers/DeepSeek: expose DeepSeek V4 `xhigh` and `max` thinking levels through the lightweight provider-policy surface, so Control UI `/think` pickers keep showing the max reasoning options when the runtime plugin registry is not active. Fixes #77139. Thanks @bittoby.
- Release/beta smoke: resolve the dispatched Telegram beta E2E run from `gh run list` when `gh workflow run` returns no run URL, so the maintainer helper does not fail immediately after dispatch. Thanks @vincentkoc.
- Media/images: keep HEIC/HEIF attachments fail-closed when optional Sharp conversion is unavailable instead of sending originals that still need conversion. Thanks @vincentkoc.
- Video generation: wait up to 20 minutes for slow fal/MiniMax queue-backed jobs, stop forwarding unsupported Google Veo generated-audio options, and normalize MiniMax `720P` requests to its supported `768P` resolution with the usual override warning/details instead of failing fallback.
- Video generation: accept provider-specific aspect-ratio and resolution hints at the tool boundary, normalize `720P` to MiniMax's supported `768P`, and stop sending Google `generateAudio` on Gemini video requests so provider fallback can recover from model-specific parameter differences. Thanks @vincentkoc.
- OpenAI/Google Meet: fail realtime voice connection attempts when the socket closes before `session.updated`, avoiding stuck Meet joins waiting on a bridge that never became ready. Thanks @vincentkoc.
- Google Meet: fork the caller's current agent transcript into agent-mode meeting consultant sessions, so Meet replies inherit the context from the tool call that joined the meeting.
- iOS/mobile pairing: reject non-loopback `ws://` setup URLs before QR/setup-code issuance and let the iOS Gateway settings screen scan QR codes or paste full setup-code messages. Thanks @BunsDev.
- Control UI: keep Gateway Access inputs and locale picker contained inside the card at narrow and tablet widths.
- Agents/trajectory: bound runtime trajectory capture and yield queued sidecar writes so oversized traces stop recording instead of monopolizing Gateway cleanup. Fixes #77124. Thanks @loyur.
- Telegram/streaming: sanitize tool-progress draft preview backticks before shared compaction, so long backtick-heavy progress text still renders inside the safe code-formatted preview instead of collapsing to an ellipsis.
- UI/chat: remove the unsupported `line-clamp` declaration from the chat queue text rule to eliminate Firefox console noise without changing visible truncation behavior. Thanks @ZanderH-code.
- Control UI: add explicit feedback for repeated actions by announcing session switches, flashing the active session selector, showing inline Save/Apply/Update progress, and distinguishing filtered-empty session lists from genuinely empty session stores. Thanks @BunsDev.
- Agents/Pi: suppress persistence for synthetic mid-turn overflow continuation prompts, so transcript-retry recovery does not write the "continue from transcript" prompt as a new user turn. Thanks @vincentkoc.
- Agents/tools: strip reasoning text from visible rich presentation titles, blocks, buttons, and select labels before message-tool sends, so structured channel payloads cannot leak hidden planning. Thanks @vincentkoc.
- Telegram: keep reply-dispatch lazy provider runtime chunks behind stable dist names and delete `/reasoning stream` previews after final delivery so package updates and live reasoning drafts do not leave Telegram turns broken or noisy. Thanks @BunsDev.
- Discord: start the gateway monitor without waiting for the startup bot/application probe, so WSL2 hosts with a slow `/users/@me` REST path still bring the channel online while status enrichment finishes asynchronously. Fixes #77103. Thanks @Suited78.
- Exec approvals: detect `env -S` split-string command-carrier risks when `-S`/`-s` is combined with other env short options, so approval explanations do not miss split payloads hidden behind `env -iS...`. Thanks @vincentkoc.
- Google Meet: log the concrete agent-mode TTS provider, model, voice, output format, and sample rate after speech synthesis, so Meet logs show which voice backend spoke each reply.
- Voice Call: mark realtime calls completed when the realtime provider closes normally, so Twilio/OpenAI/Google realtime stop events do not leave active call records behind. Thanks @vincentkoc.
- Gateway/update: keep the shutdown close path behind a stable runtime chunk and ship compatibility aliases for recent `server-close-*` hashes, so manual npm package replacement cannot leave an already-running Gateway unable to shut down cleanly. Fixes #77087. Thanks @westlife219.
- Control UI/media: mint short-lived scoped tickets for assistant media fetches and render ticketed URLs instead of exposing long-lived auth tokens in chat image URLs. Fixes #70830 and #77097. Thanks @hclsys.
- Exec approvals: treat POSIX `exec` as a command carrier for inline eval, shell-wrapper, and eval/source detection, so approval explanations and command-risk checks do not miss payloads hidden behind `exec`. Thanks @vincentkoc.
- Google Meet: log the resolved audio provider model when starting Chrome and paired-node Meet talk-back bridges, so agent-mode joins show the STT model and bidi joins show the realtime voice model.
- Diagnostics: handle missing session-tail files in cron recovery context without tripping extension test typecheck. Thanks @vincentkoc.
- QA/Slack: update the Slack dispatch preview fallback test SDK mock for structured progress draft helpers, so the rich progress draft regression suite covers the new imports instead of failing before assertions run. Thanks @vincentkoc.
- Release validation: allow focused QA live reruns to select Matrix and Telegram without running Slack, so known Slack credential-pool outages do not block non-Slack live proof. Thanks @vincentkoc.
- Plugins/loader: keep bundled plugin package `test-api.js` aliases behind private QA mode, so source transforms do not expose test-only public surfaces during normal plugin loading. Thanks @vincentkoc.
- Gateway/startup: start cron and record the post-ready memory trace even when deferred maintenance timers fail after readiness, so a non-fatal timer setup issue does not silently leave scheduled jobs idle. Thanks @vincentkoc.
- Exec approvals: unwrap BSD/macOS `env -P <path>` carrier commands before approval-command and strict inline-eval checks, so `/approve` shell execution and inline interpreter payloads are still blocked behind that env form.
- Agents/session status: keep semantic `session_status({ sessionKey: "current" })` on the live run session even before that run has a persisted session-store entry, instead of falling back to the sandbox policy key. Thanks @vincentkoc.
- QA/Slack: resolve bundled official plugin public-surface package aliases during source-mode QA runs, so release Slack live validation can load `@openclaw/slack/api.js` without workspace symlinks. Thanks @vincentkoc.
- Codex: pass the live run session key into app-server dynamic tools when sandbox policy uses a separate session key, so `session_status({ sessionKey: "current" })` reports the active run instead of the sandbox policy key. Thanks @vincentkoc.
- Web search: keep first-class assistant `web_search` auto-detect and configured runtime providers visible when active runtime metadata or the active plugin registry is incomplete. Fixes #77073. Thanks @joeykrug.
- Plugins/tools: mark manifest-optional sibling tools as optional even when they come from a shared non-optional factory, so cached/status/MCP metadata keeps opt-in tool policy accurate. Thanks @vincentkoc.
- Matrix: keep `streaming.progress.toolProgress` scoped to progress draft mode, so partial and quiet Matrix previews do not lose tool progress unless `streaming.preview.toolProgress` is disabled. Thanks @vincentkoc.
- Gateway/validation: isolate gateway server validation files, ignore unrelated startup logs in request-trace coverage, and fail fast on stuck shared-auth sockets, reducing false main-branch CI failures for contributors. Thanks @amknight.
- Channels/streaming: keep `streaming.progress.toolProgress` scoped to progress draft mode, so disabling compact progress lines does not silence partial/block preview tool updates. Thanks @vincentkoc.
- Plugins/update: treat OpenClaw stable correction versions like `2026.5.3-1` as stable releases for npm installs, plugin updates, and bundled-version comparisons, so `latest` can advance official plugins without prerelease opt-in. Thanks @vincentkoc.
- Control UI: point the Appearance tweakcn browse action and docs at the live tweakcn editor route instead of the removed `/themes` page. Fixes #77048.
- Control UI: render Dream Diary prose through the sanitized markdown pipeline, so diary bold/italic/header markdown no longer appears as literal source text. Fixes #62413.
- Control UI: render tool results whose output arrives as text-block arrays and give expanded tool output a scrollable block, so read/exec output remains visible in WebChat. Fixes #77054.
- MCP: include serialized conversation/message payloads in the primary text content for `conversations_list` and `messages_read`, while preserving `structuredContent` for capable clients. Fixes #77024.
- Media: treat `EPERM` from the post-write media fsync step as best-effort, allowing WebChat and channel uploads to finish on Windows filesystems that reject `fsync` after a successful write. Fixes #76844.
- Media/Telegram: send in-limit original images when optional image optimization is unavailable, so Telegram MEDIA replies and message-tool image sends do not fail just because `sharp` is missing. Fixes #77081. (#77117) Thanks @pfrederiksen.
- Diagnostics: include last progress, cron job/run ids, stopped cron job name, and the last assistant transcript snippet in stalled-session and stuck-session recovery logs so cron stalls show what was stopped.
- Streaming channels: add `streaming.preview.commandText: "status"` / `streaming.progress.commandText: "status"` to hide command/exec text in preview progress lines while keeping the released raw command text default. Fixes #77072.
- Agents/cron: let explicit cron `timeoutSeconds` drive both CLI no-output and embedded LLM idle watchdogs instead of being capped by resume defaults. Fixes #76289.
- Plugins/catalog: suppress missing `channelConfigs` compatibility diagnostics for external channel plugins that are disabled, denied, or outside a restrictive allowlist. Fixes #76095.
- Diagnostics: keep webhook/message OTEL attributes and Prometheus delivery labels low-cardinality and omit raw chat/message IDs from spans, so progress-draft and message-tool modes do not leak high-cardinality messaging identifiers.
- Google Meet: stop advertising legacy `mode: "realtime"` to agents and config UIs, while keeping it as a hidden compatibility alias for `mode: "agent"`, so new joins use the STT -> OpenClaw agent -> TTS path instead of selecting the direct realtime voice fallback.
- Google Meet: add `chrome.audioBufferBytes` for generated command-pair SoX audio commands and lower the default buffer from SoX's 8192 bytes to 4096 bytes to reduce Chrome talk-back latency.
- Google Meet: split realtime provider config into agent-mode transcription and bidi-mode voice providers, and migrate legacy Gemini Live bidi configs with `doctor --fix`, so Gemini Live can back direct bidi fallback without breaking the default OpenClaw agent talk-back path.
@@ -146,64 +82,238 @@ Docs: https://docs.openclaw.ai
- Google Meet: expose `voiceCall.postDtmfSpeechDelayMs` in the plugin manifest schema and setup hints, so manifest-based config editing accepts the runtime-supported Twilio delay key. Thanks @vincentkoc.
- Google Meet: keep explicit non-Google `realtime.provider` values as the transcription provider compatibility fallback when `realtime.transcriptionProvider` is unset. Thanks @vincentkoc.
- Google Meet: make Twilio setup status require an enabled `voice-call` plugin entry instead of treating a missing entry as ready. Thanks @vincentkoc.
- Google Meet: avoid treating repeated participant words as multiple assistant-overlap matches when suppressing realtime echo transcripts. Thanks @vincentkoc.
- Google Meet: make `mode: "agent"` the default Chrome talk-back path, using realtime transcription for input and regular OpenClaw TTS for speech output, while keeping direct realtime voice answers available as `mode: "bidi"` and accepting `mode: "realtime"` as an agent-mode compatibility alias.
- Google Meet: make realtime talk-back agent-driven by default with `realtime.strategy: "agent"`, keep the previous direct bidirectional model behavior available as `realtime.strategy: "bidi"`, route the Meet tab speaker output to `BlackHole 2ch` automatically for local Chrome realtime joins, coalesce nearby speech transcript fragments before consulting the agent, and avoid cutting off agent speech from server VAD or stale playback pipe errors.
- Google Meet: suppress queued assistant playback and assistant-like transcript echoes from the realtime input path, so the meeting does not hear the agent's own speech as a new user turn and loop or cut itself off.
- Google Meet: keep Chrome realtime transport tests hermetic on Linux prerelease shards while preserving the macOS-only runtime guard. Thanks @vincentkoc.
- Voice Call: mark realtime calls completed when the realtime provider closes normally, so Twilio/OpenAI/Google realtime stop events do not leave active call records behind. Thanks @vincentkoc.
- Slack: keep health-monitor recovery stops from poisoning manual-stop state after channel stop timeouts, allowing Socket Mode accounts to reconnect after event-loop stalls instead of staying dead until Gateway restart. Fixes #77651. Thanks @Gusty3055.
- Slack: report `unknown error` instead of `undefined` in socket-mode startup retry logs and label the retry reason explicitly.
- Slack/mentions: record thread participation for successful visible threaded Slack sends, including message-tool and media delivery paths, so unmentioned replies in bot-participated threads can bypass mention gating as documented. Fixes #77648. Thanks @bek91.
- Slack/subagents: keep resumed parent `message.send` calls in the originating Slack thread when ambient session thread context is present, and suppress successful silent child completion rows from follow-up findings. Thanks @bek91.
- WhatsApp/onboarding: canonicalize setup and pairing allowlist entries to WhatsApp's digit-only phone ids while still accepting E.164, JID, and `whatsapp:` inputs, so personal-phone allowlists match WhatsApp Web sender ids after setup. Thanks @vincentkoc.
- WhatsApp/login: route login success and failure messages through the injected runtime, so setup/onboarding surfaces capture all login output instead of only the QR. Thanks @vincentkoc.
- Channels/WhatsApp: apply the shared group/channel visible-reply mode during inbound dispatch so group replies stay message-tool-only by default without overriding direct-chat harness defaults. Refs #75178 and #67394. Thanks @scoootscooob.
- Telegram/media: derive no-caption inbound media placeholders from saved MIME metadata instead of the Telegram `photo` shape, so non-image and mixed attachments no longer reach the model as `<media:image>`. Fixes #69793. Thanks @aspalagin.
- Telegram/streaming: reuse the active preview as the first chunk for long text finals, so multi-chunk replies no longer create a transient extra bubble that appears and then disappears. Thanks @vincentkoc.
- Telegram/streaming: sanitize tool-progress draft preview backticks before shared compaction, so long backtick-heavy progress text still renders inside the safe code-formatted preview instead of collapsing to an ellipsis.
- Telegram: clean up tool-only draft previews after assistant message boundaries so transient `Surfacing...` tool-status bubbles do not linger when no matching final preview arrives. Thanks @BunsDev.
- Telegram: let explicit forum-topic `requireMention` settings override persisted `/activate` and `/deactivate` state, so per-topic mention gates work consistently. Fixes #49864. Thanks @Panniantong.
- Telegram: keep reply-dispatch lazy provider runtime chunks behind stable dist names and delete `/reasoning stream` previews after final delivery so package updates and live reasoning drafts do not leave Telegram turns broken or noisy. Thanks @BunsDev.
- Telegram: render shared interactive reply buttons in reply delivery so plugin approval messages show inline keyboards. (#76238) Thanks @keshavbotagent.
- Cron/sessions: keep cron metadata rows without an on-disk transcript non-resumable until a transcript exists, so doctor and `sessions cleanup --fix-missing` no longer report or prune pre-transcript cron rows as broken sessions. Refs #77011.
- Agents/cli-runner: drop a saved `claude-cli` resume sessionId at preparation time when its on-disk transcript no longer exists in `~/.claude/projects/`, so a stale binding from a half-installed `update.run` cannot trap follow-up runs (auto-reply / Telegram direct) in a `claude --resume` timeout loop; the run starts fresh and the new sessionId is written back through the existing post-run flow. (#77030; refs #77011) Thanks @openperf.
- Release validation: install the cross-OS TypeScript harness through Windows-safe Node/npm shims so native Windows package checks reach the OpenClaw smoke suites instead of exiting before artifact capture. Thanks @vincentkoc.
- Release validation: let Windows packaged-upgrade checks continue after the shipped 2026.5.2 updater hits its native-module swap cleanup fallback, verifying the fallback-installed candidate through package metadata and downstream smoke instead of crashing on the immediate update-status probe. Thanks @vincentkoc.
- Doctor/plugins: skip channel-derived official plugin installs when another configured plugin is the effective owner for the same channel, so `doctor --repair` does not reinstall `feishu` while `openclaw-lark` handles `channels.feishu`. Fixes #76623. Thanks @fuyizheng3120.
- Gateway/sessions: memoize repeated thinking-option enrichment and skip unused cost fallback checks while listing sessions, reducing per-row work on large multi-agent stores. Fixes #76931.
- Gateway/sessions: bound default `sessions.list` RPC responses and report truncation metadata, preventing Slack-heavy long-lived stores from forcing unbounded Gateway row construction. Fixes #77062.
- Telegram: deliver button-only interactive replies by sending the shared fallback button-label text with the inline keyboard instead of dropping the reply as empty. Thanks @vincentkoc.
- Telegram: keep status checks pointed at the active chat so asking for the current session no longer reports an old direct-message conversation. (#76708) Thanks @amknight.
- Media/Telegram: send in-limit original images when optional image optimization is unavailable, so Telegram MEDIA replies and message-tool image sends do not fail just because `sharp` is missing. Fixes #77081. (#77117) Thanks @pfrederiksen.
- Discord/replies: treat failed final reply delivery as a failed turn instead of counting it as a delivered automatic visible reply, so guild/channel turns no longer show done when the final message was dropped. Fixes #77520. Thanks @Patrick-Erichsen.
- Discord: prefer IPv4 for Discord REST and gateway WebSocket startup paths so IPv4-only networks no longer stall before Gateway READY and inbound message dispatch. Fixes #77398; refs #77526. Thanks @Beandon13.
- Discord: clear stale startup probe bot/application status when the async bot probe throws, not just when it returns a degraded probe result. Thanks @vincentkoc.
- Discord: start the gateway monitor without waiting for the startup bot/application probe, so WSL2 hosts with a slow `/users/@me` REST path still bring the channel online while status enrichment finishes asynchronously. Fixes #77103. Thanks @Suited78.
- Discord/Gateway startup: retry Discord READY waits with backoff, defer startup `sessions.list` and native approval readiness failures until sidecars recover, and preserve component-only Discord payloads when final reply scrubbing removes all text. (#77478) Thanks @NikolaFC.
- Webhooks/Gmail/Windows: resolve `gcloud`, `gog`, and `tailscale` PATH/PATHEXT shims before setup and watcher spawns, using the Windows-safe `.cmd` wrapper for long-lived `gog serve` processes. (#74881, fixes #54470) Thanks @Angfr95.
- Infra/Windows: skip the POSIX `/tmp/openclaw` preferred path on Windows in `resolvePreferredOpenClawTmpDir` so log files, TTS temp files, and other writes land in `%TEMP%\openclaw-<uid>` instead of `C:\tmp\openclaw`. Fixes #60713. Thanks @juan-flores077.
- Media/Windows: open saved attachment temp files read/write before fsync so Windows WebChat and `chat.send` media offloads no longer fail with EPERM during durability flush. (#76593) Thanks @qq230849622-a11y.
- Plugins/Windows: show a Git install hint when npm plugin installation fails with `spawn git ENOENT`, and document the WhatsApp plugin's Git-on-PATH requirement for Baileys/libsignal installs.
- Media/images: keep HEIC/HEIF attachments fail-closed when optional Sharp conversion is unavailable instead of sending originals that still need conversion. Thanks @vincentkoc.
- Control UI/chat: suppress `HEARTBEAT_OK` acknowledgement history, streams, deltas, and final events before they enter the transcript view, so repeated heartbeat no-op turns do not stack noisy bubbles. Thanks @BunsDev.
- Control UI/Talk: make failed Talk startup errors dismissable and clear the stale Talk error state when dismissed, so missing realtime voice provider configuration does not leave a permanent chat banner. Fixes #77071. Thanks @ijoshdavis.
- Control UI/Talk: stop and clear failed realtime Talk sessions when dismissing runtime error banners, so the next Talk click starts a fresh session instead of only stopping the stale one. Thanks @vincentkoc.
- Control UI/Talk: retry from a failed realtime Talk session on the next Talk click instead of requiring a separate stale-session stop click first. Thanks @vincentkoc.
- Control UI/media: mint short-lived scoped tickets for assistant media fetches and render ticketed URLs instead of exposing long-lived auth tokens in chat image URLs. Fixes #70830 and #77097. Thanks @hclsys.
- Control UI: keep Gateway Access inputs and locale picker contained inside the card at narrow and tablet widths.
- Control UI: add explicit feedback for repeated actions by announcing session switches, flashing the active session selector, showing inline Save/Apply/Update progress, and distinguishing filtered-empty session lists from genuinely empty session stores. Thanks @BunsDev.
- Control UI: point the Appearance tweakcn browse action and docs at the live tweakcn editor route instead of the removed `/themes` page. Fixes #77048.
- Control UI: render Dream Diary prose through the sanitized markdown pipeline, so diary bold/italic/header markdown no longer appears as literal source text. Fixes #62413.
- Control UI: render tool results whose output arrives as text-block arrays and give expanded tool output a scrollable block, so read/exec output remains visible in WebChat. Fixes #77054.
- UI/chat: remove the unsupported `line-clamp` declaration from the chat queue text rule to eliminate Firefox console noise without changing visible truncation behavior. Thanks @ZanderH-code.
- TUI/escape abort: track the in-flight runId after `chat.send` resolves so pressing Esc during the gap before the first gateway event aborts the run instead of repeatedly printing `no active run`. Fixes #1296. Thanks @Lukavyi and @romneyda.
- TUI/render: stop the long-token sanitizer from injecting literal spaces inside inline code spans, fenced code blocks, table borders, and bare hyphenated/dotted identifiers, so copied package names, entity IDs, and shell line-continuations stay byte-for-byte intact while narrow-terminal protection still chunks unidentifiable long prose tokens. Fixes #48432, #39505. Thanks @DocOellerson, @xeusoc, @CCcassiusdjs, @akramcodez, @brokemac79, @romneyda.
- iOS/mobile pairing: reject non-loopback `ws://` setup URLs before QR/setup-code issuance and let the iOS Gateway settings screen scan QR codes or paste full setup-code messages. Thanks @BunsDev.
- Canvas host: preserve the Gateway TLS scheme in browser canvas host URLs and startup mount logs, so direct HTTPS gateways do not advertise insecure canvas links. Thanks @vincentkoc.
- Model switching: include the exact additive allowlist repair command when `/model ... --runtime ...` targets a blocked model, and make Telegram's model picker say that it changes only the session model while leaving the runtime unchanged. Thanks @vincentkoc.
- Mattermost: clarify that the model picker only changes the session model and that runtime switches require `/oc_model <provider/model> --runtime <runtime>`. Thanks @vincentkoc.
- Mattermost: use the shared progress draft formatter for tool status previews, including raw command/detail output when `agents.defaults.toolProgressDetail: "raw"` is enabled. Thanks @vincentkoc.
- Mattermost: suppress standalone default tool-progress messages while draft previews are active, including when draft tool lines are disabled. Thanks @vincentkoc.
- Discord/Slack/Mattermost: align draft preview tool-progress config help with the runtime behavior that hides interim tool updates when `streaming.preview.toolProgress` is false. Thanks @vincentkoc.
- Google Chat: create an isolated Google auth transport per auth client, so google-auth-library interceptor mutations do not accumulate across webhook verification and access-token clients. Thanks @vincentkoc.
- Google Chat: normalize Google auth certificate response headers before google-auth-library reads cache-control, so inbound webhook auth no longer rejects with `res?.headers.get is not a function`. Fixes #76880. Thanks @donbowman.
- Providers/DeepSeek: expose DeepSeek V4 `xhigh` and `max` thinking levels through the lightweight provider-policy surface, so Control UI `/think` pickers keep showing the max reasoning options when the runtime plugin registry is not active. Fixes #77139. Thanks @bittoby.
- Providers/OpenRouter: keep DeepSeek V4 `reasoning_effort` on OpenRouter-supported values, mapping stale `max` thinking overrides to `xhigh` so `openrouter/deepseek/deepseek-v4-pro` no longer fails with OpenRouter's invalid-effort 400. Fixes #77350. (#77423) Thanks @krllagent, @mushuiyu886, and @sallyom.
- Providers/OpenAI Codex: stop the OAuth progress spinner before showing the manual redirect paste prompt, so callback timeouts do not spam `Browser callback did not finish` across terminals.
- Providers/OpenAI Codex: fail closed on malformed `/codex` control commands and diagnostics confirmations before changing bindings, permissions, model overrides, active turns, or feedback uploads. Thanks @vincentkoc.
- Providers/OpenAI Codex: sanitize Codex app-server command readouts, failure replies, approval prompts, elicitation prompts, and `request_user_input` text before posting them back into chat. Thanks @vincentkoc.
- Providers/OpenAI Codex: preserve local bound-turn image paths, reject stale same-thread turn notifications, enforce option-only user input prompts, and return failed dynamic tool results to Codex as unsuccessful tool calls. Thanks @vincentkoc.
- OpenAI Codex: recreate missing bound app-server threads once when a stale `/codex bind` sidecar survives a restart, preserving the selected auth profile and turn overrides before retrying the inbound turn. (#76936) Thanks @keshavbotagent.
- OpenAI Codex: honor `auth.order.openai-codex` when starting app-server clients without an explicit auth profile, so status/model probes and implicit startup use the configured Codex account instead of falling back to the default profile. Thanks @vincentkoc.
- OpenAI Codex: let SSRF-guarded provider requests inherit OpenClaw's undici IPv4/IPv6 fallback policy, so ChatGPT-backed Codex runs recover on IPv4-working hosts when DNS still returns unreachable IPv6 addresses. Fixes #76857. Thanks @jplavoiemtl and @SymbolStar.
- Auth/OpenAI Codex: rewrite invalidated per-agent Codex auth-order and session profile overrides toward a healthy relogin profile, so revoked OAuth accounts do not stay pinned after signing in again. Thanks @BunsDev.
- Plugins/Codex: preserve Codex-native OAuth routing for `/codex bind` app-server turns so bound sessions keep the selected Codex auth profile instead of falling back to public OpenAI credentials. (#76714) Thanks @keshavbotagent.
- Codex harness: preserve app-server usage-limit reset details and deliver OpenClaw-owned runtime failure notices through tool-only source-reply mode, so Telegram and other chat channels tell users when Codex subscription limits or API failures block a turn instead of going silent. (#77557) Thanks @pashpashpash.
- Codex harness: keep `codex_app_server.*` telemetry publication owned by the harness instead of republishing the same callback event from core runners. Thanks @vincentkoc.
- Codex plugin: mirror the experimental upstream app-server protocol and format generated TypeScript before drift checks, keeping OpenClaw's `experimentalApi` bridge compatible with latest Codex while preserving formatter gates.
- Agents/OpenAI: default direct OpenAI Responses models to the SSE transport instead of WebSocket auto-selection, preventing pi runtime chat turns from hanging on servers where the WebSocket path stalls while the OpenAI HTTP stream works. Thanks @vincentkoc.
- Claude CLI: honor non-off `/think` levels by passing Claude Code's session-scoped `--effort` flag through the CLI backend seam, so chat bridges no longer show an inert thinking control. Fixes #77303. Thanks @Petr1t.
- Browser/SSRF: enforce the existing current-tab URL navigation policy before tab-scoped debug, export, and read routes (console, page errors, network requests, trace start/stop, response body, screenshot, snapshot, storage, etc.) collect from an already-selected tab, so blocked tabs return a policy error instead of being read first and redacted only at response time. (#75731) Thanks @eleqtrizit.
- Browser: enforce strict SSRF current-URL checks before existing-session screenshots, matching existing-session snapshot handling. Thanks @vincentkoc.
- fix(gateway): clamp unbound websocket auth scopes [AI]. (#77413) Thanks @pgondhi987.
- fix(device-pair): require pairing scope for pair command [AI]. (#76377) Thanks @pgondhi987.
- fix: harden backend message action gateway routing [AI]. (#76374) Thanks @pgondhi987.
- Gate QQBot streaming command auth [AI]. (#76375) Thanks @pgondhi987.
- fix(qqbot): keep private commands off framework surface [AI]. (#77212) Thanks @pgondhi987.
- Gate zalouser startup name matching [AI]. (#77411) Thanks @pgondhi987.
- QQBot: preserve the framework command authorization decision when converting framework command contexts into engine slash command contexts, so downstream slash handlers see `commandAuthorized` matching the channel's resolved `isAuthorizedSender` instead of a hardcoded `true`. (#77453) Thanks @drobison00.
- Agents/cache: keep per-turn runtime context out of ordinary chat system prompts while still delivering hidden current-turn context, restoring prompt-cache reuse on chat continuations. Fixes #77431. Thanks @Udjin79.
- Agents/tools: honor narrow runtime tool allowlists when constructing embedded-runner tool families and bundled MCP/LSP runtimes, so cron/subagent runs that request tools such as `update_plan`, `browser`, `x_search`, channel login tools, or `group:plugins` no longer start with missing tools or unrelated bootstrap work. (#77519, #77532)
- Agents/Tools: add post-compaction loop guard in `pi-embedded-runner` that arms after auto-compaction-retry and aborts the run with `compaction_loop_persisted` when the agent emits the same `(tool, args, result)` triple `windowSize` times (default 3) within that window. Disable via existing `tools.loopDetection.enabled`; tune via `tools.loopDetection.postCompactionGuard.windowSize`. Targets the failure mode where context-overflow + compaction does not break a tool-call loop. Refs #77474; carries forward #21597. Thanks @efpiva.
- Agents/tools: strip reasoning text from visible rich presentation titles, blocks, buttons, and select labels before message-tool sends, so structured channel payloads cannot leak hidden planning. Thanks @vincentkoc.
- Agents/tools: use config-only runtime snapshots for plugin tool registration and live runtime config getters, avoiding expensive full secrets snapshot clones on the core-plugin-tools prep path. Fixes #76295.
- Agents/tools: honor the effective tool denylist before constructing optional PDF/media tool factories, so `tools.deny: ["pdf"]` skips PDF setup before later policy filtering. Fixes #76997.
- Agents/skills: require exact `<location>` skill paths for both single-skill and multi-skill prompt selection, so agents do not guess or hard-code skill file paths. (#74161) Thanks @lanzhi-lee.
- Agents/skills: rebuild sandboxed non-rw run skill prompts from the sandbox workspace copy, so `<available_skills>` no longer points at host-only `~/.openclaw/skills` paths. Fixes #50590. Thanks @kidroca and @sallyom.
- Agents/media: avoid sending generated image, video, and music attachments twice when streamed reply text arrives before the final `MEDIA:` directive.
- Agents/media: tell async music and video completion agents when normal final replies are private, and send completion fallbacks directly to message-tool-only group/channel routes when the completion agent still only writes a private final reply, so generated media does not disappear behind the delivery contract.
- Agents/media: route async music and video completion results back through the requester agent, preserving automatic replies while requiring the message tool only for message-tool-only group/channel delivery.
- Agents/subagents: refresh deferred final-delivery payloads when same-session completion output changes, so retried parent notifications use the final child summary instead of stale progress text. Thanks @vincentkoc.
- Agents/subagents: detect prefix-only completion announce replies and fall back to the captured child result so requester chats no longer lose most of long sub-agent reports silently. Fixes #76412. Thanks @inxaos and @davemorin.
- Active Memory: give timeout partial transcript recovery enough abort-settle headroom so temporary recall summaries are returned before cleanup. Thanks @vincentkoc.
- Active Memory: send a bounded latest-message search query to the recall worker so channel/runtime metadata does not become the memory search string. Fixes #65309. Thanks @joeykrug, @westley3601, @pimenov, and @tasi333.
- active-memory: skip the memory sub-agent gracefully instead of logging a confusing allowlist error when no memory plugin (`memory-core` or `memory-lancedb`) is loaded, so active-memory with no memory backend no longer produces misleading "No callable tools remain" warnings in the gateway log. Fixes #77506. Thanks @hclsys.
- Memory/wiki: preserve representation from both corpora in `corpus=all` searches while backfilling unused result capacity, so memory hits are not starved by numerically higher wiki integer scores. Fixes #77337. Thanks @hclsys.
- Plugin skills: publish plugin-declared skills through the generated plugin skills directory (`~/.openclaw/plugin-skills/`) while keeping direct prompt loading intact, so agent file-based discovery paths find plugin skill `SKILL.md` files and inactive plugin links are cleaned up. Fixes #77296. (#77328) Thanks @zhangguiping-xydt.
- Plugins/install: honor the beta update channel for onboarding and doctor-managed plugin installs by requesting floating npm and ClawHub specs with `@beta` while keeping persistent install records on the catalog default. Thanks @vincentkoc.
- Plugins/install: remove the previous managed plugin directory when a reinstall switches sources, so stale ClawHub and npm copies no longer keep duplicate plugin ids in discovery after the new install wins. Thanks @vincentkoc.
- Plugins/install: let official plugin reinstall recovery repair source-only installed runtime shadows, so `openclaw plugins install npm:@openclaw/discord --force` can replace the bad package instead of stopping at stale config validation. Thanks @vincentkoc.
- Plugins/install: suppress dangerous-pattern scanner warnings for trusted official OpenClaw npm installs, so installing `@openclaw/discord` no longer prints credential-harvesting warnings for the official package. Thanks @vincentkoc.
- Plugins/update: repair missing plugin-local `openclaw` peer links before skipping unchanged npm plugin updates, so current external Codex installs can recover `openclaw/plugin-sdk/*` resolution during OTA repair. (#77544) Thanks @ProspectOre.
- Plugins/update: treat OpenClaw CalVer correction versions like `2026.5.3-1` as satisfying base plugin API ranges, so correction builds can install plugins that require the base runtime API. Fixes #77293. (#77450) Thanks @p3nchan.
- Plugins/update: treat OpenClaw stable correction versions like `2026.5.3-1` as stable releases for npm installs, plugin updates, and bundled-version comparisons, so `latest` can advance official plugins without prerelease opt-in. Thanks @vincentkoc.
- Plugins/commands: allow the official ClawHub Codex plugin package to keep reserved `/codex` command ownership, matching the existing npm-managed Codex package behavior. Thanks @vincentkoc.
- Plugins/commands: scope QQBot framework slash commands to the QQBot channel so `/bot-*` command handlers and native specs do not leak onto unrelated chat surfaces. Thanks @vincentkoc.
- Plugins/commands: suppress dangerous-pattern scanner warnings for trusted catalog npm installs from owner-gated `/plugins install` commands, so chat-driven installs match the CLI install trust path. Thanks @vincentkoc.
- Plugins/discovery: ignore managed npm plugin packages that only expose TypeScript source entries without compiled runtime output, so stale/broken installs cannot hide a working bundled or reinstallable channel plugin during setup. Thanks @vincentkoc.
- Plugins/discovery: demote the source-only TypeScript runtime check on already-installed `origin: "global"` plugin packages from a config-blocking error to a warning and let the runtime fall through to the TypeScript source via jiti, so a single broken installed package no longer blocks `plugins install` for unrelated plugins; install-time rejection of newly-installed source-only packages is unchanged. Thanks @romneyda.
- Plugins/registry: recover managed-npm external plugins from the owned npm root when a stale persisted registry would otherwise hide them after package-manager upgrades. Fixes #77266. Thanks @p3nchan.
- Plugins/providers: make bundled provider discovery honor restrictive `plugins.allow` by default for new configs, while doctor migrates legacy restrictive allowlist configs to `plugins.bundledDiscovery: "compat"` to preserve upgrade behavior. Thanks @dougbtv.
- Plugins/security: ignore inline and block comments when matching source-rule context in plugin install scans, so comment-only `fetch`/`post` references near environment defaults do not block clean plugins. Thanks @vincentkoc.
- Plugins/packages: reject inferred built runtime entries that exist but fail package-boundary checks instead of falling back to TypeScript source for installed packages. Thanks @vincentkoc.
- Plugins/packages: reject blank `openclaw.runtimeExtensions` entries instead of silently ignoring them and falling back to inferred TypeScript runtime entries. Thanks @vincentkoc.
- Plugins/loader: do not retry native-loaded JavaScript plugin modules through the source transformer after native evaluation has already reached a missing dependency, avoiding duplicate top-level side effects. Thanks @vincentkoc.
- Plugins/loader: keep bundled plugin package `test-api.js` aliases behind private QA mode, so source transforms do not expose test-only public surfaces during normal plugin loading. Thanks @vincentkoc.
- Plugins/runtime-deps: include `json5` in the memory-core plugin runtime dependency set so packaged `memory_search` sandboxes can resolve generated OpenClaw runtime chunks that parse JSON5 config. Fixes #77461.
- Plugins/runtime state: keep the key being registered when namespace eviction runs in the same millisecond as existing entries, so `register` and `registerIfAbsent` do not report success while evicting their own fresh value. Thanks @vincentkoc.
- Plugins/release: make the published npm runtime verifier reject blank `openclaw.runtimeExtensions` entries instead of treating them as absent and passing via inferred outputs. Thanks @vincentkoc.
- Doctor/config: keep active `auth.profiles` metadata intact when `doctor --fix` strips stale secret fields from configs, repairing legacy `<provider>:default` API-key profile metadata when model fallbacks or explicit `model@profile` refs still depend on it. Fixes #77400.
- Doctor/config: restore legacy group chat config migrations for `routing.allowFrom`, `routing.groupChat.*`, and `channels.telegram.requireMention` so upgrades keep WhatsApp, Telegram, and iMessage group mention gates and history settings instead of leaving configs invalid or silently blocked. Thanks @scoootscooob.
- Doctor/plugins: include `plugins.allow`-only official plugin ids in the release configured-plugin repair set, so `doctor --fix` installs official external plugins that are configured but not yet loaded instead of removing them as stale allow entries. Fixes #77155. Thanks @hclsys.
- Doctor/plugins: remove stale managed install records for bundled plugins even when the bundled plugin is not explicitly configured, so doctor cleanup cannot leave orphaned install metadata behind. Thanks @vincentkoc.
- Doctor/plugins: remove stale managed npm plugin shadow entries from the managed package lock as well as `package.json` and `node_modules`, so future npm operations do not keep referencing repaired bundled-plugin shadows. Thanks @vincentkoc.
- Doctor/plugins: remove orphaned or recovered managed npm copies of bundled `@openclaw/*` plugins during `doctor --fix`, so stale package manifests cannot shadow the current bundled plugin config schema.
- Doctor/plugins: skip channel-derived official plugin installs when another configured plugin is the effective owner for the same channel, so `doctor --repair` does not reinstall `feishu` while `openclaw-lark` handles `channels.feishu`. Fixes #76623. Thanks @fuyizheng3120.
- Doctor/plugins: do not treat `plugins.allow` entries as configured plugins during missing-plugin repair, so restrictive allowlists no longer install allowed-but-unused plugins. Thanks @vincentkoc.
- Doctor/sessions: clear auto-created stale session routing state from the sessions store when `doctor --fix` sees plugin-owned model/runtime/auth/session bindings outside the current configured route, while leaving explicit user model choices for manual review. Refs #68615.
- CLI/sessions: prune old unreferenced transcript, compaction checkpoint, and trajectory artifacts during normal `sessions cleanup`, so gateway restart or crash orphans do not accumulate indefinitely outside `sessions.json`. Fixes #77608. Thanks @slideshow-dingo.
- CLI/sessions: cap `openclaw sessions` output to the newest 100 rows by default and add `--limit <n|all>` plus JSON pagination metadata, so repeated machine polling of large session stores cannot fan out into unbounded per-row enrichment/output work. Fixes #77500. Thanks @Kaotic3.
- CLI/update: disable and skip plugins that fail package-update plugin sync, so a broken npm/ClawHub/git/marketplace plugin cannot turn a successful OpenClaw package update into a failed update result. Thanks @vincentkoc.
- CLI/update: use an absolute POSIX npm script shell during package-manager updates, so restricted PATH environments can still run dependency lifecycle scripts while updating from `--tag main`. Fixes #77530. Thanks @PeterTremonti.
- CLI/update: make package-update follow-up processes write completion results and exit explicitly, so Windows packaged upgrades do not hang after the new package finishes post-core plugin work. Thanks @vincentkoc.
- CLI/update: stage pnpm-detected npm-layout global package updates through a clean npm prefix swap, keep plugin install runtime imports behind a stable alias, and ship legacy install-runtime aliases back to `2026.3.22`, preventing stale overlay chunks from breaking plugin post-update sync. Thanks @vincentkoc.
- CLI/update: treat OpenClaw stable correction versions like `2026.5.3-1` as newer than their base stable release, so package updates no longer ask for downgrade confirmation. Thanks @vincentkoc.
- CLI/launcher: forward termination signals to compile-cache respawn children, so killing a wrapper process no longer leaves the security audit worker orphaned. Fixes #77458. Thanks @jaikharbanda.
- Update/restart: probe managed Gateway restarts with the service environment and add a Docker product lane that exercises candidate-owned `openclaw update --yes --json` restarts, so SecretRef-backed local gateway auth cannot regress behind mocked restart checks. Thanks @vincentkoc.
- Gateway/startup: load provider plugins that own explicitly configured image, video, or music generation defaults so generation tools become live after gateway restart instead of remaining catalog-only. Fixes #77244. Thanks @buyuangtampan, @Nikoxx99, and @vincentkoc.
- Gateway/startup: include resolved thinking and fast-mode defaults in the `agent model` startup log line, defaulting unset startup thinking to `medium` without mixing in reasoning visibility.
- Gateway/startup: log the canvas host mount only after the HTTP server has bound, so startup logs no longer report the canvas host as mounted before it can serve requests.
- Gateway/startup: start cron and record the post-ready memory trace even when deferred maintenance timers fail after readiness, so a non-fatal timer setup issue does not silently leave scheduled jobs idle. Thanks @vincentkoc.
- Gateway/update: resolve local gateway probe auth from the installed config during post-update restart verification, so token/device-authenticated VPS gateways are not misreported as unhealthy port conflicts after a package swap. Thanks @vincentkoc.
- Gateway/update: keep the shutdown close path behind a stable runtime chunk and ship compatibility aliases for recent `server-close-*` hashes, so manual npm package replacement cannot leave an already-running Gateway unable to shut down cleanly. Fixes #77087. Thanks @westlife219.
- Gateway/chat: clear the active reply-run guard before draining queued same-session follow-up turns, so sequential `chat.send` calls no longer trip `ReplyRunAlreadyActiveError` every other request. Fixes #77485. Thanks @bws14email.
- Gateway/status: label Linux managed gateway services as `systemd user`, making status output explicit about the user-service scope instead of implying a system-level unit. Thanks @vincentkoc.
- Gateway/sessions: memoize repeated thinking-option enrichment and skip unused cost fallback checks while listing sessions, reducing per-row work on large multi-agent stores. Fixes #76931.
- Gateway/sessions: bound default `sessions.list` RPC responses and report truncation metadata, preventing Slack-heavy long-lived stores from forcing unbounded Gateway row construction. Fixes #77062.
- Gateway/sessions: cache selected model override resolution while building session-list rows so `openclaw sessions` and Control UI session lists stay responsive on model-heavy stores. (#77650) Thanks @ragesaq.
- Gateway/watch: suppress sync-I/O trace output during `pnpm gateway:watch --benchmark` unless explicitly requested, so CPU profiling no longer floods the terminal with stack traces.
- Gateway/watch: when benchmark sync-I/O tracing is explicitly enabled, tee trace blocks to the benchmark output log and filter them from the terminal pane while keeping normal Gateway logs visible.
- Gateway/diagnostics: make stuck-session recovery outcome-driven and generation-guarded, add `diagnostics.stuckSessionAbortMs`, and emit structured recovery requested/completed events so stale or skipped recovery no longer looks like a successful abort.
- Gateway/validation: isolate gateway server validation files, ignore unrelated startup logs in request-trace coverage, and fail fast on stuck shared-auth sockets, reducing false main-branch CI failures for contributors. Thanks @amknight.
- Gateway/install: keep `.env`-managed values in the macOS LaunchAgent env file while still tracking `OPENCLAW_SERVICE_MANAGED_ENV_KEYS`, so regenerated services do not boot without managed auth/provider keys. Fixes #75374.
- Gateway/restart: verify listener PIDs by argv when `lsof` reports only the Node process name, so stale gateway cleanup can find macOS `cnode` listeners. Fixes #70664.
- Gateway/logging: expand leading `~` in `logging.file` before creating the file logger, preventing startup crash loops for home-relative log paths. Fixes #73587.
- Gateway/install: prefer supported system Node over nvm/fnm/volta/asdf/mise when regenerating managed gateway services, so `gateway install --force` no longer recreates service definitions that doctor immediately flags as version-manager-backed. Fixes #76339. Thanks @brokemac79 and @BunsDev.
- Cron: surface failed isolated-run diagnostics in `cron show`, status, and run history when requested tools are unavailable, so blocked cron runs report the actual tool-policy failure instead of a misleading green result. Fixes #75763. Thanks @RyanSandoval.
- Cron/sessions: keep cron metadata rows without an on-disk transcript non-resumable until a transcript exists, so doctor and `sessions cleanup --fix-missing` no longer report or prune pre-transcript cron rows as broken sessions. Refs #77011.
- Docker/compose: pin container-side `OPENCLAW_CONFIG_DIR` and `OPENCLAW_WORKSPACE_DIR` on both gateway and CLI services so the host paths written into `.env` by `scripts/docker/setup.sh` (used as Compose bind-mount sources) cannot leak into runtime code via the `env_file` import. Fixes regressions on macOS Docker setups where the first agent reply died with `EACCES: permission denied, mkdir '/Users'` because the host-style workspace path got persisted into `agents.defaults.workspace`. Fixes #77436. Thanks @lonexreb.
- Docker: prune package-excluded plugin dist directories from runtime images unless the build explicitly opts that plugin in, so official external plugins such as Feishu stay install-on-demand instead of shipping partial metadata without compiled runtime output. Fixes #77424. Thanks @vincentkoc.
- Web search: honor late-bound `tools.web.search.enabled: false` during tool execution so config reloads cannot leave an already-created `web_search` tool runnable. Thanks @vincentkoc.
- Web search: scope explicit bundled `web_search` provider runtime loading through manifest ownership, so selecting DuckDuckGo/Gemini/etc. does not import unrelated bundled providers or log their optional dependency failures. Thanks @vincentkoc.
- Web search: keep first-class assistant `web_search` auto-detect and configured runtime providers visible when active runtime metadata or the active plugin registry is incomplete. Fixes #77073. Thanks @joeykrug.
- Web fetch: scope provider fallback cache entries by the selected fetch provider so config reloads cannot reuse another provider's cached fallback payload. Thanks @vincentkoc.
- Web fetch: late-bind `web_fetch` config and provider fallback metadata from the active runtime snapshot, matching `web_search` so long-lived tools do not use stale fetch provider settings. Thanks @vincentkoc.
- Diagnostics: grant the internal diagnostics event bus to official installed diagnostics exporter plugins, so npm-installed `@openclaw/diagnostics-prometheus` can emit metrics without broadening the capability to arbitrary global plugins. Fixes #76628. Thanks @RayWoo.
- Diagnostics: handle missing session-tail files in cron recovery context without tripping extension test typecheck. Thanks @vincentkoc.
- Diagnostics: include last progress, cron job/run ids, stopped cron job name, and the last assistant transcript snippet in stalled-session and stuck-session recovery logs so cron stalls show what was stopped.
- Diagnostics: keep webhook/message OTEL attributes and Prometheus delivery labels low-cardinality and omit raw chat/message IDs from spans, so progress-draft and message-tool modes do not leak high-cardinality messaging identifiers.
- Exec approvals: detect `env -S` split-string command-carrier risks when `-S`/`-s` is combined with other env short options, so approval explanations do not miss split payloads hidden behind `env -iS...`. Thanks @vincentkoc.
- Exec approvals: treat POSIX `exec` as a command carrier for inline eval, shell-wrapper, and eval/source detection, so approval explanations and command-risk checks do not miss payloads hidden behind `exec`. Thanks @vincentkoc.
- Exec approvals: unwrap BSD/macOS `env -P <path>` carrier commands before approval-command and strict inline-eval checks, so `/approve` shell execution and inline interpreter payloads are still blocked behind that env form.
- Agents/session status: keep semantic `session_status({ sessionKey: "current" })` on the live run session even before that run has a persisted session-store entry, instead of falling back to the sandbox policy key. Thanks @vincentkoc.
- Agents/trajectory: bound runtime trajectory capture and yield queued sidecar writes so oversized traces stop recording instead of monopolizing Gateway cleanup. Fixes #77124. Thanks @loyur.
- Agents/Pi: suppress persistence for synthetic mid-turn overflow continuation prompts, so transcript-retry recovery does not write the "continue from transcript" prompt as a new user turn. Thanks @vincentkoc.
- Release validation: skip Slack live QA unless Slack credentials are explicitly configured, so release gates can keep proving non-Slack surfaces while Slack is still local and credential-gated. Thanks @vincentkoc.
- Release validation: allow focused QA live reruns to select Matrix and Telegram without running Slack, so known Slack credential-pool outages do not block non-Slack live proof. Thanks @vincentkoc.
- Release validation: install the cross-OS TypeScript harness through Windows-safe Node/npm shims so native Windows package checks reach the OpenClaw smoke suites instead of exiting before artifact capture. Thanks @vincentkoc.
- Release validation: let Windows packaged-upgrade checks continue after the shipped 2026.5.2 updater hits its native-module swap cleanup fallback, verifying the fallback-installed candidate through package metadata and downstream smoke instead of crashing on the immediate update-status probe. Thanks @vincentkoc.
- Release/beta smoke: resolve the dispatched Telegram beta E2E run from `gh run list` when `gh workflow run` returns no run URL, so the maintainer helper does not fail immediately after dispatch. Thanks @vincentkoc.
- QA/Slack: update the Slack dispatch preview fallback test SDK mock for structured progress draft helpers, so the rich progress draft regression suite covers the new imports instead of failing before assertions run. Thanks @vincentkoc.
- QA/Slack: resolve bundled official plugin public-surface package aliases during source-mode QA runs, so release Slack live validation can load `@openclaw/slack/api.js` without workspace symlinks. Thanks @vincentkoc.
- QA/Matrix: let the live tool-progress preview and error checks verify progress replacement events without depending on the preview saying `Working`, `tool: read`, an unlabelled/pathless `read from`, or the original draft root being observed. Thanks @vincentkoc.
- QA/Matrix: keep the target=both approval scenario focused on channel and DM metadata delivery by resolving the accepted approval through the gateway after both Matrix events are observed. Thanks @vincentkoc.
- QA/Matrix: wait for live approval reactions to echo before starting the threaded approval decision timeout. Thanks @vincentkoc.
- QA/Matrix: reuse the primed driver sync stream when confirming approval reaction echoes, avoiding missed self-reactions in live release runs. Thanks @vincentkoc.
- Channels/plugins: key bundled package-state probes, env/config presence, and read-only command defaults by channel id instead of manifest plugin id, preserving setup and native-command detection for channel plugins whose package id differs from the channel alias. Thanks @vincentkoc.
- Control UI/performance: cap long-task and long-animation-frame diagnostics in the shared event log, so slow-render telemetry does not evict gateway/plugin events from the Debug and Overview views. Thanks @vincentkoc.
- Control UI/i18n: render the Sessions active filter tooltip with the configured minute count in every locale and make the i18n check reject placeholder drift. Thanks @BunsDev.
- Codex: pass the live run session key into app-server dynamic tools when sandbox policy uses a separate session key, so `session_status({ sessionKey: "current" })` reports the active run instead of the sandbox policy key. Thanks @vincentkoc.
- Plugins/tools: mark manifest-optional sibling tools as optional even when they come from a shared non-optional factory, so cached/status/MCP metadata keeps opt-in tool policy accurate. Thanks @vincentkoc.
- Matrix: keep `streaming.progress.toolProgress` scoped to progress draft mode, so partial and quiet Matrix previews do not lose tool progress unless `streaming.preview.toolProgress` is disabled. Thanks @vincentkoc.
- Channels/streaming: keep `streaming.progress.toolProgress` scoped to progress draft mode, so disabling compact progress lines does not silence partial/block preview tool updates. Thanks @vincentkoc.
- MCP: include serialized conversation/message payloads in the primary text content for `conversations_list` and `messages_read`, while preserving `structuredContent` for capable clients. Fixes #77024.
- Media: treat `EPERM` from the post-write media fsync step as best-effort, allowing WebChat and channel uploads to finish on Windows filesystems that reject `fsync` after a successful write. Fixes #76844.
- Streaming channels: add `streaming.preview.commandText: "status"` / `streaming.progress.commandText: "status"` to hide command/exec text in preview progress lines while keeping the released raw command text default. Fixes #77072.
- Agents/cron: let explicit cron `timeoutSeconds` drive both CLI no-output and embedded LLM idle watchdogs instead of being capped by resume defaults. Fixes #76289.
- Plugins/catalog: suppress missing `channelConfigs` compatibility diagnostics for external channel plugins that are disabled, denied, or outside a restrictive allowlist. Fixes #76095.
- Agents/cli-runner: drop a saved `claude-cli` resume sessionId at preparation time when its on-disk transcript no longer exists in `~/.claude/projects/`, so a stale binding from a half-installed `update.run` cannot trap follow-up runs (auto-reply / Telegram direct) in a `claude --resume` timeout loop; the run starts fresh and the new sessionId is written back through the existing post-run flow. (#77030; refs #77011) Thanks @openperf.
- MCP/plugin tools: apply global `tools.profile`, `tools.alsoAllow`, and `tools.deny` policy while exposing plugin tools over the standalone MCP bridge, so ACP clients do not see policy-hidden plugin tools or miss opt-in optional tools. Thanks @vincentkoc.
- Plugin tools: honor explicit tool denylists while selecting plugin tool runtimes, so denied plugin tools are not materialized for direct command or gateway surfaces before later policy filtering. Thanks @vincentkoc.
- Plugin tools: filter factory-returned tools by manifest per-tool optional policy, so optional sibling tools from a shared runtime factory stay hidden unless explicitly allowed. Thanks @vincentkoc.
- Agents/transcripts: retry context-overflow compaction from the current transcript only after the inbound user turn was actually persisted, and keep WebChat agent-run live delivery from writing duplicate Pi-managed assistant turns. Fixes #76424. (#77033)
- Agents/bootstrap: keep pending `BOOTSTRAP.md` and bootstrap truncation notices in system-prompt Project Context instead of copying setup text or raw warning diagnostics into WebChat user/runtime context. Fixes #76946.
- Gateway/install: keep `.env`-managed values in the macOS LaunchAgent env file while still tracking `OPENCLAW_SERVICE_MANAGED_ENV_KEYS`, so regenerated services do not boot without managed auth/provider keys. Fixes #75374.
- Gateway/restart: verify listener PIDs by argv when `lsof` reports only the Node process name, so stale gateway cleanup can find macOS `cnode` listeners. Fixes #70664.
- Gateway/logging: expand leading `~` in `logging.file` before creating the file logger, preventing startup crash loops for home-relative log paths. Fixes #73587.
- Channels/CLI: keep `openclaw channels list --json` usable when provider usage fetching fails, and report per-provider usage errors without aborting the channel list. Refs #67595.
- Doctor/plugins: do not treat `plugins.allow` entries as configured plugins during missing-plugin repair, so restrictive allowlists no longer install allowed-but-unused plugins. Thanks @vincentkoc.
- Agents/messaging: deliver distinct final commentary after same-target `message` tool sends while still deduping text/media already sent by the tool, so short closing remarks are no longer silently dropped. Fixes #76915. Thanks @hclsys.
- Agents/messaging: preserve string thread IDs when matching message-tool reply dedupe routes, avoiding precision loss on numeric-looking topic IDs before channel plugin comparison. Thanks @vincentkoc.
- Channels/streaming: honor `agents.defaults.toolProgressDetail: "raw"` in Slack, Discord, Telegram, Matrix, and Microsoft Teams progress drafts, so tool-start lines include raw command/detail output when debugging. Thanks @vincentkoc.
- Channels/streaming: strip unmatched inline-code backticks from compacted raw progress draft lines, avoiding stray markdown markers after long command details are shortened. Thanks @vincentkoc.
- Discord/Slack/Mattermost: align draft preview tool-progress config help with the runtime behavior that hides interim tool updates when `streaming.preview.toolProgress` is false. Thanks @vincentkoc.
- Feishu: use the shared channel progress formatter for streaming-card tool status lines, including raw command/detail output and message-tool filtering. Thanks @vincentkoc.
- Mattermost: use the shared progress draft formatter for tool status previews, including raw command/detail output when `agents.defaults.toolProgressDetail: "raw"` is enabled. Thanks @vincentkoc.
- Mattermost: suppress standalone default tool-progress messages while draft previews are active, including when draft tool lines are disabled. Thanks @vincentkoc.
- Telegram: deliver button-only interactive replies by sending the shared fallback button-label text with the inline keyboard instead of dropping the reply as empty. Thanks @vincentkoc.
- OpenAI Codex: honor `auth.order.openai-codex` when starting app-server clients without an explicit auth profile, so status/model probes and implicit startup use the configured Codex account instead of falling back to the default profile. Thanks @vincentkoc.
- OpenAI Codex: let SSRF-guarded provider requests inherit OpenClaw's undici IPv4/IPv6 fallback policy, so ChatGPT-backed Codex runs recover on IPv4-working hosts when DNS still returns unreachable IPv6 addresses. Fixes #76857. Thanks @jplavoiemtl and @SymbolStar.
- Plugin updates: do not short-circuit trusted official npm updates as unchanged when the default/latest spec still resolves to an already-installed prerelease that the installer should replace with a stable fallback. Thanks @vincentkoc.
- Plugin updates: clean stale bundled load paths for already-externalized npm installs whose legacy install record only preserved the resolved package name. Thanks @vincentkoc.
- Plugin tools: keep auth-unavailable optional tools hidden even when another default tool from the same plugin is available and `tools.alsoAllow` names the optional tool. Thanks @vincentkoc.
- Realtime transcription: report socket closes before provider readiness as closed-before-ready failures instead of mislabeling them as connection timeouts for OpenAI, xAI, and Deepgram streaming transcription. Thanks @vincentkoc.
- OpenAI/Google Meet: fail realtime voice connection attempts when the socket closes before `session.updated`, avoiding stuck Meet joins waiting on a bridge that never became ready. Thanks @vincentkoc.
- Google Meet: avoid treating repeated participant words as multiple assistant-overlap matches when suppressing realtime echo transcripts. Thanks @vincentkoc.
- Google Meet: make `mode: "agent"` the default Chrome talk-back path, using realtime transcription for input and regular OpenClaw TTS for speech output, while keeping direct realtime voice answers available as `mode: "bidi"` and accepting `mode: "realtime"` as an agent-mode compatibility alias.
- Slack/Discord: suppress standalone tool-progress chatter when partial preview streaming has `streaming.preview.toolProgress: false`, matching the documented quiet-preview behavior. Thanks @vincentkoc.
- Matrix: bind native approval reaction targets before publishing option reactions, so fast approver reactions on threaded prompts are not dropped while the approval handler finishes setup. Thanks @vincentkoc.
- Google Meet: make realtime talk-back agent-driven by default with `realtime.strategy: "agent"`, keep the previous direct bidirectional model behavior available as `realtime.strategy: "bidi"`, route the Meet tab speaker output to `BlackHole 2ch` automatically for local Chrome realtime joins, coalesce nearby speech transcript fragments before consulting the agent, and avoid cutting off agent speech from server VAD or stale playback pipe errors.
- Google Meet: suppress queued assistant playback and assistant-like transcript echoes from the realtime input path, so the meeting does not hear the agent's own speech as a new user turn and loop or cut itself off.
- Google Meet: keep Chrome realtime transport tests hermetic on Linux prerelease shards while preserving the macOS-only runtime guard. Thanks @vincentkoc.
- QA/Matrix: let the live tool-progress preview and error checks verify progress replacement events without depending on the preview saying `Working`, `tool: read`, an unlabelled/pathless `read from`, or the original draft root being observed. Thanks @vincentkoc.
- QA/Matrix: keep the target=both approval scenario focused on channel and DM metadata delivery by resolving the accepted approval through the gateway after both Matrix events are observed. Thanks @vincentkoc.
- QA/Matrix: wait for live approval reactions to echo before starting the threaded approval decision timeout. Thanks @vincentkoc.
- QA/Matrix: reuse the primed driver sync stream when confirming approval reaction echoes, avoiding missed self-reactions in live release runs. Thanks @vincentkoc.
- Channels/WhatsApp: apply the shared group/channel visible-reply mode during inbound dispatch so group replies stay message-tool-only by default without overriding direct-chat harness defaults. Refs #75178 and #67394. Thanks @scoootscooob.
- Plugins/Codex: preserve Codex-native OAuth routing for `/codex bind` app-server turns so bound sessions keep the selected Codex auth profile instead of falling back to public OpenAI credentials. (#76714) Thanks @keshavbotagent.
- Telegram: keep status checks pointed at the active chat so asking for the current session no longer reports an old direct-message conversation. (#76708) Thanks @amknight.
- Gateway/install: prefer supported system Node over nvm/fnm/volta/asdf/mise when regenerating managed gateway services, so `gateway install --force` no longer recreates service definitions that doctor immediately flags as version-manager-backed. Fixes #76339. Thanks @brokemac79 and @BunsDev.
- Google Chat: normalize Google auth certificate response headers before google-auth-library reads cache-control, so inbound webhook auth no longer rejects with `res?.headers.get is not a function`. Fixes #76880. Thanks @donbowman.
- WhatsApp: route terminal login QR output through the active runtime for initial and restart sockets, so `openclaw channels login --channel whatsapp` does not lose the QR behind direct stdout writes. Fixes #76213. Thanks @dougvk.
- Proxy/debugging: disable debug proxy direct upstream forwarding for proxy requests and CONNECT tunnels while managed proxy mode is active unless `OPENCLAW_DEBUG_PROXY_ALLOW_DIRECT_CONNECT_WITH_MANAGED_PROXY=1` is explicitly set for approved local diagnostics. Thanks @jesse-merhi and @mjamiv.
- Direct APNs: route direct HTTP/2 delivery through the active managed proxy with redacted proxy diagnostics, so push requests honor configured egress controls and `openclaw proxy validate --apns-reachable` can prove APNs is reachable through the proxy before deployment. (#74905) Thanks @jesse-merhi.
- Agents/subagents: detect prefix-only completion announce replies and fall back to the captured child result so requester chats no longer lose most of long sub-agent reports silently. Fixes #76412. Thanks @inxaos and @davemorin.
- TUI: replace the stale-response watchdog notice with plain user-facing copy so stalled replies no longer surface backend or streaming internals. (#77120) Thanks @davemorin.
- Security/Windows: validate `SystemRoot`/`WINDIR` env values through the Windows install-root validator and add them to the dangerous-host-env policy when resolving `icacls.exe`/`whoami.exe` for `openclaw security audit`, so workspace `.env` overrides and bare command names cannot redirect Windows ACL helpers to attacker-controlled binaries. (#74458) Thanks @mmaps.
- Security/Windows: pin Windows registry-probe `reg.exe` resolution to the canonical Windows install root in install-root probing, so `SystemRoot`/`WINDIR` env overrides cannot redirect registry queries during Windows host detection. (#74454) Thanks @mmaps.
- Security/Windows: block `LOCALAPPDATA` from workspace `.env` and resolve Windows update-flow portable Git path prepends from the trusted process-local `LOCALAPPDATA` only, so workspace-supplied values cannot redirect `git` discovery during `openclaw update`. (#77470) Thanks @drobison00.
- Security/Windows: route the `.cmd`/`.bat` process wrapper through the shared Windows install-root resolver instead of `process.env.ComSpec`, so workspace dotenv-blocked `SystemRoot`/`WINDIR` overrides and unsafe values like UNC paths or path-lists cannot redirect `cmd.exe` selection on Windows. (#77472) Thanks @drobison00.
- Agents/bootstrap: honor `BOOTSTRAP.md` content injected by `agent:bootstrap` hooks when deciding whether bootstrap is pending, so hook-provided required setup instructions are included in the system prompt. (#77501) Thanks @ificator.
- Agents/replay-history: drop trailing assistant turns whose content is empty or carries only the stream-error sentinel before sending the transcript to the provider, so prefill-strict providers (such as github-copilot/claude-opus-4.6) no longer reject the request with `400 The conversation must end with a user message` after a session whose last turn errored before producing content. Refs #77228. (#77287) Thanks @openperf.
- Agents/session-file-repair: drop `type: "message"` entries with a missing, `null`, or blank role during the on-disk repair pass so sessions that accumulated null-role JSONL corruption (such as the 935+ corrupt entries in #77228) get fully cleaned up rather than carried forward into the repaired file. Refs #77228. (#77288) Thanks @openperf.
- Doctor/device pairing: stop suggesting `openclaw devices rotate --role <role>` for stale local cached device auth when that role is no longer approved by the gateway pairing record, so doctor no longer points users at a command that must be denied. (#77688) Thanks @Conan-Scott.
- Ollama/thinking: expose the lightweight Ollama provider thinking profile through the public provider-policy artifact too, so reasoning-capable Ollama models such as `ollama/deepseek-v4-pro:cloud` keep `/think max` available even before the full plugin runtime activates. (#77617, fixes #77612) Thanks @rriggs and @yfge.
- Codex/app-server: stabilize transcript mirror dedupe across re-mirrored turns so reordered snapshots no longer drop reasoning entries or duplicate the assistant reply. Refs #77012. (#77046) Thanks @openperf.
- Agents/auth-profiles: do not record request-shape (`format`) rejections as auth-profile health failures, so a single per-session transcript-shape error (such as a prefill-strict 400 "conversation must end with a user message") no longer triggers a profile-wide cooldown that blocks every other healthy session sharing the same auth profile. Refs #77228. (#77280) Thanks @openperf.
## 2026.5.3-1
@@ -320,6 +430,7 @@ Docs: https://docs.openclaw.ai
- CLI/message: exit cleanly with a nonzero status when message-command plugin registry loading fails before dispatch, preventing `openclaw-message` children from staying alive after plugin load errors. Fixes #76168.
- Plugins/config: report configured plugins that are present but blocked by path-safety checks as blocked instead of stale `plugin not found` entries, and deduplicate repeated blocked-candidate warnings during discovery. Fixes #76144. Thanks @mayank6136.
- Gateway/update: recover an installed-but-unloaded macOS LaunchAgent after package updates, rerun Gateway health/version/channel readiness checks, and print restart, reinstall, and rollback guidance before reporting update failure. (#76790) Thanks @jonathanlindsay.
- Codex/runtime: preserve native Codex thread bindings across dynamic-tool reorder and no-tool maintenance turns, and project mirrored history when a legacy Codex run must start without a native binding, preventing follow-up requests from losing conversation context. (#76824) Thanks @VACInc.
- CLI/plugins: explain when a missing plugin command alias belongs to a bundled plugin that is disabled by default, including the `openclaw plugins enable <plugin>` repair command. (#76835)
- Gateway/Bonjour: auto-start LAN multicast discovery only on macOS hosts while preserving explicit `openclaw plugins enable bonjour` startup elsewhere, so Linux servers and containers that do not need LAN discovery avoid default mDNS probing and watchdog churn. Refs #74209.
- Gateway/macOS: stop `doctor` and LaunchAgent recovery from running `launchctl kickstart -k` after a fresh bootstrap, avoiding an immediate SIGTERM of the just-started gateway while still nudging already-loaded launchd jobs. Fixes #76261. Thanks @solosage1.
@@ -341,12 +452,14 @@ Docs: https://docs.openclaw.ai
- Plugins/config: deduplicate identical manifest compatibility diagnostics when an explicitly configured plugin overrides another discovered candidate, so external channel plugins do not print the same missing `channelConfigs` warning repeatedly during install and enable. Thanks @vincentkoc.
- Discord/status: honor explicit `messages.statusReactions.enabled: true` in tool-only guild channels so queued ack reactions can progress through thinking/done lifecycle reactions instead of stopping at the initial emoji. Thanks @Marvinthebored.
- Discord/native commands: compare Discord-normalized slash-command descriptions and localized descriptions during reconcile so CJK or multiline command text no longer triggers redundant startup PATCH bursts and rate-limit 429s. Fixes #76587. Thanks @zhengsx.
- Agents/OpenAI Codex: align ChatGPT Codex Responses replay with the Codex wire contract by preserving session cache identity while omitting prior Responses reasoning/message/function item IDs, so tool-call turns do not feed stale item identity into later Telegram replies. (#76832) Refs #76413. Thanks @MkDev11.
- Agents/OpenAI: omit Chat Completions `reasoning_effort` for `gpt-5.4-mini` only when function tools are present while preserving tool-free Chat and Responses reasoning support, preventing Telegram-routed fallback runs from hanging after OpenAI rejects tool payloads. Fixes #76176. Thanks @ThisIsAdilah and @chinar-amrutkar.
- Telegram: reuse the successful startup `getMe` probe for grammY polling startup and continue into `getUpdates` after recoverable `deleteWebhook` cleanup failures, reducing high-latency Bot API control-plane calls before long polling starts. Refs #76388. Thanks @jackiedepp.
- Gateway/diagnostics: merge session id/key aliases in diagnostic session state and activity tracking so completed runs no longer leave stale queued work behind that keeps liveness samples at warning level.
- Agents/models: forward model `maxTokens` as the default output-token limit for OpenAI-compatible Responses and Completions transports when no runtime override is provided, preventing provider defaults from silently truncating larger outputs. (#76645) Thanks @joeyfrasier.
- macOS CLI/onboarding: honor sensitive wizard text steps in `openclaw-mac wizard` with termios no-echo input, suppressing saved credential previews while preserving long API keys and gateway tokens. Fixes #76698. Thanks @anurag-bg-neu and @sallyom.
- Control UI/Skills: fix skill detail modal silently failing to open in all browsers by deferring `showModal()` until the dialog element is connected to the DOM; the Lit `ref` callback fired before connection causing a `DOMException: HTMLDialogElement.showModal: Dialog element is not connected` on every skill click. Thanks @nickmopen.
- fix(lsp): resolve Windows .cmd shims in LSP server spawning so npm-installed language servers (e.g. typescript-language-server) start correctly on Windows. Fixes #75352. Thanks @ElliotDrel.
- Gateway/update: run `doctor --non-interactive --fix` after Control UI global package updates before reporting success, so legacy config is migrated before the gateway restart. Thanks @stevenchouai.
- Gateway/cron: stop a lazy cron startup that loses a hot-reload race, preventing the old cron service from starting after reload has already replaced cron state.
- CLI/plugins: warn when npm plugin installs remain shadowed by a failing config-selected source and surface the repair path in `plugins doctor`. Thanks @LindalyX-Lee.
@@ -456,6 +569,7 @@ Docs: https://docs.openclaw.ai
- Status/update: resolve beta update-channel checks from the installed version when config still says `stable`, and let `status --deep` reuse live gateway channel credential state instead of warning on command-path-only token misses.
- Doctor/plugins: preserve unmanaged third-party plugin `node_modules` during `doctor --fix`, while still pruning OpenClaw-managed runtime dependency caches.
- Gateway/restart: add `openclaw gateway restart --force` and `--wait <duration>`, log active task run IDs before restart deferral timers, and report timeout restarts as explicit forced restarts.
- Gateway/restart: align `gateway.restart.safe` preflight with scheduled restart deferral by counting only active restart blockers (running non-ended tasks), so queued task records no longer keep "safe" restarts deferred indefinitely. (#76923) Thanks @NikolaFC.
- Discord: persist slash-command deploy hashes across process restarts so unchanged command sets skip redeploy and avoid restart-loop 429s.
- Providers/LM Studio: normalize binary `off`/`on` reasoning metadata from Gemma 4 and other local models to LM Studio's accepted OpenAI-compatible `reasoning_effort` values.
- Plugins/externalization: keep official external install docs, update examples, and live Codex npm checks on default npm tags instead of `@beta`. Thanks @vincentkoc.
@@ -463,6 +577,7 @@ Docs: https://docs.openclaw.ai
- Plugins/ClawHub: fall back to version metadata when the artifact resolver route is missing and keep the Docker ClawHub fixture aligned with npm-pack artifact resolution, avoiding false version-not-found failures during plugin install validation. Thanks @vincentkoc.
- Providers/openai-codex: honor `providerConfig.baseUrl` in the dynamic-model synthesis fallback so codex providers configured with a custom upstream (for example a forwarding proxy) no longer silently bypass the configured URL when the registry has no template row to clone for the requested model id. (#76428) Thanks @arniesaha.
- Status/channels: show configured channels in `openclaw status` and config-only `openclaw channels status` output even when the Gateway is unreachable, avoiding empty Channels tables on WSL and other no-Gateway paths. Thanks @vincentkoc.
- Agents/main-session: keep pending final delivery markers until the final reply is actually routed or queued, so restart and heartbeat recovery can retry failed delivery. Refs #65037. (#75280) Thanks @MertBasar0.
- Plugins/ClawHub: explain unavailable explicit ClawHub ClawPack artifact downloads with a temporary npm install hint while ClawHub artifact routing rolls out. Thanks @vincentkoc.
- Media: accept home-relative `MEDIA:~/...` attachment paths while preserving existing file-read policy, traversal checks, and media type validation. Fixes #73796. Thanks @fabkury.
- Onboarding/search: install official external web-search plugins such as Brave before saving provider config, and make doctor repair reconcile selected external search providers whose npm payload is missing. Thanks @vincentkoc.
@@ -1669,6 +1784,7 @@ Docs: https://docs.openclaw.ai
- Control UI: show loading, reload, and retry states when a lazy dashboard panel cannot load after an upgrade, so the Logs tab no longer appears blank on stale browser bundles. Fixes #72450. Thanks @sobergou.
- Gateway/plugins: start the Gateway in degraded mode when a single plugin entry has invalid schema config, and let `openclaw doctor --fix` quarantine that plugin config instead of crash-looping every channel. Fixes #62976 and #70371. Thanks @Doraemon-Claw and @pksidekyk.
- Agents/plugins: skip malformed plugin tools with missing schema objects and report plugin diagnostics, so one broken tool no longer crashes Anthropic agent runs. Fixes #69423. Thanks @jmnickels.
- Dashboard: log a CVE-safe self-recovery hint pointing users to `OPENCLAW_GATEWAY_TOKEN`, `gateway.auth.token`, and fragment key `token` when neither clipboard nor browser delivery places the token-bearing URL within reach, so headless and WSL invocations are not stranded on the bare URL. Fixes #72081. Thanks @praveen9354 and @BunsDev.
- Agents/reasoning: recover fully wrapped unclosed `<think>` replies that would otherwise sanitize to empty text while keeping strict stripping for closed reasoning blocks and unclosed tails after visible text. Fixes #37696; supersedes #51915. Thanks @druide67 and @okuyam2y.
- Control UI/Gateway: bind WebChat handshakes to their active socket and reject post-close server registrations, so aborted connects no longer leave zombie clients or misleading duplicate WebSocket connection logs. Fixes #72753. Thanks @LumenFromTheFuture.
- Agents/fallback: split ambiguous provider failures into `empty_response`, `no_error_details`, and `unclassified`, and add flat fallback-step fields to structured fallback logs so primary-model failures stay visible when later fallbacks also fail. Fixes #71922; refs #71744. Thanks @andyk-ms and @nikolaykazakovvs-ux.

View File

@@ -100,6 +100,7 @@ For coordinated change sets that genuinely need more than 20 PRs, join the **#cl
## Before You PR
- Test locally with your OpenClaw instance
- External PRs must include a filled **Real behavior proof** section in the PR body. Show the real setup you tested, the exact command or steps you ran after the patch, after-fix evidence, the observed result, and anything you did not test. Screenshots, recordings, terminal screenshots, console output, copied live output, linked artifacts, and redacted runtime logs all count. Unit tests, mocks, snapshots, lint, typechecks, and CI are useful but do not satisfy this requirement by themselves. Maintainers may apply `proof: override` only when the proof gate should not apply.
- Run tests: `pnpm build && pnpm check && pnpm test`
- For iterative local commits, `scripts/committer --fast "message" <files...>` passes `FAST_COMMIT=1` through to the pre-commit hook so it skips the repo-wide `pnpm check`. Only use it when you've already run equivalent targeted validation for the touched surface.
- For extension/plugin changes, run the fast local lane first:
@@ -160,7 +161,7 @@ Built with Codex, Claude, or other AI tools? **Awesome - just mark it!**
Please include in your PR:
- [ ] Mark as AI-assisted in the PR title or description
- [ ] Note the degree of testing (untested / lightly tested / fully tested)
- [ ] Include human-run real behavior proof from your own setup. AI-generated tests, mocks, lint, typechecks, and CI output are supplemental only; they do not prove the fix works for users.
- [ ] Include prompts or session logs if possible (super helpful!)
- [ ] Confirm you understand what the code does
- [ ] If you have access to Codex, run `codex review --base origin/main` locally and address the findings before asking for review

View File

@@ -1,7 +1,7 @@
# syntax=docker/dockerfile:1.7
# Opt-in extension dependencies at build time (space-separated directory names).
# Example: docker build --build-arg OPENCLAW_EXTENSIONS="diagnostics-otel matrix" .
# Opt-in plugin dependencies at build time (space- or comma-separated directory names).
# Example: docker build --build-arg OPENCLAW_EXTENSIONS="diagnostics-otel,matrix" .
#
# Multi-stage build produces a minimal runtime image without build tools,
# source code, or Bun. Works with Docker, Buildx, and Podman.
@@ -32,7 +32,7 @@ ARG OPENCLAW_BUNDLED_PLUGIN_DIR
# Copy package.json for opted-in extensions so pnpm resolves their deps.
RUN --mount=type=bind,source=${OPENCLAW_BUNDLED_PLUGIN_DIR},target=/tmp/${OPENCLAW_BUNDLED_PLUGIN_DIR},readonly \
mkdir -p /out && \
for ext in $OPENCLAW_EXTENSIONS; do \
for ext in $(printf '%s\n' "$OPENCLAW_EXTENSIONS" | tr ',' ' '); do \
if [ -f "/tmp/${OPENCLAW_BUNDLED_PLUGIN_DIR}/$ext/package.json" ]; then \
mkdir -p "/out/$ext" && \
cp "/tmp/${OPENCLAW_BUNDLED_PLUGIN_DIR}/$ext/package.json" "/out/$ext/package.json"; \
@@ -118,12 +118,13 @@ ARG OPENCLAW_BUNDLED_PLUGIN_DIR
# prune must not rediscover unrelated workspaces from the later full source
# copy.
RUN printf 'packages:\n - .\n - ui\n' > /tmp/pnpm-workspace.runtime.yaml && \
for ext in $OPENCLAW_EXTENSIONS; do \
for ext in $(printf '%s\n' "$OPENCLAW_EXTENSIONS" | tr ',' ' '); do \
printf ' - %s/%s\n' "$OPENCLAW_BUNDLED_PLUGIN_DIR" "$ext" >> /tmp/pnpm-workspace.runtime.yaml; \
done && \
cp /tmp/pnpm-workspace.runtime.yaml pnpm-workspace.yaml && \
CI=true NPM_CONFIG_FROZEN_LOCKFILE=false pnpm prune --prod && \
node scripts/postinstall-bundled-plugins.mjs && \
OPENCLAW_EXTENSIONS="$OPENCLAW_EXTENSIONS" node scripts/prune-docker-plugin-dist.mjs && \
find dist -type f \( -name '*.d.ts' -o -name '*.d.mts' -o -name '*.d.cts' -o -name '*.map' \) -delete && \
node scripts/check-package-dist-imports.mjs /app

View File

@@ -4172,6 +4172,7 @@ public struct CronListParams: Codable, Sendable {
public let enabled: AnyCodable?
public let sortby: AnyCodable?
public let sortdir: AnyCodable?
public let agentid: String?
public init(
includedisabled: Bool?,
@@ -4180,7 +4181,8 @@ public struct CronListParams: Codable, Sendable {
query: String?,
enabled: AnyCodable?,
sortby: AnyCodable?,
sortdir: AnyCodable?)
sortdir: AnyCodable?,
agentid: String?)
{
self.includedisabled = includedisabled
self.limit = limit
@@ -4189,6 +4191,7 @@ public struct CronListParams: Codable, Sendable {
self.enabled = enabled
self.sortby = sortby
self.sortdir = sortdir
self.agentid = agentid
}
private enum CodingKeys: String, CodingKey {
@@ -4199,6 +4202,7 @@ public struct CronListParams: Codable, Sendable {
case enabled
case sortby = "sortBy"
case sortdir = "sortDir"
case agentid = "agentId"
}
}

View File

@@ -4172,6 +4172,7 @@ public struct CronListParams: Codable, Sendable {
public let enabled: AnyCodable?
public let sortby: AnyCodable?
public let sortdir: AnyCodable?
public let agentid: String?
public init(
includedisabled: Bool?,
@@ -4180,7 +4181,8 @@ public struct CronListParams: Codable, Sendable {
query: String?,
enabled: AnyCodable?,
sortby: AnyCodable?,
sortdir: AnyCodable?)
sortdir: AnyCodable?,
agentid: String?)
{
self.includedisabled = includedisabled
self.limit = limit
@@ -4189,6 +4191,7 @@ public struct CronListParams: Codable, Sendable {
self.enabled = enabled
self.sortby = sortby
self.sortdir = sortdir
self.agentid = agentid
}
private enum CodingKeys: String, CodingKey {
@@ -4199,6 +4202,7 @@ public struct CronListParams: Codable, Sendable {
case enabled
case sortby = "sortBy"
case sortdir = "sortDir"
case agentid = "agentId"
}
}

View File

@@ -41,6 +41,7 @@ const bundledPluginIgnoredRuntimeDependencies = [
"@tloncorp/tlon-skill",
"@zed-industries/codex-acp",
"jiti",
"json5",
"linkedom",
"openclaw",
"pdfjs-dist",

View File

@@ -8,6 +8,14 @@ services:
environment:
HOME: /home/node
TERM: xterm-256color
# Pin container-side workspace and config paths so host values written to
# `.env` (used by Compose for the bind-mount source below) cannot leak
# into runtime code that resolves these env vars inside the container.
# Without this override, a macOS host path like /Users/<you>/.openclaw/...
# imported from .env caused first-reply `mkdir '/Users'` EACCES failures
# in Linux Docker (#77436).
OPENCLAW_CONFIG_DIR: /home/node/.openclaw
OPENCLAW_WORKSPACE_DIR: /home/node/.openclaw/workspace
OPENCLAW_GATEWAY_TOKEN: ${OPENCLAW_GATEWAY_TOKEN:-}
OPENCLAW_ALLOW_INSECURE_PRIVATE_WS: ${OPENCLAW_ALLOW_INSECURE_PRIVATE_WS:-}
# Empty means auto: Bonjour disables itself in detected containers.
@@ -85,6 +93,10 @@ services:
environment:
HOME: /home/node
TERM: xterm-256color
# Pin container-side workspace and config paths so host values written to
# `.env` cannot leak into runtime code via the env_file import (#77436).
OPENCLAW_CONFIG_DIR: /home/node/.openclaw
OPENCLAW_WORKSPACE_DIR: /home/node/.openclaw/workspace
OPENCLAW_GATEWAY_TOKEN: ${OPENCLAW_GATEWAY_TOKEN:-}
OPENCLAW_ALLOW_INSECURE_PRIVATE_WS: ${OPENCLAW_ALLOW_INSECURE_PRIVATE_WS:-}
BROWSER: echo

View File

@@ -1,4 +1,4 @@
2c78fb7af01e2ee9e919be5ab7b675347b36cae1e347f97fd2640a6f7c72f3ac config-baseline.json
31ec333df9f8b92c7656ac7107cecd5860dd02e08f7e18c7c674dc47a8811baa config-baseline.core.json
657060e80f3dc4b7d992e8625d2a8b0ff9b1b408960148d3f5f6a381d602359a config-baseline.json
92cbb12ca382f7424e7bd52df21798b10a57621f5c266909fa74e23f6cb973d7 config-baseline.core.json
cd7c0c7fb1435bc7e59099e9ac334462d5ad444016e9ab4512aae63a238f78dc config-baseline.channel.json
9832b30a696930a3da7efccf38073137571e1b66cae84e54d747b733fdafcc54 config-baseline.plugin.json

View File

@@ -1,2 +1,2 @@
f8495c07213012748f099b12ddb02847ffd4eaa1b46f2ae9dfa574fa0ef3299a plugin-sdk-api-baseline.json
815ac868dda35d0af88b9c522233d6065c3eeb70775e19c111162b80390733fa plugin-sdk-api-baseline.jsonl
43c6f668cd8301f485c64e6a663dc1b19d38c146ce2572943e2dc961973e0c6f plugin-sdk-api-baseline.json
1d877d94bebb634d90d929fe0581ba4bccf4d12d8342d179ae9bf1053e68c013 plugin-sdk-api-baseline.jsonl

View File

@@ -102,7 +102,7 @@ Not every agent run creates a task. Heartbeat turns and normal interactive chat
<Accordion title="Notify defaults for cron and media">
Main-session cron tasks use `silent` notify policy by default — they create records for tracking but do not generate notifications. Isolated cron tasks also default to `silent` but are more visible because they run in their own session.
Session-backed `music_generate` and `video_generate` runs also use `silent` notify policy. They still create task records, but completion is handed back to the original agent session as an internal wake so the agent can write the follow-up message and attach the finished media itself. If you opt into `tools.media.asyncCompletion.directSend`, async `video_generate` completions can try direct channel delivery first; async `music_generate` completions stay on the requester-session wake path.
Session-backed `music_generate` and `video_generate` runs also use `silent` notify policy. They still create task records, but completion is handed back to the original agent session as an internal wake so the agent can write the follow-up message and attach the finished media itself. Group/channel completions follow the normal visible-reply policy, so the agent uses the message tool when source delivery requires it. If the completion agent fails to produce message-tool delivery evidence in a tool-only route, OpenClaw sends the completion fallback directly to the original channel instead of leaving the media private.
</Accordion>
<Accordion title="Concurrent video_generate guardrail">

View File

@@ -19,18 +19,175 @@ Production-ready for DMs and channels via Slack app integrations. Default mode i
</Card>
</CardGroup>
## Choosing Socket Mode or HTTP Request URLs
Both transports are production-ready and reach feature parity for messaging, slash commands, App Home, and interactivity. Pick by deployment shape, not features.
| Concern | Socket Mode (default) | HTTP Request URLs |
| ---------------------------- | ------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------- |
| Public Gateway URL | Not required | Required (DNS, TLS, reverse proxy or tunnel) |
| Outbound network | Outbound WSS to `wss-primary.slack.com` must be reachable | No outbound WS; inbound HTTPS only |
| Tokens needed | Bot token (`xoxb-...`) + App-Level Token (`xapp-...`) with `connections:write` | Bot token (`xoxb-...`) + Signing Secret |
| Dev laptop / behind firewall | Works as-is | Needs a public tunnel (ngrok, Cloudflare Tunnel, Tailscale Funnel) or staging Gateway |
| Horizontal scaling | One Socket Mode session per app per host; multiple Gateways need separate Slack apps | Stateless POST handler; multiple Gateway replicas can share one app behind a load balancer |
| Multi-account on one Gateway | Supported; each account opens its own WS | Supported; each account needs a unique `webhookPath` (default `/slack/events`) so registrations do not collide |
| Slash command transport | Delivered over the WS connection; `slash_commands[].url` is ignored | Slack POSTs to `slash_commands[].url`; field is required for the command to dispatch |
| Request signing | Not used (auth is the App-Level Token) | Slack signs every request; OpenClaw verifies with `signingSecret` |
| Recovery on connection drop | Slack SDK auto-reconnects; the gateway's pong-timeout transport tuning applies | No persistent connection to drop; retries are per-request from Slack |
<Note>
**Pick Socket Mode** for single-Gateway hosts, dev laptops, and on-prem networks that can reach `*.slack.com` outbound but cannot accept inbound HTTPS.
**Pick HTTP Request URLs** when running multiple Gateway replicas behind a load balancer, when outbound WSS is blocked but inbound HTTPS is allowed, or when you already terminate Slack webhooks at a reverse proxy.
</Note>
## Quick setup
<Tabs>
<Tab title="Socket Mode (default)">
<Steps>
<Step title="Create a new Slack app">
In Slack app settings press the **[Create New App](https://api.slack.com/apps/new)** button:
Open [api.slack.com/apps](https://api.slack.com/apps/new) → **Create New App****From a manifest** → select your workspace → paste one of the manifests below → **Next****Create**.
- choose **from a manifest** and select a workspace for your app
- paste the [example manifest](#manifest-and-scope-checklist) from below and continue to create
- generate an **App-Level Token** (`xapp-...`) with `connections:write`
- install app and copy the **Bot Token** (`xoxb-...`) shown
<CodeGroup>
```json Recommended
{
"display_information": {
"name": "OpenClaw",
"description": "Slack connector for OpenClaw"
},
"features": {
"bot_user": { "display_name": "OpenClaw", "always_online": true },
"app_home": {
"home_tab_enabled": true,
"messages_tab_enabled": true,
"messages_tab_read_only_enabled": false
},
"slash_commands": [
{
"command": "/openclaw",
"description": "Send a message to OpenClaw",
"should_escape": false
}
]
},
"oauth_config": {
"scopes": {
"bot": [
"app_mentions:read",
"assistant:write",
"channels:history",
"channels:read",
"chat:write",
"commands",
"emoji:read",
"files:read",
"files:write",
"groups:history",
"groups:read",
"im:history",
"im:read",
"im:write",
"mpim:history",
"mpim:read",
"mpim:write",
"pins:read",
"pins:write",
"reactions:read",
"reactions:write",
"usergroups:read",
"users:read"
]
}
},
"settings": {
"socket_mode_enabled": true,
"event_subscriptions": {
"bot_events": [
"app_home_opened",
"app_mention",
"channel_rename",
"member_joined_channel",
"member_left_channel",
"message.channels",
"message.groups",
"message.im",
"message.mpim",
"pin_added",
"pin_removed",
"reaction_added",
"reaction_removed"
]
}
}
}
```
```json Minimal
{
"display_information": {
"name": "OpenClaw",
"description": "Slack connector for OpenClaw"
},
"features": {
"bot_user": { "display_name": "OpenClaw", "always_online": true },
"app_home": {
"home_tab_enabled": true,
"messages_tab_enabled": true,
"messages_tab_read_only_enabled": false
},
"slash_commands": [
{
"command": "/openclaw",
"description": "Send a message to OpenClaw",
"should_escape": false
}
]
},
"oauth_config": {
"scopes": {
"bot": [
"app_mentions:read",
"assistant:write",
"channels:history",
"channels:read",
"chat:write",
"commands",
"groups:history",
"groups:read",
"im:history",
"im:read",
"im:write",
"users:read"
]
}
},
"settings": {
"socket_mode_enabled": true,
"event_subscriptions": {
"bot_events": [
"app_home_opened",
"app_mention",
"message.channels",
"message.groups",
"message.im"
]
}
}
}
```
</CodeGroup>
<Note>
**Recommended** matches the bundled Slack plugin's full feature set: App Home, slash commands, files, reactions, pins, group DMs, and emoji/usergroup reads. Pick **Minimal** when workspace policy restricts scopes — it covers DMs, channel/group history, mentions, and slash commands but drops files, reactions, pins, group-DM (`mpim:*`), `emoji:read`, and `usergroups:read`. See [Manifest and scope checklist](#manifest-and-scope-checklist) for per-scope rationale and additive options like extra slash commands.
</Note>
After Slack creates the app:
- **Basic Information → App-Level Tokens → Generate Token and Scopes**: add `connections:write`, save, copy the `xapp-...` value.
- **Install App → Install to Workspace**: copy the `xoxb-...` Bot User OAuth Token.
</Step>
@@ -80,12 +237,163 @@ openclaw gateway
<Tab title="HTTP Request URLs">
<Steps>
<Step title="Create a new Slack app">
In Slack app settings press the **[Create New App](https://api.slack.com/apps/new)** button:
Open [api.slack.com/apps](https://api.slack.com/apps/new) → **Create New App** → **From a manifest** → select your workspace → paste one of the manifests below → replace `https://gateway-host.example.com/slack/events` with your public Gateway URL → **Next** → **Create**.
- choose **from a manifest** and select a workspace for your app
- paste the [example manifest](#manifest-and-scope-checklist) and update the URLs before create
- save the **Signing Secret** for request verification
- install app and copy the **Bot Token** (`xoxb-...`) shown
<CodeGroup>
```json Recommended
{
"display_information": {
"name": "OpenClaw",
"description": "Slack connector for OpenClaw"
},
"features": {
"bot_user": { "display_name": "OpenClaw", "always_online": true },
"app_home": {
"home_tab_enabled": true,
"messages_tab_enabled": true,
"messages_tab_read_only_enabled": false
},
"slash_commands": [
{
"command": "/openclaw",
"description": "Send a message to OpenClaw",
"should_escape": false,
"url": "https://gateway-host.example.com/slack/events"
}
]
},
"oauth_config": {
"scopes": {
"bot": [
"app_mentions:read",
"assistant:write",
"channels:history",
"channels:read",
"chat:write",
"commands",
"emoji:read",
"files:read",
"files:write",
"groups:history",
"groups:read",
"im:history",
"im:read",
"im:write",
"mpim:history",
"mpim:read",
"mpim:write",
"pins:read",
"pins:write",
"reactions:read",
"reactions:write",
"usergroups:read",
"users:read"
]
}
},
"settings": {
"event_subscriptions": {
"request_url": "https://gateway-host.example.com/slack/events",
"bot_events": [
"app_home_opened",
"app_mention",
"channel_rename",
"member_joined_channel",
"member_left_channel",
"message.channels",
"message.groups",
"message.im",
"message.mpim",
"pin_added",
"pin_removed",
"reaction_added",
"reaction_removed"
]
},
"interactivity": {
"is_enabled": true,
"request_url": "https://gateway-host.example.com/slack/events",
"message_menu_options_url": "https://gateway-host.example.com/slack/events"
}
}
}
```
```json Minimal
{
"display_information": {
"name": "OpenClaw",
"description": "Slack connector for OpenClaw"
},
"features": {
"bot_user": { "display_name": "OpenClaw", "always_online": true },
"app_home": {
"home_tab_enabled": true,
"messages_tab_enabled": true,
"messages_tab_read_only_enabled": false
},
"slash_commands": [
{
"command": "/openclaw",
"description": "Send a message to OpenClaw",
"should_escape": false,
"url": "https://gateway-host.example.com/slack/events"
}
]
},
"oauth_config": {
"scopes": {
"bot": [
"app_mentions:read",
"assistant:write",
"channels:history",
"channels:read",
"chat:write",
"commands",
"groups:history",
"groups:read",
"im:history",
"im:read",
"im:write",
"users:read"
]
}
},
"settings": {
"event_subscriptions": {
"request_url": "https://gateway-host.example.com/slack/events",
"bot_events": [
"app_home_opened",
"app_mention",
"message.channels",
"message.groups",
"message.im"
]
},
"interactivity": {
"is_enabled": true,
"request_url": "https://gateway-host.example.com/slack/events",
"message_menu_options_url": "https://gateway-host.example.com/slack/events"
}
}
}
```
</CodeGroup>
<Note>
**Recommended** matches the bundled Slack plugin's full feature set; **Minimal** drops files, reactions, pins, group-DM (`mpim:*`), `emoji:read`, and `usergroups:read` for restrictive workspaces. See [Manifest and scope checklist](#manifest-and-scope-checklist) for per-scope rationale.
</Note>
<Info>
The three URL fields (`slash_commands[].url`, `event_subscriptions.request_url`, and `interactivity.request_url` / `message_menu_options_url`) all point at the same OpenClaw endpoint. Slack's manifest schema requires them named separately, but OpenClaw routes by payload type so a single `webhookPath` (default `/slack/events`) is enough. Slash commands without `slash_commands[].url` will silently no-op in HTTP mode.
</Info>
After Slack creates the app:
- **Basic Information → App Credentials**: copy the **Signing Secret** for request verification.
- **Install App → Install to Workspace**: copy the `xoxb-...` Bot User OAuth Token.
</Step>

View File

@@ -344,6 +344,7 @@ curl "https://api.telegram.org/bot<bot_token>/getUpdates"
For text-only replies:
- short DM/group/topic previews: OpenClaw keeps the same preview message and performs a final edit in place, unless a visible non-preview message was sent after the preview appeared
- long text finals that split into multiple Telegram messages reuse the existing preview as the first final chunk when possible, then send only the remaining chunks
- previews followed by visible non-preview output: OpenClaw sends the completed reply as a fresh final message and cleans up the older preview, so the final answer appears after intermediate output
- previews older than about one minute: OpenClaw sends the completed reply as a fresh final message and then cleans up the preview, so Telegram's visible timestamp reflects completion time instead of the preview creation time

View File

@@ -26,6 +26,16 @@ openclaw plugins install @openclaw/whatsapp
Use the bare package to follow the current official release tag. Pin an exact
version only when you need a reproducible install.
On Windows, the WhatsApp plugin needs Git on `PATH` during npm install because
one of its Baileys/libsignal dependencies is fetched from a git URL. Install
Git for Windows, then restart the shell and rerun the install:
```powershell
winget install --id Git.Git -e
```
Portable Git also works if its `bin` directory is on `PATH`.
<CardGroup cols={3}>
<Card title="Pairing" icon="link" href="/channels/pairing">
Default DM policy is pairing for unknown senders.

View File

@@ -152,7 +152,7 @@ Every lane uploads GitHub artifacts. When `CLAWGRIT_REPORTS_TOKEN` is configured
## Full Release Validation
`Full Release Validation` is the manual umbrella workflow for "run everything before release." It accepts a branch, tag, or full commit SHA, dispatches the manual `CI` workflow with that target, dispatches `Plugin Prerelease` for release-only plugin/package/static/Docker proof, and dispatches `OpenClaw Release Checks` for install smoke, package acceptance, Docker release-path suites, live/E2E, OpenWebUI, QA Lab parity, Matrix, and Telegram lanes. With `rerun_group=all` and `release_profile=full`, it also runs `NPM Telegram Beta E2E` against the `release-package-under-test` artifact from release checks. After publishing, pass `npm_telegram_package_spec` to rerun the same Telegram package lane against the published npm package.
`Full Release Validation` is the manual umbrella workflow for "run everything before release." It accepts a branch, tag, or full commit SHA, dispatches the manual `CI` workflow with that target, dispatches `Plugin Prerelease` for release-only plugin/package/static/Docker proof, and dispatches `OpenClaw Release Checks` for install smoke, package acceptance, cross-OS package checks, QA Lab parity, Matrix, and Telegram lanes. Stable/default runs keep exhaustive live/E2E and Docker release-path coverage behind `run_release_soak=true`; `release_profile=full` forces that soak coverage on so broad advisory validation remains broad. With `rerun_group=all` and `release_profile=full`, it also runs `NPM Telegram Beta E2E` against the `release-package-under-test` artifact from release checks. After publishing, pass `npm_telegram_package_spec` to rerun the same Telegram package lane against the published npm package.
See [Full release validation](/reference/full-release-validation) for the
stage matrix, exact workflow job names, profile differences, artifacts, and
@@ -189,7 +189,9 @@ different SHA.
`release_profile` controls live/provider breadth passed into release checks. The
manual release workflows default to `stable`; use `full` only when you
intentionally want the broad advisory provider/media matrix.
intentionally want the broad advisory provider/media matrix. `run_release_soak`
controls whether stable/default release checks run the exhaustive live/E2E and
Docker release-path soak; `full` forces soak on.
- `minimum` keeps the fastest OpenAI/core release-critical lanes.
- `stable` adds the stable provider/backend set.
@@ -197,9 +199,9 @@ intentionally want the broad advisory provider/media matrix.
The umbrella records the dispatched child run ids, and the final `Verify full validation` job re-checks current child run conclusions and appends slowest-job tables for each child run. If a child workflow is rerun and turns green, rerun only the parent verifier job to refresh the umbrella result and timing summary.
For recovery, both `Full Release Validation` and `OpenClaw Release Checks` accept `rerun_group`. Use `all` for a release candidate, `ci` for only the normal full CI child, `plugin-prerelease` for only the plugin prerelease child, `release-checks` for every release child, or a narrower group: `install-smoke`, `cross-os`, `live-e2e`, `package`, `qa`, `qa-parity`, `qa-live`, or `npm-telegram` on the umbrella. This keeps a failed release box rerun bounded after a focused fix.
For recovery, both `Full Release Validation` and `OpenClaw Release Checks` accept `rerun_group`. Use `all` for a release candidate, `ci` for only the normal full CI child, `plugin-prerelease` for only the plugin prerelease child, `release-checks` for every release child, or a narrower group: `install-smoke`, `cross-os`, `live-e2e`, `package`, `qa`, `qa-parity`, `qa-live`, or `npm-telegram` on the umbrella. This keeps a failed release box rerun bounded after a focused fix. For one failed cross-OS lane, combine `rerun_group=cross-os` with `cross_os_suite_filter`, for example `windows/packaged-upgrade`; long cross-OS commands emit heartbeat lines and packaged-upgrade summaries include per-phase timings. QA release-check lanes are advisory, so QA-only failures warn but do not block the release-check verifier.
`OpenClaw Release Checks` uses the trusted workflow ref to resolve the selected ref once into a `release-package-under-test` tarball, then passes that artifact to both the live/E2E release-path Docker workflow and the package acceptance shard. That keeps the package bytes consistent across release boxes and avoids repacking the same candidate in multiple child jobs.
`OpenClaw Release Checks` uses the trusted workflow ref to resolve the selected ref once into a `release-package-under-test` tarball, then passes that artifact to cross-OS checks and Package Acceptance, plus the live/E2E release-path Docker workflow when soak coverage runs. That keeps the package bytes consistent across release boxes and avoids repacking the same candidate in multiple child jobs.
Duplicate `Full Release Validation` runs for `ref=main` and `rerun_group=all`
supersede the older umbrella. The parent monitor cancels any child workflow it
@@ -263,7 +265,7 @@ For the dedicated update and plugin testing policy, including local commands,
Docker lanes, Package Acceptance inputs, release defaults, and failure triage,
see [Testing updates and plugins](/help/testing-updates-plugins).
Release checks call Package Acceptance with `source=artifact`, the prepared release package artifact, `suite_profile=custom`, `docker_lanes='doctor-switch update-channel-switch upgrade-survivor published-upgrade-survivor plugins-offline plugin-update'`, `published_upgrade_survivor_baselines=all-since-2026.4.23`, `published_upgrade_survivor_scenarios=reported-issues`, and `telegram_mode=mock-openai`. This keeps package migration, update, stale-plugin-dependency cleanup, configured-plugin install repair, offline plugin, plugin-update, and Telegram proof on the same resolved package tarball. Set `package_acceptance_package_spec` on Full Release Validation or OpenClaw Release Checks to run that same matrix against a shipped npm package instead of the SHA-built artifact. Cross-OS release checks still cover OS-specific onboarding, installer, and platform behavior; package/update product validation should start with Package Acceptance. The `published-upgrade-survivor` Docker lane validates one published package baseline per run. In Package Acceptance, the resolved `package-under-test` tarball is always the candidate and `published_upgrade_survivor_baseline` selects the fallback published baseline, defaulting to `openclaw@latest`; failed-lane rerun commands preserve that baseline. Set `published_upgrade_survivor_baselines=all-since-2026.4.23` to expand Full Release CI across every stable npm release from `2026.4.23` through `latest`; `release-history` remains available for manual wider sampling with the older pre-date anchor. Set `published_upgrade_survivor_scenarios=reported-issues` to expand the same baselines across issue-shaped fixtures for Feishu config, preserved bootstrap/persona files, configured OpenClaw plugin installs, tilde log paths, and stale legacy plugin dependency roots. The separate `Update Migration` workflow uses the `update-migration` Docker lane with `all-since-2026.4.23` and `plugin-deps-cleanup` when the question is exhaustive published update cleanup, not normal Full Release CI breadth. Local aggregate runs can pass exact package specs with `OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPECS`, keep a single lane with `OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPEC` such as `openclaw@2026.4.15`, or set `OPENCLAW_UPGRADE_SURVIVOR_SCENARIOS` for the scenario matrix. The published lane configures the baseline with a baked `openclaw config set` command recipe, records recipe steps in `summary.json`, and probes `/healthz`, `/readyz`, plus RPC status after Gateway start. The Windows packaged and installer fresh lanes also verify that an installed package can import a browser-control override from a raw absolute Windows path. The OpenAI cross-OS agent-turn smoke defaults to `OPENCLAW_CROSS_OS_OPENAI_MODEL` when set, otherwise `openai/gpt-5.4`, so the install and gateway proof stays on a GPT-5 test model while avoiding GPT-4.x defaults.
Release checks call Package Acceptance with `source=artifact`, the prepared release package artifact, `suite_profile=custom`, `docker_lanes='doctor-switch update-channel-switch upgrade-survivor published-upgrade-survivor plugins-offline plugin-update'`, and `telegram_mode=mock-openai`. This keeps package migration, update, stale-plugin-dependency cleanup, configured-plugin install repair, offline plugin, plugin-update, and Telegram proof on the same resolved package tarball. Set `package_acceptance_package_spec` on Full Release Validation or OpenClaw Release Checks to run that same matrix against a shipped npm package instead of the SHA-built artifact. Cross-OS release checks still cover OS-specific onboarding, installer, and platform behavior; package/update product validation should start with Package Acceptance. The `published-upgrade-survivor` Docker lane validates one published package baseline per run in the blocking release path. In Package Acceptance, the resolved `package-under-test` tarball is always the candidate and `published_upgrade_survivor_baseline` selects the fallback published baseline, defaulting to `openclaw@latest`; failed-lane rerun commands preserve that baseline. Full Release Validation with `run_release_soak=true` or `release_profile=full` sets `published_upgrade_survivor_baselines='last-stable-4 2026.4.23 2026.5.2 2026.4.15'` and `published_upgrade_survivor_scenarios=reported-issues` to expand across the four latest stable npm releases plus pinned plugin-compatibility boundary releases and issue-shaped fixtures for Feishu config, preserved bootstrap/persona files, configured OpenClaw plugin installs, tilde log paths, and stale legacy plugin dependency roots. Multi-baseline published-upgrade survivor selections are sharded by baseline into separate targeted Docker runner jobs. The separate `Update Migration` workflow uses the `update-migration` Docker lane with `all-since-2026.4.23` and `plugin-deps-cleanup` when the question is exhaustive published update cleanup, not normal Full Release CI breadth. Local aggregate runs can pass exact package specs with `OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPECS`, keep a single lane with `OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPEC` such as `openclaw@2026.4.15`, or set `OPENCLAW_UPGRADE_SURVIVOR_SCENARIOS` for the scenario matrix. The published lane configures the baseline with a baked `openclaw config set` command recipe, records recipe steps in `summary.json`, and probes `/healthz`, `/readyz`, plus RPC status after Gateway start. The Windows packaged and installer fresh lanes also verify that an installed package can import a browser-control override from a raw absolute Windows path. The OpenAI cross-OS agent-turn smoke defaults to `OPENCLAW_CROSS_OS_OPENAI_MODEL` when set, otherwise `openai/gpt-5.4`, so the install and gateway proof stays on a GPT-5 test model while avoiding GPT-4.x defaults.
### Legacy compatibility windows

View File

@@ -211,12 +211,15 @@ Manual run and inspection:
```bash
openclaw cron list
openclaw cron list --agent ops
openclaw cron show <job-id>
openclaw cron run <job-id>
openclaw cron run <job-id> --due
openclaw cron runs --id <job-id> --limit 50
```
`openclaw cron list` shows all matching jobs by default. Pass `--agent <id>` to show only jobs whose effective normalized agent id matches; jobs without a stored agent id count as the configured default agent.
`cron runs` entries include delivery diagnostics with the intended cron target, the resolved target, message-tool sends, fallback use, and delivered state.
Agent and session retargeting:

View File

@@ -36,7 +36,7 @@ openclaw daemon uninstall
- `status`: `--url`, `--token`, `--password`, `--timeout`, `--no-probe`, `--require-rpc`, `--deep`, `--json`
- `install`: `--port`, `--runtime <node|bun>`, `--token`, `--force`, `--json`
- `restart`: `--force`, `--wait <duration>`, `--json`
- `restart`: `--safe`, `--force`, `--wait <duration>`, `--json`
- lifecycle (`uninstall|start|stop`): `--json`
Notes:
@@ -53,6 +53,7 @@ Notes:
- If both `gateway.auth.token` and `gateway.auth.password` are configured and `gateway.auth.mode` is unset, install is blocked until mode is set explicitly.
- On macOS, `install` keeps LaunchAgent plists owner-only and loads managed service environment values through an owner-only file and wrapper instead of serializing API keys or auth-profile env refs into `EnvironmentVariables`.
- If you intentionally run multiple gateways on one host, isolate ports, config/state, and workspaces; see [/gateway#multiple-gateways-same-host](/gateway#multiple-gateways-same-host).
- `restart --safe` asks the running Gateway to preflight active work and schedule one coalesced restart after active work drains. Plain `restart` keeps the existing service-manager behavior; `--force` remains the immediate override path.
## Prefer

View File

@@ -20,6 +20,10 @@ Notes:
- `dashboard` resolves configured `gateway.auth.token` SecretRefs when possible.
- `dashboard` follows `gateway.tls.enabled`: TLS-enabled gateways print/open
`https://` Control UI URLs and connect over `wss://`.
- If clipboard/browser delivery fails for a token-authenticated dashboard URL,
`dashboard` logs a safe manual-auth hint naming `OPENCLAW_GATEWAY_TOKEN`,
`gateway.auth.token`, and fragment key `token` without printing the token
value.
- For SecretRef-managed tokens (resolved or unresolved), `dashboard` prints/copies/opens a non-tokenized URL to avoid exposing external secrets in terminal output, clipboard history, or browser-launch arguments.
- If `gateway.auth.token` is SecretRef-managed but unresolved in this command path, the command prints a non-tokenized URL and explicit remediation guidance instead of embedding an invalid token placeholder.

View File

@@ -45,7 +45,7 @@ Notes:
- State integrity checks now detect orphan transcript files in the sessions directory. Archiving them as `.deleted.<timestamp>` requires an interactive confirmation; `--fix`, `--yes`, and headless runs leave them in place.
- Doctor also scans `~/.openclaw/cron/jobs.json` (or `cron.store`) for legacy cron job shapes and can rewrite them in place before the scheduler has to auto-normalize them at runtime.
- On Linux, doctor warns when the user's crontab still runs legacy `~/.openclaw/bin/ensure-whatsapp.sh`; that script is no longer maintained and can log false WhatsApp gateway outages when cron lacks the systemd user-bus environment.
- Doctor cleans legacy plugin dependency staging state created by older OpenClaw versions. It also repairs missing configured downloadable plugins when the registry can resolve them, and the 2026.5.2 doctor pass automatically installs downloadable plugins that an older config already uses before marking the config touched for that release. If the download fails, doctor reports the install error and preserves the configured plugin entry for the next repair attempt.
- Doctor cleans legacy plugin dependency staging state created by older OpenClaw versions. It also repairs missing downloadable plugins that are referenced by config, such as `plugins.entries`, configured channels, configured provider/search settings, or configured agent runtimes. During package updates, doctor skips package-manager plugin repair until the package swap is complete; rerun `openclaw doctor --fix` afterward if a configured plugin still needs recovery. If the download fails, doctor reports the install error and preserves the configured plugin entry for the next repair attempt.
- Doctor repairs stale plugin config by removing missing plugin ids from `plugins.allow`/`plugins.entries`, plus matching dangling channel config, heartbeat targets, and channel model overrides when plugin discovery is healthy.
- Doctor quarantines invalid plugin config by disabling the affected `plugins.entries.<id>` entry and removing its invalid `config` payload. Gateway startup already skips only that bad plugin so other plugins and channels can keep running.
- Set `OPENCLAW_SERVICE_REPAIR_POLICY=external` when another supervisor owns the gateway lifecycle. Doctor still reports gateway/service health and applies non-service repairs, but skips service install/start/restart/bootstrap and legacy service cleanup.

View File

@@ -105,6 +105,16 @@ openclaw gateway run
Raw stream jsonl path.
</ParamField>
## Restart the Gateway
```bash
openclaw gateway restart
openclaw gateway restart --safe
openclaw gateway restart --force
```
`openclaw gateway restart --safe` asks the running Gateway to preflight active OpenClaw work before restarting. If queued operations, reply delivery, embedded runs, or task runs are active, the Gateway reports the blockers, coalesces duplicate safe restart requests, and restarts once the active work drains. Plain `restart` keeps the existing service-manager behavior for compatibility. Use `--force` only when you explicitly want the immediate override path.
<Warning>
Inline `--password` can be exposed in local process listings. Prefer `--password-file`, env, or a SecretRef-backed `gateway.auth.password`.
</Warning>
@@ -471,12 +481,13 @@ openclaw gateway restart
<Accordion title="Command options">
- `gateway status`: `--url`, `--token`, `--password`, `--timeout`, `--no-probe`, `--require-rpc`, `--deep`, `--json`
- `gateway install`: `--port`, `--runtime <node|bun>`, `--token`, `--wrapper <path>`, `--force`, `--json`
- `gateway restart`: `--force`, `--wait <duration>`, `--json`
- `gateway restart`: `--safe`, `--force`, `--wait <duration>`, `--json`
- `gateway uninstall|start|stop`: `--json`
</Accordion>
<Accordion title="Lifecycle behavior">
- Use `gateway restart` to restart a managed service. Do not chain `gateway stop` and `gateway start` as a restart substitute; on macOS, `gateway stop` intentionally disables the LaunchAgent before stopping it.
- `gateway restart --safe` asks the running Gateway to preflight active OpenClaw work and defer the restart until reply delivery, embedded runs, and task runs drain. `--safe` cannot be combined with `--force` or `--wait`.
- `gateway restart --wait 30s` overrides the configured restart drain budget for that restart. Bare numbers are milliseconds; units such as `s`, `m`, and `h` are accepted. `--wait 0` waits indefinitely.
- `gateway restart --force` skips the active-work drain and restarts immediately. Use it when an operator has already inspected the listed task blockers and wants the gateway back now.
- Lifecycle commands accept `--json` for scripting.

View File

@@ -266,7 +266,7 @@ directory remains inert so normal packaged installs still use compiled dist.
For runtime hook debugging:
- `openclaw plugins inspect <id> --runtime --json` shows registered hooks and diagnostics from a module-loaded inspection pass. Runtime inspection never installs dependencies; use `openclaw doctor --fix` to clean legacy dependency state or install missing configured downloadable plugins.
- `openclaw plugins inspect <id> --runtime --json` shows registered hooks and diagnostics from a module-loaded inspection pass. Runtime inspection never installs dependencies; use `openclaw doctor --fix` to clean legacy dependency state or recover missing downloadable plugins that are referenced by config.
- `openclaw gateway status --deep --require-rpc` confirms the reachable Gateway, service/process hints, config path, and RPC health.
- Non-bundled conversation hooks (`llm_input`, `llm_output`, `before_agent_finalize`, `agent_end`) require `plugins.entries.<id>.hooks.allowConversationAccess=true`.

View File

@@ -16,17 +16,19 @@ until a message is processed. Use `openclaw channels status --probe`,
`openclaw status --deep`, or `openclaw health --verbose` when you need live
channel connectivity.
Gateway `sessions.list` responses are bounded by default so large long-lived
stores cannot monopolize the Gateway event loop. Pass an explicit positive
`limit` from RPC clients when a different result window is needed; responses
include `totalCount`, `limitApplied`, and `hasMore` when callers need to show
that more rows exist.
`openclaw sessions` and Gateway `sessions.list` responses are bounded by
default so large long-lived stores cannot monopolize the CLI process or Gateway
event loop. The CLI returns the newest 100 sessions by default; pass
`--limit <n>` for a smaller/larger window or `--limit all` when you intentionally
need the full store. JSON responses include `totalCount`, `limitApplied`, and
`hasMore` when callers need to show that more rows exist.
```bash
openclaw sessions
openclaw sessions --agent work
openclaw sessions --all-agents
openclaw sessions --active 120
openclaw sessions --limit 25
openclaw sessions --verbose
openclaw sessions --json
```
@@ -38,6 +40,7 @@ Scope selection:
- `--agent <id>`: one configured agent store
- `--all-agents`: aggregate all configured agent stores
- `--store <path>`: explicit store path (cannot be combined with `--agent` or `--all-agents`)
- `--limit <n|all>`: max rows to output (default `100`; `all` restores full output)
Export a trajectory bundle for a stored session:
@@ -69,6 +72,9 @@ JSON examples:
],
"allAgents": true,
"count": 2,
"totalCount": 2,
"limitApplied": 100,
"hasMore": false,
"activeMinutes": null,
"sessions": [
{ "agentId": "main", "key": "agent:main:main", "model": "gpt-5" },
@@ -93,6 +99,7 @@ openclaw sessions cleanup --json
`openclaw sessions cleanup` uses `session.maintenance` settings from config:
- Scope note: `openclaw sessions cleanup` maintains session stores, transcripts, and trajectory sidecars. It does not prune cron run logs (`cron/runs/<jobId>.jsonl`), which are managed by `cron.runLog.maxBytes` and `cron.runLog.keepLines` in [Cron configuration](/automation/cron-jobs#configuration) and explained in [Cron maintenance](/automation/cron-jobs#maintenance).
- Cleanup also prunes unreferenced primary transcripts, compaction checkpoints, and trajectory sidecars older than `session.maintenance.pruneAfter`; files still referenced by `sessions.json` are preserved.
- `--dry-run`: preview how many entries would be pruned/capped without writing.
- In text mode, dry-run prints a per-session action table (`Action`, `Key`, `Age`, `Model`, `Flags`) so you can see what would be kept vs removed.

View File

@@ -26,6 +26,7 @@ Notes:
- Session status output separates `Execution:` from `Runtime:`. `Execution` is the sandbox path (`direct`, `docker/*`), while `Runtime` tells you whether the session is using `OpenClaw Pi Default`, `OpenAI Codex`, a CLI backend, or an ACP backend such as `codex (acp/acpx)`. See [Agent runtimes](/concepts/agent-runtimes) for the provider/model/runtime distinction.
- MiniMax's raw `usage_percent` / `usagePercent` fields are remaining quota, so OpenClaw inverts them before display; count-based fields win when present. `model_remains` responses prefer the chat-model entry, derive the window label from timestamps when needed, and include the model name in the plan label.
- When the current session snapshot is sparse, `/status` can backfill token and cache counters from the most recent transcript usage log. Existing nonzero live values still win over transcript fallback values.
- `/status` includes compact Gateway process uptime and host system uptime.
- Transcript fallback can also recover the active runtime model label when the live session entry is missing it. If that transcript model differs from the selected model, status resolves the context window against the recovered runtime model instead of the selected one.
- For prompt-size accounting, transcript fallback prefers the larger prompt-oriented total when session metadata is missing or smaller, so custom-provider sessions do not collapse to `0` token displays.
- Output includes per-agent session stores when multiple agents are configured.

View File

@@ -168,8 +168,9 @@ manually.
On the beta update channel, tracked npm and ClawHub plugin installs that follow
the default/latest line try a plugin `@beta` release first. If the plugin has no
beta release, OpenClaw falls back to the recorded default/latest spec. Exact
versions and explicit tags are not rewritten.
beta release, OpenClaw falls back to the recorded default/latest spec. For npm
plugins, OpenClaw also falls back when the beta package exists but fails install
validation. Exact versions and explicit tags are not rewritten.
<Warning>
If an exact pinned npm plugin update resolves to an artifact whose integrity differs from the stored install record, `openclaw update` aborts that plugin artifact update instead of installing it. Reinstall or update the plugin explicitly only after verifying that you trust the new artifact.

View File

@@ -165,7 +165,7 @@ surfaces, while Codex native hooks remain a separate lower-level Codex mechanism
- `agent.wait` default: 30s (just the wait). `timeoutMs` param overrides.
- Agent runtime: `agents.defaults.timeoutSeconds` default 172800s (48 hours); enforced in `runEmbeddedPiAgent` abort timer.
- Cron runtime: isolated agent-turn `timeoutSeconds` is owned by cron. The scheduler starts that timer when execution begins, aborts the underlying run at the configured deadline, then runs bounded cleanup before recording the timeout so a stale child session cannot keep the lane stuck.
- Session liveness diagnostics: with diagnostics enabled, `diagnostics.stuckSessionWarnMs` classifies long `processing` sessions that have no observed reply, tool, status, block, or ACP progress. Active embedded runs, model calls, and tool calls report as `session.long_running`; active work with no recent progress reports as `session.stalled`; `session.stuck` is reserved for stale session bookkeeping with no active work. Stale session bookkeeping releases the affected session lane immediately; stalled embedded runs are abort-drained only after an extended no-progress window (at least 10 minutes and 5x the warning threshold) so queued work can resume without cutting off merely slow runs. Repeated `session.stuck` diagnostics back off while the session remains unchanged.
- Session liveness diagnostics: with diagnostics enabled, `diagnostics.stuckSessionWarnMs` classifies long `processing` sessions that have no observed reply, tool, status, block, or ACP progress. Active embedded runs, model calls, and tool calls report as `session.long_running`; active work with no recent progress reports as `session.stalled`; `session.stuck` is reserved for stale session bookkeeping with no active work. Stale session bookkeeping releases the affected session lane immediately; stalled embedded runs are abort-drained only after `diagnostics.stuckSessionAbortMs` (default: at least 10 minutes and 5x the warning threshold) so queued work can resume without cutting off merely slow runs. Recovery emits structured requested/completed outcomes, and diagnostic state is marked idle only if the same processing generation is still current. Repeated `session.stuck` diagnostics back off while the session remains unchanged.
- Model idle timeout: OpenClaw aborts a model request when no response chunks arrive before the idle window. `models.providers.<id>.timeoutSeconds` extends this idle watchdog for slow local/self-hosted providers; otherwise OpenClaw uses `agents.defaults.timeoutSeconds` when configured, capped at 120s by default. Cron-triggered runs with no explicit model or agent timeout disable the idle watchdog and rely on the cron outer timeout.
- Provider HTTP request timeout: `models.providers.<id>.timeoutSeconds` applies to that provider's model HTTP fetches, including connect, headers, body, SDK request timeout, total guarded-fetch abort handling, and model stream idle watchdog. Use this for slow local/self-hosted providers such as Ollama before raising the whole agent runtime timeout.

View File

@@ -168,7 +168,11 @@ worktrees, runs `discord-status-reactions-tool-only` against each worktree, and
uploads `baseline/`, `candidate/`, `comparison.json`, and `mantis-report.md` as
Actions artifacts. It also renders each lane's timeline HTML in a Crabbox
desktop browser and publishes those VNC screenshots beside the deterministic
timeline PNGs in the PR comment. The workflow builds the Crabbox CLI from
timeline PNGs in the PR comment. The same PR comment embeds lightweight
motion-trimmed GIF previews generated by `crabbox media preview`, links to the
matching motion-trimmed MP4 clips, and keeps the full desktop MP4 files for deep
inspection. Screenshots stay inline for quick review. The workflow builds the
Crabbox CLI from
`openclaw/crabbox` main so it can use the current desktop/browser lease flags
before the next Crabbox binary release is cut.

View File

@@ -119,7 +119,8 @@ openclaw config set agents.defaults.models '{"openai/gpt-5.4":{}}' --strict-json
If `agents.defaults.models` is set, it becomes the **allowlist** for `/model` and for session overrides. When a user selects a model that isn't in that allowlist, OpenClaw returns:
```
Model "provider/model" is not allowed. Use /model to list available models.
Model "provider/model" is not allowed. Use /models to list providers, or /models <provider> to list models.
Add it with: openclaw config set agents.defaults.models '{"provider/model":{}}' --strict-json --merge
```
<Warning>
@@ -131,6 +132,8 @@ This happens **before** a normal reply is generated, so the message can feel lik
</Warning>
When the rejected command included a runtime override such as `/model openai/gpt-5.5 --runtime codex`, fix the allowlist first, then retry the same `/model ... --runtime ...` command. For native Codex execution, the selected model is still `openai/gpt-5.5`; the `codex` runtime selects the harness and uses Codex auth separately.
For local/GGUF models, store the full provider-prefixed ref in the allowlist,
for example `ollama/gemma4:26b`, `lmstudio/Gemma4-26b-a4-it-gguf`, or the
exact provider/model shown by `openclaw models list --provider <provider>`.

View File

@@ -116,7 +116,6 @@ For transport-real Telegram, Discord, and Slack smoke lanes:
```bash
pnpm openclaw qa telegram
pnpm openclaw qa discord
# Slack live QA is currently parked from scheduled/release workflows.
pnpm openclaw qa slack
```
@@ -133,12 +132,37 @@ pnpm openclaw qa mantis slack-desktop-smoke \
That command leases a Crabbox desktop/browser machine, runs the Slack live lane
inside the VM, opens Slack Web in the VNC browser, captures the desktop, and
copies `slack-qa/` plus `slack-desktop-smoke.png` back to the Mantis artifact
directory. Reuse `--lease-id <cbx_...>` after logging in to Slack Web manually
copies `slack-qa/`, `slack-desktop-smoke.png`, and `slack-desktop-smoke.mp4`
when video capture is available back to the Mantis artifact directory. Reuse `--lease-id <cbx_...>` after logging in to Slack Web manually
through VNC. With `--gateway-setup`, Mantis leaves a persistent OpenClaw Slack
gateway running inside the VM on port `38973`; without it, the command runs the
normal bot-to-bot Slack QA lane and exits after artifact capture.
For an agent/CV style desktop task, run:
```bash
pnpm openclaw qa mantis visual-task \
--browser-url https://example.net \
--expect-text "Example Domain" \
--vision-model openai/gpt-5.4
```
`visual-task` leases or reuses a Crabbox desktop/browser machine, starts
`crabbox record --while`, drives the visible browser through a nested
`visual-driver`, captures `visual-task.png`, runs `openclaw infer image describe`
against the screenshot when `--vision-mode image-describe` is selected, and
writes `visual-task.mp4`, `mantis-visual-task-summary.json`,
`mantis-visual-task-driver-result.json`, and `mantis-visual-task-report.md`.
When `--expect-text` is set, the vision prompt asks for a structured JSON
verdict and only passes when the model reports positive visible evidence; a
negative response that merely quotes the target text fails the assertion.
Use `--vision-mode metadata` for a no-model smoke that proves the desktop,
browser, screenshot, and video plumbing without calling an image-understanding
provider. Recording is a required artifact for `visual-task`; if Crabbox records
no non-empty `visual-task.mp4`, the task fails even when the visual driver
passed. On failure, Mantis keeps the lease for VNC unless the task had already
passed and `--keep-lease` was not set.
Before using pooled live credentials, run:
```bash
@@ -233,6 +257,8 @@ Scenarios (`extensions/qa-lab/src/live-transports/telegram/telegram-live.runtime
- `telegram-tools-compact-command`
- `telegram-whoami-command`
- `telegram-context-command`
- `telegram-long-final-reuses-preview`
- `telegram-long-final-three-chunks`
Output artifacts:
@@ -265,7 +291,7 @@ Scenarios (`extensions/qa-lab/src/live-transports/discord/discord-live.runtime.t
- `discord-canary`
- `discord-mention-gating`
- `discord-native-help-command-registration`
- `discord-status-reactions-tool-only` — opt-in Mantis scenario. Runs by itself because it switches the SUT to always-on, tool-only guild replies with `messages.statusReactions.enabled=true`, then captures a REST reaction timeline plus an HTML/PNG visual artifact.
- `discord-status-reactions-tool-only` — opt-in Mantis scenario. Runs by itself because it switches the SUT to always-on, tool-only guild replies with `messages.statusReactions.enabled=true`, then captures a REST reaction timeline plus HTML/PNG visual artifacts. Mantis before/after reports also preserve scenario-provided MP4 artifacts as `baseline.mp4` and `candidate.mp4`.
Run the Mantis status-reaction scenario explicitly:
@@ -315,6 +341,180 @@ Output artifacts:
- `slack-qa-summary.json`
- `slack-qa-observed-messages.json` — bodies redacted unless `OPENCLAW_QA_SLACK_CAPTURE_CONTENT=1`.
#### Setting up the Slack workspace
The lane needs two distinct Slack apps in one workspace, plus a channel both bots are members of:
- `channelId` — the `Cxxxxxxxxxx` id of a channel both bots have been invited to. Use a dedicated channel; the lane posts on every run.
- `driverBotToken` — bot token (`xoxb-...`) of the **Driver** app.
- `sutBotToken` — bot token (`xoxb-...`) of the **SUT** app, which must be a separate Slack app from the driver so its bot user id is distinct.
- `sutAppToken` — app-level token (`xapp-...`) of the SUT app with `connections:write`, used by Socket Mode so the SUT app can receive events.
Prefer a Slack workspace dedicated to QA over reusing a production workspace.
The SUT manifest below mirrors the bundled Slack plugin's production install (`extensions/slack/src/setup-shared.ts:10`). For the production-channel setup as users see it, see [Slack channel quick setup](/channels/slack#quick-setup); the QA Driver/SUT pair is intentionally separate because the lane needs two distinct bot user ids in one workspace.
**1. Create the Driver app**
Go to [api.slack.com/apps](https://api.slack.com/apps) → _Create New App__From a manifest_ → pick the QA workspace, paste the following manifest, then _Install to Workspace_:
```json
{
"display_information": {
"name": "OpenClaw QA Driver",
"description": "Test driver bot for OpenClaw QA Slack live lane"
},
"features": {
"bot_user": {
"display_name": "OpenClaw QA Driver",
"always_online": true
}
},
"oauth_config": {
"scopes": {
"bot": ["chat:write", "channels:history", "groups:history", "users:read"]
}
},
"settings": {
"socket_mode_enabled": false
}
}
```
Copy the _Bot User OAuth Token_ (`xoxb-...`) — that becomes `driverBotToken`. The driver only needs to post messages and identify itself; no events, no Socket Mode.
**2. Create the SUT app**
Repeat _Create New App → From a manifest_ in the same workspace. The scope set mirrors the bundled Slack plugin's production install (`extensions/slack/src/setup-shared.ts:10`):
```json
{
"display_information": {
"name": "OpenClaw QA SUT",
"description": "OpenClaw QA SUT connector for OpenClaw"
},
"features": {
"bot_user": {
"display_name": "OpenClaw QA SUT",
"always_online": true
},
"app_home": {
"home_tab_enabled": true,
"messages_tab_enabled": true,
"messages_tab_read_only_enabled": false
}
},
"oauth_config": {
"scopes": {
"bot": [
"app_mentions:read",
"assistant:write",
"channels:history",
"channels:read",
"chat:write",
"commands",
"emoji:read",
"files:read",
"files:write",
"groups:history",
"groups:read",
"im:history",
"im:read",
"im:write",
"mpim:history",
"mpim:read",
"mpim:write",
"pins:read",
"pins:write",
"reactions:read",
"reactions:write",
"usergroups:read",
"users:read"
]
}
},
"settings": {
"socket_mode_enabled": true,
"event_subscriptions": {
"bot_events": [
"app_home_opened",
"app_mention",
"channel_rename",
"member_joined_channel",
"member_left_channel",
"message.channels",
"message.groups",
"message.im",
"message.mpim",
"pin_added",
"pin_removed",
"reaction_added",
"reaction_removed"
]
}
}
}
```
After Slack creates the app, do two things on its settings page:
- _Install to Workspace_ → copy the _Bot User OAuth Token_ → that becomes `sutBotToken`.
- _Basic Information → App-Level Tokens → Generate Token and Scopes_ → add scope `connections:write` → save → copy the `xapp-...` value → that becomes `sutAppToken`.
Verify the two bots have distinct user ids by calling `auth.test` on each token. The runtime distinguishes driver and SUT by user id; reusing one app for both will fail mention-gating immediately.
**3. Create the channel**
In the QA workspace, create a channel (e.g. `#openclaw-qa`) and invite both bots from inside the channel:
```
/invite @OpenClaw QA Driver
/invite @OpenClaw QA SUT
```
Copy the `Cxxxxxxxxxx` id from _channel info → About → Channel ID_ — that becomes `channelId`. A public channel works; if you use a private channel both apps already have `groups:history` so the harness's history reads will still succeed.
**4. Register the credentials**
Two options. Use env vars for single-machine debugging (set the four `OPENCLAW_QA_SLACK_*` variables and pass `--credential-source env`), or seed the shared Convex pool so CI and other maintainers can lease them.
For the Convex pool, write the four fields to a JSON file:
```json
{
"channelId": "Cxxxxxxxxxx",
"driverBotToken": "xoxb-...",
"sutBotToken": "xoxb-...",
"sutAppToken": "xapp-..."
}
```
With `OPENCLAW_QA_CONVEX_SITE_URL` and `OPENCLAW_QA_CONVEX_SECRET_MAINTAINER` exported in your shell, register and verify:
```bash
pnpm openclaw qa credentials add \
--kind slack \
--payload-file slack-creds.json \
--note "QA Slack pool seed"
pnpm openclaw qa credentials list --kind slack --status all --json
```
Expect `count: 1`, `status: "active"`, no `lease` field.
**5. Verify end to end**
Run the lane locally to confirm both bots can talk to each other through the broker:
```bash
pnpm openclaw qa slack \
--credential-source convex \
--credential-role maintainer \
--output-dir .artifacts/qa-e2e/slack-local
```
A green run completes in well under 30 seconds and `slack-qa-report.md` shows both `slack-canary` and `slack-mention-gating` at status `pass`. If the lane hangs for ~90 seconds and exits with `Convex credential pool exhausted for kind "slack"`, either the pool is empty or every row is leased — `qa credentials list --kind slack --status all --json` will tell you which.
### Convex credential pool
Telegram, Discord, and Slack lanes can lease credentials from a shared Convex pool instead of reading the env vars above. Pass `--credential-source convex` (or set `OPENCLAW_QA_CREDENTIAL_SOURCE=convex`); QA Lab acquires an exclusive lease, heartbeats it for the duration of the run, and releases it on shutdown. Pool kinds are `"telegram"`, `"discord"`, and `"slack"`.
@@ -323,6 +523,7 @@ Payload shapes the broker validates on `admin/add`:
- Telegram (`kind: "telegram"`): `{ groupId: string, driverToken: string, sutToken: string }``groupId` must be a numeric chat-id string.
- Discord (`kind: "discord"`): `{ guildId: string, channelId: string, driverBotToken: string, sutBotToken: string, sutApplicationId: string }`.
- Slack (`kind: "slack"`): `{ channelId: string, driverBotToken: string, sutBotToken: string, sutAppToken: string }``channelId` must match `^[A-Z][A-Z0-9]+$` (a Slack id like `Cxxxxxxxxxx`). See [Setting up the Slack workspace](#setting-up-the-slack-workspace) for app and scope provisioning.
Operational env vars and the Convex broker endpoint contract live in [Testing → Shared Telegram credentials via Convex](/help/testing#shared-telegram-credentials-via-convex-v1) (the section name predates Discord support; the broker semantics are identical for both kinds).

View File

@@ -178,6 +178,12 @@ that agent. To force a different Claude mode, set explicit raw backend args
such as `--permission-mode default` or `--permission-mode acceptEdits` under
`agents.defaults.cliBackends.claude-cli.args` and matching `resumeArgs`.
The bundled Anthropic `claude-cli` backend also maps OpenClaw `/think` levels
to Claude Code's native `--effort` flag for non-off levels. `minimal` and
`low` map to `low`, `adaptive` and `medium` map to `medium`, and `high`,
`xhigh`, and `max` map directly. Other CLI backends need their owning plugin to
declare an equivalent argv mapper before `/think` can affect the spawned CLI.
Before OpenClaw can use the bundled `claude-cli` backend, Claude Code itself
must already be logged in on the same host:

View File

@@ -209,7 +209,7 @@ Configures inbound media understanding (image/audio/video):
media: {
concurrency: 2,
asyncCompletion: {
directSend: false, // opt-in: send finished async video directly to the channel
directSend: false, // deprecated: completions stay agent-mediated
},
audio: {
enabled: true,
@@ -262,7 +262,7 @@ Configures inbound media understanding (image/audio/video):
**Async completion fields:**
- `asyncCompletion.directSend`: when `true`, completed async media tasks that support direct completion delivery try direct channel delivery first. Default: `false` (requester-session wake/model-delivery path). Today this applies to async `video_generate`; async `music_generate` completions stay requester-session mediated even when this is enabled.
- `asyncCompletion.directSend`: deprecated compatibility flag. Completed async media tasks stay requester-session mediated so the agent receives the result, decides how to tell the user, and uses the message tool when source delivery requires it.
</Accordion>
</AccordionGroup>

View File

@@ -166,6 +166,7 @@ See [MCP](/cli/mcp#openclaw-as-an-mcp-client-registry) and
plugins: {
enabled: true,
allow: ["voice-call"],
bundledDiscovery: "allowlist",
deny: [],
load: {
paths: ["~/Projects/oss/voice-call-plugin"],
@@ -187,6 +188,10 @@ See [MCP](/cli/mcp#openclaw-as-an-mcp-client-registry) and
- Discovery accepts native OpenClaw plugins plus compatible Codex bundles and Claude bundles, including manifestless Claude default-layout bundles.
- **Config changes require a gateway restart.**
- `allow`: optional allowlist (only listed plugins load). `deny` wins.
- `bundledDiscovery`: defaults to `"allowlist"` for new configs, so a non-empty
`plugins.allow` also gates bundled provider plugins, including web-search
runtime providers. Doctor writes `"compat"` for migrated legacy allowlist
configs to preserve existing bundled provider behavior until you opt in.
- `plugins.entries.<id>.apiKey`: plugin-level API key convenience field (when supported by the plugin).
- `plugins.entries.<id>.env`: plugin-scoped env var map.
- `plugins.entries.<id>.hooks.allowPromptInjection`: when `false`, core blocks `before_prompt_build` and ignores prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride`. Applies to native plugin hooks and supported bundle-provided hook directories.
@@ -915,6 +920,7 @@ Notes:
enabled: true,
flags: ["telegram.*"],
stuckSessionWarnMs: 30000,
stuckSessionAbortMs: 600000,
otel: {
enabled: false,
@@ -954,6 +960,7 @@ Notes:
- `enabled`: master toggle for instrumentation output (default: `true`).
- `flags`: array of flag strings enabling targeted log output (supports wildcards like `"telegram.*"` or `"*"`).
- `stuckSessionWarnMs`: no-progress age threshold in ms for classifying long-running processing sessions as `session.long_running`, `session.stalled`, or `session.stuck`. Reply, tool, status, block, and ACP progress reset the timer; repeated `session.stuck` diagnostics back off while unchanged.
- `stuckSessionAbortMs`: no-progress age threshold in ms before eligible stalled active work may be abort-drained for recovery. When unset, OpenClaw uses the safer extended embedded-run window of at least 10 minutes and 5x `stuckSessionWarnMs`.
- `otel.enabled`: enables the OpenTelemetry export pipeline (default: `false`). For the full configuration, signal catalog, and privacy model, see [OpenTelemetry export](/gateway/opentelemetry).
- `otel.endpoint`: collector URL for OTel export.
- `otel.tracesEndpoint` / `otel.metricsEndpoint` / `otel.logsEndpoint`: optional signal-specific OTLP endpoints. When set, they override `otel.endpoint` for that signal only.

View File

@@ -117,12 +117,19 @@ diagnostics are enabled. It is for operational facts, not content.
The same diagnostic heartbeat records liveness samples when the Gateway keeps
running but the Node.js event loop or CPU looks saturated. These
`diagnostic.liveness.warning` events include event-loop delay, event-loop
utilization, CPU-core ratio, and active/waiting/queued session counts. Idle
samples stay in telemetry at `info` level. Liveness samples become Gateway
warnings only when work is waiting or queued, or when active work overlaps with
sustained event-loop delay. Transient max-delay spikes during otherwise healthy
background work stay in debug logs. They do not restart the Gateway by
themselves.
utilization, CPU-core ratio, active/waiting/queued session counts, the current
startup/runtime phase when known, recent phase spans, and bounded active/queued
work labels. Idle samples stay in telemetry at `info` level. Liveness samples
become Gateway warnings only when work is waiting or queued, or when active work
overlaps with sustained event-loop delay. Transient max-delay spikes during
otherwise healthy background work stay in debug logs. They do not restart the
Gateway by themselves.
Startup phases also emit `diagnostic.phase.completed` events with wall-clock and
CPU timing. Stalled embedded-run diagnostics mark `terminalProgressStale=true`
when the last bridge progress looked terminal, such as a raw response item or
response completion event, but the Gateway still considers the embedded run
active.
Inspect the live recorder:

View File

@@ -169,7 +169,9 @@ That stages grounded durable candidates into the short-term dreaming store while
Doctor also warns when `plugins.allow` is non-empty and tool policy uses
wildcard or plugin-owned tool entries. `tools.allow: ["*"]` only matches tools
from plugins that actually load; it does not bypass the exclusive plugin
allowlist.
allowlist. Doctor writes `plugins.bundledDiscovery: "compat"` for migrated
legacy allowlist configs to preserve existing bundled provider behavior, and
then points to the stricter `"allowlist"` setting.
</Accordion>
<Accordion title="2. Legacy config key migrations">
@@ -189,6 +191,7 @@ That stages grounded durable candidates into the short-term dreaming store while
- `routing.groupChat.requireMention` → `channels.whatsapp/telegram/imessage.groups."*".requireMention`
- `routing.groupChat.historyLimit` → `messages.groupChat.historyLimit`
- `routing.groupChat.mentionPatterns` → `messages.groupChat.mentionPatterns`
- `channels.telegram.requireMention` → `channels.telegram.groups."*".requireMention`
- configured-channel configs missing visible reply policy → `messages.groupChat.visibleReplies: "message_tool"`
- `routing.queue` → `messages.queue`
- `routing.bindings` → top-level `bindings`
@@ -268,6 +271,12 @@ That stages grounded durable candidates into the short-term dreaming store while
If the warning appears, choose the route you intended and edit config manually. Keep the warning as-is when PI Codex OAuth is intentional.
</Accordion>
<Accordion title="2g. Session route cleanup">
Doctor also scans the active sessions store for stale auto-created route state after you move the configured default/fallback model or runtime away from a plugin-owned route such as Codex.
`openclaw doctor --fix` can clear auto-created stale state such as `modelOverrideSource: "auto"` model pins, runtime model metadata, pinned harness ids, CLI session bindings, and auto auth-profile overrides when their owning route is no longer configured. Explicit user or legacy session model choices are reported for manual review and left untouched; switch them with `/model ...`, `/new`, or reset the session when that route is no longer intended.
</Accordion>
<Accordion title="3. Legacy state migrations (disk layout)">
Doctor can migrate older on-disk layouts into the current structure:
@@ -346,7 +355,7 @@ That stages grounded durable candidates into the short-term dreaming store while
<Accordion title="7b. Plugin install cleanup">
Doctor removes legacy OpenClaw-generated plugin dependency staging state in `openclaw doctor --fix` / `openclaw doctor --repair` mode. This covers stale generated dependency roots, old install-stage directories, package-local debris from earlier bundled-plugin dependency repair code, and orphaned or recovered managed npm copies of bundled `@openclaw/*` plugins that can shadow the current bundled manifest.
Doctor can also reinstall configured downloadable plugins when the config references them but the local plugin registry cannot find them. For the 2026.5.2 bundled-plugin externalization, doctor automatically installs downloadable plugins that the existing config already uses and then relies on `meta.lastTouchedVersion` to run that release pass only once. Gateway startup and config reload do not run package managers; plugin installs remain explicit doctor/install/update work.
Doctor can also reinstall missing downloadable plugins when config references them but the local plugin registry cannot find them. Examples include material `plugins.entries`, configured channel/provider/search settings, and configured agent runtimes. During package updates, doctor avoids running package-manager plugin repair while the core package is being swapped; run `openclaw doctor --fix` again after the update if a configured plugin still needs recovery. Gateway startup and config reload do not run package managers; plugin installs remain explicit doctor/install/update work.
</Accordion>
<Accordion title="8. Gateway service migrations and cleanup hints">

View File

@@ -15,6 +15,17 @@ OpenClaw has two log “surfaces”:
- **Console output** (what you see in the terminal / Debug UI).
- **File logs** (JSON lines) written by the gateway logger.
At startup, the Gateway logs the resolved default agent model together with the
mode defaults that affect new sessions, for example:
```text
agent model: openai-codex/gpt-5.5 (thinking=medium, fast=on)
```
`thinking` comes from the default agent, model params, or global agent default;
when it is unset, the startup summary shows `medium`. `fast` comes from the
default agent or model `fastMode` params.
## File-based logger
- Default rolling log file is under `/tmp/openclaw/` (one file per day): `openclaw-YYYY-MM-DD.log`

View File

@@ -216,11 +216,18 @@ OpenClaw classifies sessions by the work it can still observe:
still making progress.
- `session.stalled`: active work exists, but the active run has not reported
recent progress. Stalled embedded runs stay observe-only at first, then
abort-drain after at least 10 minutes and 5x `diagnostics.stuckSessionWarnMs`
with no progress so queued turns behind the lane can resume.
abort-drain after `diagnostics.stuckSessionAbortMs` with no progress so queued
turns behind the lane can resume. When unset, the abort threshold defaults to
the safer extended window of at least 10 minutes and 5x
`diagnostics.stuckSessionWarnMs`.
- `session.stuck`: stale session bookkeeping with no active work. This releases
the affected session lane immediately.
Recovery emits structured `session.recovery.requested` and
`session.recovery.completed` events. Diagnostic session state is marked idle
only after a mutating recovery outcome (`aborted` or `released`) and only if the
same processing generation is still current.
Only `session.stuck` emits the `openclaw.session.stuck` counter, the
`openclaw.session.stuck_age_ms` histogram, and the `openclaw.session.stuck`
span. Repeated `session.stuck` diagnostics back off while the session remains

View File

@@ -89,6 +89,17 @@ OPENCLAW_RUN_NODE_CPU_PROF_DIR=.artifacts/cli-cpu pnpm openclaw status
The source runner adds Node CPU profile flags and writes a `.cpuprofile` for the
command. Use this before adding temporary instrumentation to command code.
For startup stalls that look like synchronous filesystem or module-loader work,
add Node's sync I/O trace flag through the source runner:
```bash
OPENCLAW_TRACE_SYNC_IO=1 pnpm openclaw gateway --force
```
`pnpm gateway:watch` enables this flag by default for the watched Gateway child.
Set `OPENCLAW_TRACE_SYNC_IO=0` to suppress Node sync I/O trace output in watch
mode.
## Gateway watch mode
For fast iteration, run the gateway under the file watcher:
@@ -146,6 +157,11 @@ Use `--benchmark-dir <path>` when you want profiles somewhere else.
Use `--benchmark-no-force` when you want the benchmarked child to skip the
default `--force` port cleanup and fail fast if the Gateway port is already in
use.
Benchmark mode suppresses sync-I/O trace spam by default. Set
`OPENCLAW_TRACE_SYNC_IO=1` with `--benchmark` when you explicitly want both CPU
profiles and Node sync-I/O stack traces. In benchmark mode those trace blocks
are written to `gateway-watch-output.log` under the benchmark directory and
filtered from the terminal pane; normal Gateway logs remain visible.
The tmux wrapper carries common non-secret runtime selectors such as
`OPENCLAW_PROFILE`, `OPENCLAW_CONFIG_PATH`, `OPENCLAW_STATE_DIR`,

View File

@@ -191,11 +191,14 @@ troubleshooting, see the main [FAQ](/help/faq).
session overrides. Choosing a model that isn't in that list returns:
```
Model "provider/model" is not allowed. Use /model to list available models.
Model "provider/model" is not allowed. Use /models to list providers, or /models <provider> to list models.
Add it with: openclaw config set agents.defaults.models '{"provider/model":{}}' --strict-json --merge
```
That error is returned **instead of** a normal reply. Fix: add the model to
`agents.defaults.models`, remove the allowlist, or pick a model from `/model list`.
If the command also included `--runtime codex`, add the model first and then retry
the same `/model provider/model --runtime codex` command.
</Accordion>
@@ -463,6 +466,8 @@ Related: [/concepts/oauth](/concepts/oauth) (OAuth flows, token storage, multi-a
~/.openclaw/agents/<agentId>/agent/auth-profiles.json
```
To inspect saved profiles without dumping secrets, run `openclaw models auth list` (optionally `--provider <id>` or `--json`). See [Models CLI](/cli/models#openclaw-models-auth-list) for details.
</Accordion>
<Accordion title="What are typical profile IDs?">

View File

@@ -78,6 +78,7 @@ pnpm test:docker:plugin-lifecycle-matrix
pnpm test:docker:plugin-update
pnpm test:docker:upgrade-survivor
pnpm test:docker:published-upgrade-survivor
pnpm test:docker:update-restart-auth
pnpm test:docker:update-migration
```
@@ -103,6 +104,10 @@ Important lanes:
configures it through a baked `openclaw config set` recipe, updates it to the
candidate tarball, runs doctor, checks legacy cleanup, starts the Gateway, and
probes `/healthz`, `/readyz`, and RPC status.
- `test:docker:update-restart-auth` installs the candidate package, starts a
managed token-auth Gateway, unsets caller gateway auth env for
`openclaw update --yes --json`, and requires the candidate update command to
restart the Gateway before the normal probes.
- `test:docker:update-migration` is the cleanup-heavy published-update lane. It
starts from a configured Discord/Telegram-style user state, runs baseline
doctor so configured plugin dependencies have a chance to materialize, seeds
@@ -123,8 +128,8 @@ pnpm test:docker:published-upgrade-survivor
```
Available scenarios are `base`, `feishu-channel`, `bootstrap-persona`,
`plugin-deps-cleanup`, `configured-plugin-installs`, `tilde-log-path`, and
`versioned-runtime-deps`. In aggregate runs,
`plugin-deps-cleanup`, `configured-plugin-installs`,
`stale-source-plugin-shadow`, `tilde-log-path`, and `versioned-runtime-deps`. In aggregate runs,
`OPENCLAW_UPGRADE_SURVIVOR_SCENARIOS=reported-issues` expands to all reported
issue-shaped scenarios, including the configured-plugin install migration.
@@ -164,30 +169,41 @@ resolved release SHA. For post-publish proof, pass
`package_acceptance_package_spec=openclaw@YYYY.M.D` so the same upgrade matrix
targets the shipped npm package instead.
Release checks call Package Acceptance with the package/update/plugin set:
Release checks call Package Acceptance with the package/update/restart/plugin set:
```text
doctor-switch update-channel-switch upgrade-survivor published-upgrade-survivor plugins-offline plugin-update
doctor-switch update-channel-switch upgrade-survivor published-upgrade-survivor update-restart-auth plugins-offline plugin-update
```
They also pass:
When release soak is enabled, they also pass:
```text
published_upgrade_survivor_baselines=all-since-2026.4.23
published_upgrade_survivor_baselines=last-stable-4 2026.4.23 2026.5.2 2026.4.15
published_upgrade_survivor_scenarios=reported-issues
telegram_mode=mock-openai
```
This keeps package migration, update channel switching, stale plugin dependency
cleanup, offline plugin coverage, plugin update behavior, and Telegram package
QA on the same resolved artifact.
QA on the same resolved artifact without making the default release package gate
walk every published release.
`all-since-2026.4.23` is the Full Release CI upgrade sample: every stable npm-published release from `2026.4.23` through `latest`. For exhaustive published
`last-stable-4` resolves to the four latest stable npm-published OpenClaw
releases. Release package acceptance pins `2026.4.23` as the first plugin-update
compatibility boundary, `2026.5.2` as a plugin-architecture churn boundary, and
`2026.4.15` as an older 2026.4.1x published-update baseline; the resolver
dedupes pins that are already in the latest four. For exhaustive published
update migration coverage, use `all-since-2026.4.23` in the separate Update
Migration workflow instead of Full Release CI. `release-history` remains
available for manual wider sampling when you also want the legacy pre-date
anchor.
When multiple published-upgrade survivor baselines are selected, the reusable
Docker workflow shards each baseline into its own targeted runner job. Each
baseline shard still runs the selected scenario set, but logs and artifacts stay
per-baseline and wall time is bounded by the slowest shard instead of one large
serial job.
Run a package profile manually when validating a candidate before release:
```bash
@@ -197,7 +213,7 @@ gh workflow run package-acceptance.yml \
-f source=npm \
-f package_spec=openclaw@beta \
-f suite_profile=package \
-f published_upgrade_survivor_baselines=all-since-2026.4.23 \
-f published_upgrade_survivor_baselines="last-stable-4 2026.4.23 2026.5.2 2026.4.15" \
-f published_upgrade_survivor_scenarios=reported-issues \
-f telegram_mode=mock-openai
```
@@ -213,7 +229,7 @@ For release candidates, the default proof stack is:
1. `pnpm check:changed` and `pnpm test:changed` for source-level regressions.
2. `pnpm release:check` for package artifact integrity.
3. Package Acceptance `package` profile or the release-check custom package
lanes for install/update/plugin contracts.
lanes for install/update/restart/plugin contracts.
4. Cross-OS release checks for OS-specific installer, onboarding, and platform
behavior.
5. Live suites only when the changed surface touches provider or hosted-service
@@ -234,7 +250,8 @@ Compatibility leniency is narrow and time boxed:
warning or skipping.
Do not add new startup migrations for these old shapes. Add or extend a doctor
repair, then prove it with `upgrade-survivor` or `published-upgrade-survivor`.
repair, then prove it with `upgrade-survivor`, `published-upgrade-survivor`, or
`update-restart-auth` when the update command owns the restart.
## Adding coverage
@@ -246,6 +263,7 @@ can fail for the right reason:
checker test.
- CLI install/update behavior: Docker lane assertion or fixture.
- Published-release migration behavior: `published-upgrade-survivor` scenario.
- Update-owned restart behavior: `update-restart-auth`.
- Registry/package source behavior: `test:docker:plugins` fixture or ClawHub
fixture server.
- Dependency layout or cleanup behavior: assert both runtime execution and the

View File

@@ -112,7 +112,9 @@ These commands sit beside the main test suites when you need QA-lab realism:
CI runs QA Lab in dedicated workflows. Agentic parity is nested under
`QA-Lab - All Lanes` and release validation, not a standalone PR workflow.
Broad validation should use `Full Release Validation` with
`rerun_group=qa-parity` or the release-checks QA group. `QA-Lab - All Lanes`
`rerun_group=qa-parity` or the release-checks QA group. Stable/default release
checks keep exhaustive live/Docker soak behind `run_release_soak=true`; the
`full` profile forces soak on. `QA-Lab - All Lanes`
runs nightly on `main` and from manual dispatch with the mock parity lane, live
Matrix lane, Convex-managed live Telegram lane, and Convex-managed live Discord
lane as parallel jobs. Scheduled QA and release checks pass Matrix
@@ -144,6 +146,14 @@ inside every shard.
`aimock` starts a local AIMock-backed provider server for experimental
fixture and protocol-mock coverage without replacing the scenario-aware
`mock-openai` lane.
- `pnpm test:plugins:kitchen-sink-live`
- Runs the live OpenAI Kitchen Sink plugin gauntlet through QA Lab. It
installs the external Kitchen Sink package, verifies the plugin SDK surface
inventory, probes `/healthz` and `/readyz`, records gateway CPU/RSS
evidence, runs a live OpenAI turn, and checks adversarial diagnostics.
Requires live OpenAI auth such as `OPENAI_API_KEY`. In hydrated Testbox
sessions it automatically sources the Testbox live-auth profile when the
`openclaw-testbox-env` helper is present.
- `pnpm test:gateway:cpu-scenarios`
- Runs the gateway startup bench plus a small mock QA Lab scenario pack
(`channel-chat-baseline`, `memory-failure-fallback`,
@@ -630,10 +640,10 @@ The live-model Docker runners also bind-mount only the needed CLI auth homes (or
- Observability smoke: `pnpm qa:otel:smoke` is a private QA source-checkout lane. It is intentionally not part of package Docker release lanes because the npm tarball omits QA Lab.
- Open WebUI live smoke: `pnpm test:docker:openwebui` (script: `scripts/e2e/openwebui-docker.sh`)
- Onboarding wizard (TTY, full scaffolding): `pnpm test:docker:onboard` (script: `scripts/e2e/onboard-docker.sh`)
- Npm tarball onboarding/channel/agent smoke: `pnpm test:docker:npm-onboard-channel-agent` installs the packed OpenClaw tarball globally in Docker, configures OpenAI via env-ref onboarding plus Telegram by default, runs doctor, and runs one mocked OpenAI agent turn. Reuse a prebuilt tarball with `OPENCLAW_CURRENT_PACKAGE_TGZ=/path/to/openclaw-*.tgz`, skip the host rebuild with `OPENCLAW_NPM_ONBOARD_HOST_BUILD=0`, or switch channel with `OPENCLAW_NPM_ONBOARD_CHANNEL=discord`.
- Npm tarball onboarding/channel/agent smoke: `pnpm test:docker:npm-onboard-channel-agent` installs the packed OpenClaw tarball globally in Docker, configures OpenAI via env-ref onboarding plus Telegram by default, runs doctor, and runs one mocked OpenAI agent turn. Reuse a prebuilt tarball with `OPENCLAW_CURRENT_PACKAGE_TGZ=/path/to/openclaw-*.tgz`, skip the host rebuild with `OPENCLAW_NPM_ONBOARD_HOST_BUILD=0`, or switch channel with `OPENCLAW_NPM_ONBOARD_CHANNEL=discord` or `OPENCLAW_NPM_ONBOARD_CHANNEL=slack`.
- Update channel switch smoke: `pnpm test:docker:update-channel-switch` installs the packed OpenClaw tarball globally in Docker, switches from package `stable` to git `dev`, verifies the persisted channel and plugin post-update work, then switches back to package `stable` and checks update status.
- Upgrade survivor smoke: `pnpm test:docker:upgrade-survivor` installs the packed OpenClaw tarball over a dirty old-user fixture with agents, channel config, plugin allowlists, stale plugin dependency state, and existing workspace/session files. It runs package update plus non-interactive doctor without live provider or channel keys, then starts a loopback Gateway and checks config/state preservation plus startup/status budgets.
- Published upgrade survivor smoke: `pnpm test:docker:published-upgrade-survivor` installs `openclaw@latest` by default, seeds realistic existing-user files, configures that baseline with a baked command recipe, validates the resulting config, updates that published install to the candidate tarball, runs non-interactive doctor, writes `.artifacts/upgrade-survivor/summary.json`, then starts a loopback Gateway and checks configured intents, state preservation, startup, `/healthz`, `/readyz`, and RPC status budgets. Override one baseline with `OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPEC`, ask the aggregate scheduler to expand exact baselines with `OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPECS` such as `all-since-2026.4.23`, and expand issue-shaped fixtures with `OPENCLAW_UPGRADE_SURVIVOR_SCENARIOS` such as `reported-issues`; the reported-issues set includes `configured-plugin-installs` for automatic external OpenClaw plugin install repair. Package Acceptance exposes those as `published_upgrade_survivor_baseline`, `published_upgrade_survivor_baselines`, and `published_upgrade_survivor_scenarios`.
- Published upgrade survivor smoke: `pnpm test:docker:published-upgrade-survivor` installs `openclaw@latest` by default, seeds realistic existing-user files, configures that baseline with a baked command recipe, validates the resulting config, updates that published install to the candidate tarball, runs non-interactive doctor, writes `.artifacts/upgrade-survivor/summary.json`, then starts a loopback Gateway and checks configured intents, state preservation, startup, `/healthz`, `/readyz`, and RPC status budgets. Override one baseline with `OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPEC`, ask the aggregate scheduler to expand exact local baselines with `OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPECS` such as `openclaw@2026.5.2 openclaw@2026.4.23 openclaw@2026.4.15`, and expand issue-shaped fixtures with `OPENCLAW_UPGRADE_SURVIVOR_SCENARIOS` such as `reported-issues`; the reported-issues set includes `configured-plugin-installs` for automatic external OpenClaw plugin install repair. Package Acceptance exposes those as `published_upgrade_survivor_baseline`, `published_upgrade_survivor_baselines`, and `published_upgrade_survivor_scenarios`, resolves meta baseline tokens such as `last-stable-4` or `all-since-2026.4.23`, and Full Release Validation expands the release-soak package gate to `last-stable-4 2026.4.23 2026.5.2 2026.4.15` plus `reported-issues`.
- Session runtime context smoke: `pnpm test:docker:session-runtime-context` verifies hidden runtime context transcript persistence plus doctor repair of affected duplicated prompt-rewrite branches.
- Bun global install smoke: `bash scripts/e2e/bun-global-install-smoke.sh` packs the current tree, installs it with `bun install -g` in an isolated home, and verifies `openclaw infer image providers --json` returns bundled image providers instead of hanging. Reuse a prebuilt tarball with `OPENCLAW_BUN_GLOBAL_SMOKE_PACKAGE_TGZ=/path/to/openclaw-*.tgz`, skip the host build with `OPENCLAW_BUN_GLOBAL_SMOKE_HOST_BUILD=0`, or copy `dist/` from a built Docker image with `OPENCLAW_BUN_GLOBAL_SMOKE_DIST_IMAGE=openclaw-dockerfile-smoke:local`.
- Installer Docker smoke: `bash scripts/test-install-sh-docker.sh` shares one npm cache across its root, update, and direct-npm containers. Update smoke defaults to npm `latest` as the stable baseline before upgrading to the candidate tarball. Override with `OPENCLAW_INSTALL_SMOKE_UPDATE_BASELINE=2026.4.22` locally, or with the Install Smoke workflow's `update_baseline_version` input on GitHub. Non-root installer checks keep an isolated npm cache so root-owned cache entries do not mask user-local install behavior. Set `OPENCLAW_INSTALL_SMOKE_NPM_CACHE_DIR=/path/to/cache` to reuse the root/update/direct-npm cache across local reruns.

View File

@@ -245,8 +245,40 @@ Full guide: [Getting Started](/start/getting-started)
## Windows companion app
We do not have a Windows companion app yet. Contributions are welcome if you want
contributions to make it happen.
We do not have a Windows companion app yet. Contributions are welcome if you want to
help make it happen.
## Git and GitHub connectivity (contributors)
Some networks block or throttle HTTPS to GitHub. If `git clone` fails with timeouts
or connection resets, try another network, a VPN, or an HTTP/HTTPS proxy your
organization provides.
If `gh auth login` fails during the browser device flow (for example a timeout
reaching `github.com:443`), authenticate with a personal access token instead:
1. Create a token with at least the `repo` scope (classic PAT) or equivalent
fine-grained access.
2. In PowerShell for the current session:
```powershell
$env:GH_TOKEN="<your-token>"
gh auth status
gh auth setup-git
```
3. If `gh auth status` warns about missing `read:org`, mint a token that includes
that scope and re-assign the variable:
```powershell
$env:GH_TOKEN="<your-token-with-repo-and-read:org>"
gh auth status
```
`gh auth refresh -s read:org` only applies when you authenticated via `gh auth login`
and have stored credentials to refresh (not when using `GH_TOKEN`).
Never commit tokens or paste them into issues or pull requests.
## Related

View File

@@ -262,8 +262,8 @@ dual-format packages from being partially installed as bundles.
downloadable through the plugin installer. Gateway startup never runs a
package manager for them.
- `openclaw doctor --fix` removes legacy staged dependency directories and can
install configured downloadable plugins that are missing from the local
plugin index.
recover downloadable plugins that are missing from the local plugin index when
config references them.
## Security

View File

@@ -845,6 +845,10 @@ Common forms:
- `/codex mcp` lists Codex app-server MCP server status.
- `/codex skills` lists Codex app-server skills.
When Codex reports a usage-limit failure, OpenClaw includes the next
app-server reset time when Codex provided one. Use `/codex account` in the same
conversation to inspect the current account and rate-limit windows.
### Common debugging workflow
When a Codex-backed agent does something surprising in Telegram, Discord, Slack,

View File

@@ -85,9 +85,10 @@ openclaw plugins install <source>
openclaw doctor --fix
```
`doctor --fix` can clean legacy OpenClaw-generated dependency state and install
configured downloadable plugins that are missing from the local install records.
It does not repair dependencies for an already-installed local plugin.
`doctor --fix` can clean legacy OpenClaw-generated dependency state and recover
downloadable plugins that are missing from the local install records when config
references them. Doctor does not repair dependencies for an already-installed
local plugin.
## Bundled plugins

View File

@@ -92,7 +92,9 @@ when it was previously pinned to an exact version or tag.
When `openclaw update` runs on the beta channel, default-line npm and ClawHub
plugin records try the matching plugin `@beta` release first. If that beta
release does not exist, OpenClaw falls back to the recorded default/latest spec.
Exact versions and explicit tags such as `@rc` or `@beta` are preserved.
For npm plugins, OpenClaw also falls back when the beta package exists but fails
install validation. Exact versions and explicit tags such as `@rc` or `@beta`
are preserved.
## Uninstall plugins

View File

@@ -18,6 +18,16 @@ Adds the WhatsApp channel surface for sending and receiving OpenClaw messages.
channels: whatsapp
## Windows install note
On Windows, the WhatsApp plugin needs Git on `PATH` during npm install because one of its Baileys/libsignal dependencies is fetched from a git URL. Install Git for Windows, then restart the shell and rerun the install:
```powershell
winget install --id Git.Git -e
```
Portable Git also works if its `bin` directory is on `PATH`.
## Related docs
- [whatsapp](/channels/whatsapp)

View File

@@ -257,6 +257,9 @@ AI CLI backend such as `codex-cli`.
plugin default before running the CLI.
- Use `normalizeConfig` when a backend needs compatibility rewrites after merge
(for example normalizing old flag shapes).
- Use `resolveExecutionArgs` for request-scoped argv rewrites that belong to
the CLI dialect, such as mapping OpenClaw thinking levels to a native effort
flag.
### Exclusive slots

View File

@@ -211,7 +211,9 @@ does **not** inject those OpenRouter-specific headers or Anthropic cache markers
On verified OpenRouter routes, `openrouter/deepseek/deepseek-v4-flash` and
`openrouter/deepseek/deepseek-v4-pro` fill missing `reasoning_content` on
replayed assistant turns so thinking/tool conversations keep DeepSeek V4's
required follow-up shape.
required follow-up shape. OpenClaw sends OpenRouter-supported
`reasoning_effort` values for these routes; `xhigh` is the highest advertised
level, and stale `max` overrides are mapped to `xhigh`.
</Accordion>
<Accordion title="OpenAI-only request shaping">

View File

@@ -114,11 +114,13 @@ the maintainer-only release runbook.
- Run the manual `Full Release Validation` workflow before release approval to
kick off all pre-release test boxes from one entrypoint. It accepts a branch,
tag, or full commit SHA, dispatches manual `CI`, and dispatches
`OpenClaw Release Checks` for install smoke, package acceptance, Docker
release-path suites, live/E2E, OpenWebUI, QA Lab parity, Matrix, and Telegram
lanes. With `release_profile=full` and `rerun_group=all`, it also runs package
Telegram E2E against the `release-package-under-test` artifact from release
checks. Provide `npm_telegram_package_spec` after publishing when the same
`OpenClaw Release Checks` for install smoke, package acceptance, cross-OS
package checks, QA Lab parity, Matrix, and Telegram lanes. Stable/default runs
keep exhaustive live/E2E and Docker release-path soak behind
`run_release_soak=true`; `release_profile=full` forces soak on. With
`release_profile=full` and `rerun_group=all`, it also runs package Telegram
E2E against the `release-package-under-test` artifact from release checks.
Provide `npm_telegram_package_spec` after publishing when the same
Telegram E2E should prove the published npm package too. Provide
`package_acceptance_package_spec` after publishing when Package Acceptance
should run its package/update matrix against the shipped npm package instead
@@ -139,11 +141,13 @@ the maintainer-only release runbook.
`telegram_mode=mock-openai` or `telegram_mode=live-frontier`. When the
selected Docker lanes include `published-upgrade-survivor`, the package
artifact is the candidate and `published_upgrade_survivor_baseline` selects
the published baseline.
the published baseline. `update-restart-auth` uses the candidate package as
both the installed CLI and the package-under-test so it exercises the
candidate update command's managed restart path.
Example: `gh workflow run package-acceptance.yml --ref main -f workflow_ref=main -f source=npm -f package_spec=openclaw@beta -f suite_profile=product -f published_upgrade_survivor_baseline=openclaw@2026.4.26 -f telegram_mode=mock-openai`
Common profiles:
- `smoke`: install/channel/agent, gateway network, and config reload lanes
- `package`: artifact-native package/update/plugin lanes without OpenWebUI or live ClawHub
- `package`: artifact-native package/update/restart/plugin lanes without OpenWebUI or live ClawHub
- `product`: package profile plus MCP channels, cron/subagent cleanup,
OpenAI web search, and OpenWebUI
- `full`: Docker release-path chunks with OpenWebUI
@@ -293,8 +297,8 @@ parent `release-package-under-test` artifact for package-facing checks, and
dispatches standalone package Telegram E2E when `release_profile=full` with
`rerun_group=all` or when `npm_telegram_package_spec` is set. `OpenClaw Release
Checks` then fans out install smoke, cross-OS release checks, live/E2E Docker
release-path coverage, Package Acceptance with Telegram package QA, QA Lab
parity, live Matrix, and live Telegram. A full run is only acceptable when the
release-path coverage when soak is enabled, Package Acceptance with Telegram
package QA, QA Lab parity, live Matrix, and live Telegram. A full run is only acceptable when the
`Full Release Validation`
summary shows `normal_ci` and `release_checks` as successful. In full/all mode,
the `npm_telegram` child must also be successful; outside full/all it is skipped
@@ -318,10 +322,18 @@ Use `release_profile` to select live/provider breadth:
- `stable`: minimum plus stable provider/backend coverage for release approval
- `full`: stable plus broad advisory provider/media coverage
Use `run_release_soak=true` with `stable` when the release-blocking lanes are
green and you want the exhaustive live/E2E, Docker release-path, and
bounded published upgrade-survivor sweep before promotion. That sweep covers
the latest four stable packages plus pinned `2026.4.23` and `2026.5.2`
baselines plus older `2026.4.15` coverage, with duplicate baselines removed and
each baseline sharded into its own Docker runner job. `full` implies
`run_release_soak=true`.
`OpenClaw Release Checks` uses the trusted workflow ref to resolve the target
ref once as `release-package-under-test` and reuses that artifact in both
release-path Docker checks and Package Acceptance. This keeps all
package-facing boxes on the same bytes and avoids repeated package builds.
ref once as `release-package-under-test` and reuses that artifact in cross-OS,
Package Acceptance, and release-path Docker checks when soak runs. This keeps
all package-facing boxes on the same bytes and avoids repeated package builds.
The cross-OS OpenAI install smoke uses `OPENCLAW_CROSS_OS_OPENAI_MODEL` when the
repo/org variable is set, otherwise `openai/gpt-5.4`, because this lane is
proving package install, onboarding, gateway startup, and one live agent turn
@@ -372,7 +384,10 @@ runs only the release-only plugin child, `release-checks` runs every release
box, and the narrower release groups are `install-smoke`, `cross-os`,
`live-e2e`, `package`, `qa`, `qa-parity`, `qa-live`, and `npm-telegram`.
Focused `npm-telegram` reruns require `npm_telegram_package_spec`; full/all runs
with `release_profile=full` use the release-checks package artifact.
with `release_profile=full` use the release-checks package artifact. Focused
cross-OS reruns can add `cross_os_suite_filter=windows/packaged-upgrade` or
another OS/suite filter. QA release-check failures are advisory; a QA-only
failure does not block release validation.
### Vitest
@@ -473,12 +488,14 @@ Supported candidate sources:
`OpenClaw Release Checks` runs Package Acceptance with `source=artifact`, the
prepared release package artifact, `suite_profile=custom`,
`docker_lanes=doctor-switch update-channel-switch upgrade-survivor published-upgrade-survivor plugins-offline plugin-update`,
`published_upgrade_survivor_baselines=all-since-2026.4.23`,
`published_upgrade_survivor_scenarios=reported-issues`, and
`telegram_mode=mock-openai`. Package Acceptance keeps migration, update, stale
plugin dependency cleanup, offline plugin fixtures, plugin update, and Telegram
package QA against the same resolved tarball. The upgrade matrix covers every stable npm-published baseline from `2026.4.23` through `latest`; use
`docker_lanes=doctor-switch update-channel-switch upgrade-survivor published-upgrade-survivor update-restart-auth plugins-offline plugin-update`,
`telegram_mode=mock-openai`. Package Acceptance keeps migration, update,
configured-auth update restart, stale plugin dependency cleanup, offline plugin
fixtures, plugin update, and Telegram package QA against the same resolved
tarball. Blocking release checks use the default latest published package
baseline; `run_release_soak=true` or
`release_profile=full` expands to every stable npm-published baseline from
`2026.4.23` through `latest` plus reported-issue fixtures. Use
Package Acceptance with `source=npm` for an already shipped candidate, or
`source=ref`/`source=artifact` for a SHA-backed local npm tarball before
publish. It is the GitHub-native
@@ -522,8 +539,8 @@ Common package profiles:
- `smoke`: quick package install/channel/agent, gateway network, and config
reload lanes
- `package`: install/update/plugin package contracts without live ClawHub; this is the release-check
default
- `package`: install/update/restart/plugin package contracts without live
ClawHub; this is the release-check default
- `product`: `package` plus MCP channels, cron/subagent cleanup, OpenAI web
search, and OpenWebUI
- `full`: Docker release-path chunks with OpenWebUI
@@ -615,6 +632,9 @@ OpenClaw package must not be published.
- `ref`: branch, tag, or full commit SHA to validate. Secret-bearing checks
require the resolved commit to be reachable from an OpenClaw branch or
release tag.
- `run_release_soak`: opt into exhaustive live/E2E, Docker release-path, and
all-since upgrade-survivor soak on stable/default release checks. It is forced
on by `release_profile=full`.
Rules:

View File

@@ -27,6 +27,11 @@ Child workflows use the trusted workflow ref for the harness and the input
`ref` for the candidate under test. That keeps new validation logic available
when validating an older release branch or tag.
By default, `release_profile=stable` runs the release-blocking lanes and skips
the exhaustive live/Docker soak. Pass `run_release_soak=true` to include the
soak lanes on a stable run. `release_profile=full` always enables soak lanes so
the broad advisory profile never drops coverage silently.
Package Acceptance normally builds the candidate tarball from the resolved
`ref`, including full-SHA runs dispatched with `pnpm ci:full-release`. After
publish, pass `package_acceptance_package_spec=openclaw@YYYY.M.D` (or
@@ -35,15 +40,15 @@ the shipped npm package instead.
## Top-level stages
| Stage | Details |
| -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Target resolution | **Job:** `Resolve target ref`<br />**Child workflow:** none<br />**Proves:** resolves the release branch, tag, or full commit SHA and records selected inputs.<br />**Rerun:** rerun the umbrella if this fails. |
| Vitest and normal CI | **Job:** `Run normal full CI`<br />**Child workflow:** `CI`<br />**Proves:** manual full CI graph against the target ref, including Linux Node lanes, bundled plugin shards, channel contracts, Node 22 compatibility, `check`, `check-additional`, build smoke, docs checks, Python skills, Windows, macOS, Control UI i18n, and Android via the umbrella.<br />**Rerun:** `rerun_group=ci`. |
| Plugin prerelease | **Job:** `Run plugin prerelease validation`<br />**Child workflow:** `Plugin Prerelease`<br />**Proves:** release-only plugin static checks, agentic plugin coverage, full extension batch shards, and plugin prerelease Docker lanes.<br />**Rerun:** `rerun_group=plugin-prerelease`. |
| Release checks | **Job:** `Run release/live/Docker/QA validation`<br />**Child workflow:** `OpenClaw Release Checks`<br />**Proves:** install smoke, cross-OS package checks, live/E2E suites, Docker release-path chunks, Package Acceptance, QA Lab parity, live Matrix, and live Telegram.<br />**Rerun:** `rerun_group=release-checks` or a narrower release-checks handle. |
| Package artifact | **Job:** `Prepare release package artifact`<br />**Child workflow:** none<br />**Proves:** creates the parent `release-package-under-test` tarball early enough for package-facing checks that do not need to wait for `OpenClaw Release Checks`.<br />**Rerun:** rerun the umbrella or provide `npm_telegram_package_spec` for `rerun_group=npm-telegram`. |
| Package Telegram | **Job:** `Run package Telegram E2E`<br />**Child workflow:** `NPM Telegram Beta E2E`<br />**Proves:** parent-artifact-backed Telegram package proof for `rerun_group=all` with `release_profile=full`, or published-package Telegram proof when `npm_telegram_package_spec` is set.<br />**Rerun:** `rerun_group=npm-telegram` with `npm_telegram_package_spec`. |
| Umbrella verifier | **Job:** `Verify full validation`<br />**Child workflow:** none<br />**Proves:** re-checks recorded child run conclusions and appends slowest-job tables from child workflows.<br />**Rerun:** rerun only this job after rerunning a failed child to green. |
| Stage | Details |
| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Target resolution | **Job:** `Resolve target ref`<br />**Child workflow:** none<br />**Proves:** resolves the release branch, tag, or full commit SHA and records selected inputs.<br />**Rerun:** rerun the umbrella if this fails. |
| Vitest and normal CI | **Job:** `Run normal full CI`<br />**Child workflow:** `CI`<br />**Proves:** manual full CI graph against the target ref, including Linux Node lanes, bundled plugin shards, channel contracts, Node 22 compatibility, `check`, `check-additional`, build smoke, docs checks, Python skills, Windows, macOS, Control UI i18n, and Android via the umbrella.<br />**Rerun:** `rerun_group=ci`. |
| Plugin prerelease | **Job:** `Run plugin prerelease validation`<br />**Child workflow:** `Plugin Prerelease`<br />**Proves:** release-only plugin static checks, agentic plugin coverage, full extension batch shards, and plugin prerelease Docker lanes.<br />**Rerun:** `rerun_group=plugin-prerelease`. |
| Release checks | **Job:** `Run release/live/Docker/QA validation`<br />**Child workflow:** `OpenClaw Release Checks`<br />**Proves:** install smoke, cross-OS package checks, Package Acceptance, QA Lab parity, live Matrix, and live Telegram. With `run_release_soak=true` or `release_profile=full`, also runs exhaustive live/E2E suites and Docker release-path chunks.<br />**Rerun:** `rerun_group=release-checks` or a narrower release-checks handle. |
| Package artifact | **Job:** `Prepare release package artifact`<br />**Child workflow:** none<br />**Proves:** creates the parent `release-package-under-test` tarball early enough for package-facing checks that do not need to wait for `OpenClaw Release Checks`.<br />**Rerun:** rerun the umbrella or provide `npm_telegram_package_spec` for `rerun_group=npm-telegram`. |
| Package Telegram | **Job:** `Run package Telegram E2E`<br />**Child workflow:** `NPM Telegram Beta E2E`<br />**Proves:** parent-artifact-backed Telegram package proof for `rerun_group=all` with `release_profile=full`, or published-package Telegram proof when `npm_telegram_package_spec` is set.<br />**Rerun:** `rerun_group=npm-telegram` with `npm_telegram_package_spec`. |
| Umbrella verifier | **Job:** `Verify full validation`<br />**Child workflow:** none<br />**Proves:** re-checks recorded child run conclusions and appends slowest-job tables from child workflows.<br />**Rerun:** rerun only this job after rerunning a failed child to green. |
For `ref=main` and `rerun_group=all`, a newer umbrella supersedes an older one.
When the parent is cancelled, its monitor cancels any child workflow it already
@@ -56,19 +61,19 @@ default.
once and prepares a shared `release-package-under-test` artifact when package
or Docker-facing stages need it.
| Stage | Details |
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Release target | **Job:** `Resolve target ref`<br />**Backing workflow:** none<br />**Tests:** selected ref, optional expected SHA, profile, rerun group, and focused live suite filter.<br />**Rerun:** `rerun_group=release-checks`. |
| Package artifact | **Job:** `Prepare release package artifact`<br />**Backing workflow:** none<br />**Tests:** packs or resolves one candidate tarball and uploads `release-package-under-test` for downstream package-facing checks.<br />**Rerun:** the affected package, cross-OS, or live/E2E group. |
| Install smoke | **Job:** `Run install smoke`<br />**Backing workflow:** `Install Smoke`<br />**Tests:** full install path with root Dockerfile smoke image reuse, QR package install, root and gateway Docker smokes, installer Docker tests, Bun global install image-provider smoke, and fast bundled-plugin install/uninstall E2E.<br />**Rerun:** `rerun_group=install-smoke`. |
| Cross-OS | **Job:** `cross_os_release_checks`<br />**Backing workflow:** `OpenClaw Cross-OS Release Checks (Reusable)`<br />**Tests:** fresh and upgrade lanes on Linux, Windows, and macOS for the selected provider and mode, using the candidate tarball plus a baseline package.<br />**Rerun:** `rerun_group=cross-os`. |
| Repo and live E2E | **Job:** `Run repo/live E2E validation`<br />**Backing workflow:** `OpenClaw Live And E2E Checks (Reusable)`<br />**Tests:** repository E2E, live cache, OpenAI websocket streaming, native live provider and plugin shards, and Docker-backed live model/backend/gateway harnesses selected by `release_profile`.<br />**Rerun:** `rerun_group=live-e2e`, optionally with `live_suite_filter`. |
| Docker release path | **Job:** `Run Docker release-path validation`<br />**Backing workflow:** `OpenClaw Live And E2E Checks (Reusable)`<br />**Tests:** release-path Docker chunks against the shared package artifact.<br />**Rerun:** `rerun_group=live-e2e`. |
| Package Acceptance | **Job:** `Run package acceptance`<br />**Backing workflow:** `Package Acceptance`<br />**Tests:** offline plugin package fixtures, plugin update, mock-OpenAI Telegram package acceptance, and published-upgrade survivor checks from every stable npm release at or after `2026.4.23` against the same tarball.<br />**Rerun:** `rerun_group=package`. |
| QA parity | **Job:** `Run QA Lab parity lane` and `Run QA Lab parity report`<br />**Backing workflow:** direct jobs<br />**Tests:** candidate and baseline agentic parity packs, then the parity report.<br />**Rerun:** `rerun_group=qa-parity` or `rerun_group=qa`. |
| QA live Matrix | **Job:** `Run QA Lab live Matrix lane`<br />**Backing workflow:** direct job<br />**Tests:** fast live Matrix QA profile in the `qa-live-shared` environment.<br />**Rerun:** `rerun_group=qa-live` or `rerun_group=qa`. |
| QA live Telegram | **Job:** `Run QA Lab live Telegram lane`<br />**Backing workflow:** direct job<br />**Tests:** live Telegram QA with Convex CI credential leases.<br />**Rerun:** `rerun_group=qa-live` or `rerun_group=qa`. |
| Release verifier | **Job:** `Verify release checks`<br />**Backing workflow:** none<br />**Tests:** required release-check jobs for the selected rerun group.<br />**Rerun:** rerun after focused child jobs pass. |
| Stage | Details |
| ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Release target | **Job:** `Resolve target ref`<br />**Backing workflow:** none<br />**Tests:** selected ref, optional expected SHA, profile, rerun group, and focused live suite filter.<br />**Rerun:** `rerun_group=release-checks`. |
| Package artifact | **Job:** `Prepare release package artifact`<br />**Backing workflow:** none<br />**Tests:** packs or resolves one candidate tarball and uploads `release-package-under-test` for downstream package-facing checks.<br />**Rerun:** the affected package, cross-OS, or live/E2E group. |
| Install smoke | **Job:** `Run install smoke`<br />**Backing workflow:** `Install Smoke`<br />**Tests:** full install path with root Dockerfile smoke image reuse, QR package install, root and gateway Docker smokes, installer Docker tests, Bun global install image-provider smoke, and fast bundled-plugin install/uninstall E2E.<br />**Rerun:** `rerun_group=install-smoke`. |
| Cross-OS | **Job:** `cross_os_release_checks`<br />**Backing workflow:** `OpenClaw Cross-OS Release Checks (Reusable)`<br />**Tests:** fresh and upgrade lanes on Linux, Windows, and macOS for the selected provider and mode, using the candidate tarball plus a baseline package.<br />**Rerun:** `rerun_group=cross-os`. |
| Repo and live E2E | **Job:** `Run repo/live E2E validation`<br />**Backing workflow:** `OpenClaw Live And E2E Checks (Reusable)`<br />**Tests:** repository E2E, live cache, OpenAI websocket streaming, native live provider and plugin shards, and Docker-backed live model/backend/gateway harnesses selected by `release_profile`.<br />**Runs:** `run_release_soak=true`, `release_profile=full`, or focused `rerun_group=live-e2e`.<br />**Rerun:** `rerun_group=live-e2e`, optionally with `live_suite_filter`. |
| Docker release path | **Job:** `Run Docker release-path validation`<br />**Backing workflow:** `OpenClaw Live And E2E Checks (Reusable)`<br />**Tests:** release-path Docker chunks against the shared package artifact.<br />**Runs:** `run_release_soak=true`, `release_profile=full`, or focused `rerun_group=live-e2e`.<br />**Rerun:** `rerun_group=live-e2e`. |
| Package Acceptance | **Job:** `Run package acceptance`<br />**Backing workflow:** `Package Acceptance`<br />**Tests:** offline plugin package fixtures, plugin update, mock-OpenAI Telegram package acceptance, and published-upgrade survivor checks against the same tarball. Blocking release checks use the default latest published baseline; soak checks expand to every stable npm release at or after `2026.4.23` plus reported-issue fixtures.<br />**Rerun:** `rerun_group=package`. |
| QA parity | **Job:** `Run QA Lab parity lane` and `Run QA Lab parity report`<br />**Backing workflow:** direct jobs<br />**Tests:** candidate and baseline agentic parity packs, then the parity report.<br />**Rerun:** `rerun_group=qa-parity` or `rerun_group=qa`. |
| QA live Matrix | **Job:** `Run QA Lab live Matrix lane`<br />**Backing workflow:** direct job<br />**Tests:** fast live Matrix QA profile in the `qa-live-shared` environment.<br />**Rerun:** `rerun_group=qa-live` or `rerun_group=qa`. |
| QA live Telegram | **Job:** `Run QA Lab live Telegram lane`<br />**Backing workflow:** direct job<br />**Tests:** live Telegram QA with Convex CI credential leases.<br />**Rerun:** `rerun_group=qa-live` or `rerun_group=qa`. |
| Release verifier | **Job:** `Verify release checks`<br />**Backing workflow:** none<br />**Tests:** required release-check jobs for the selected rerun group.<br />**Rerun:** rerun after focused child jobs pass. |
## Docker release-path chunks
@@ -93,10 +98,11 @@ commands with package artifact and image reuse inputs when available.
`release_profile` mostly controls live/provider breadth inside release checks.
It does not remove normal full CI, Plugin Prerelease, install smoke, package
acceptance, QA Lab, or Docker release-path chunks. `full` also makes the
umbrella run package Telegram E2E against the parent release package artifact when
`rerun_group=all`, so a full pre-publish candidate does not silently skip that
Telegram package lane.
acceptance, or QA Lab. For `stable`, exhaustive repo/live E2E and Docker
release-path chunks are soak coverage and run when `run_release_soak=true`.
`full` forces soak coverage on and also makes the umbrella run package Telegram
E2E against the parent release package artifact when `rerun_group=all`, so a full
pre-publish candidate does not silently skip that Telegram package lane.
| Profile | Intended use | Included live/provider coverage |
| --------- | --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
@@ -152,6 +158,17 @@ Valid filter ids are defined in the reusable live/E2E workflow, including
The `live-gateway-advisory-docker` handle is an aggregate rerun handle for its
three provider shards, so it still fans out to all advisory Docker gateway jobs.
Use `cross_os_suite_filter` with `rerun_group=cross-os` when one cross-OS lane
failed. The filter accepts an OS id, a suite id, or an OS/suite pair, for
example `windows/packaged-upgrade`, `windows`, or `packaged-fresh`. Cross-OS
summaries include per-phase timings for packaged upgrade lanes, and long-running
commands print heartbeat lines so a stuck Windows update is visible before the
job timeout.
QA release-check lanes are advisory. A QA-only failure is reported as a warning
and does not block the release-check verifier; rerun `rerun_group=qa`,
`qa-parity`, or `qa-live` when you need fresh QA evidence.
## Evidence to keep
Keep the `Full Release Validation` summary as the release-level index. It links

View File

@@ -85,7 +85,7 @@ Session persistence has automatic maintenance controls (`session.maintenance`) f
- `maxDiskBytes`: optional sessions-directory budget
- `highWaterBytes`: optional target after cleanup (default `80%` of `maxDiskBytes`)
Normal Gateway writes flow through a per-store session writer that serializes in-process mutations without taking a runtime file lock. Hot-path patch helpers borrow the validated mutable cache while they hold that writer slot, so large `sessions.json` files are not cloned or reread for every metadata update. Runtime code should prefer `updateSessionStore(...)` or `updateSessionStoreEntry(...)`; direct whole-store saves are compatibility and offline-maintenance tools. When a Gateway is reachable, non-dry-run `openclaw sessions cleanup` and `openclaw agents delete` delegate store mutations to the Gateway so cleanup joins the same writer queue; `--store <path>` is the explicit offline repair path for direct file maintenance. `maxEntries` cleanup is still batched for production-sized caps, so a store may briefly exceed the configured cap before the next high-water cleanup rewrites it back down. Session store reads do not prune or cap entries during Gateway startup; use writes or `openclaw sessions cleanup --enforce` for cleanup. `openclaw sessions cleanup --enforce` still applies the configured cap immediately.
Normal Gateway writes flow through a per-store session writer that serializes in-process mutations without taking a runtime file lock. Hot-path patch helpers borrow the validated mutable cache while they hold that writer slot, so large `sessions.json` files are not cloned or reread for every metadata update. Runtime code should prefer `updateSessionStore(...)` or `updateSessionStoreEntry(...)`; direct whole-store saves are compatibility and offline-maintenance tools. When a Gateway is reachable, non-dry-run `openclaw sessions cleanup` and `openclaw agents delete` delegate store mutations to the Gateway so cleanup joins the same writer queue; `--store <path>` is the explicit offline repair path for direct file maintenance. `maxEntries` cleanup is still batched for production-sized caps, so a store may briefly exceed the configured cap before the next high-water cleanup rewrites it back down. Session store reads do not prune or cap entries during Gateway startup; use writes or `openclaw sessions cleanup --enforce` for cleanup. `openclaw sessions cleanup --enforce` still applies the configured cap immediately and prunes old unreferenced transcript, checkpoint, and trajectory artifacts even when no disk budget is configured.
Maintenance keeps durable external conversation pointers such as group sessions
and thread-scoped chat sessions, but synthetic runtime entries for cron, hooks,

View File

@@ -44,7 +44,7 @@ title: "Tests"
- `pnpm test:docker:openwebui`: Starts Dockerized OpenClaw + Open WebUI, signs in through Open WebUI, checks `/api/models`, then runs a real proxied chat through `/api/chat/completions`. Requires a usable live model key (for example OpenAI in `~/.profile`), pulls an external Open WebUI image, and is not expected to be CI-stable like the normal unit/e2e suites.
- `pnpm test:docker:mcp-channels`: Starts a seeded Gateway container and a second client container that spawns `openclaw mcp serve`, then verifies routed conversation discovery, transcript reads, attachment metadata, live event queue behavior, outbound send routing, and Claude-style channel + permission notifications over the real stdio bridge. The Claude notification assertion reads the raw stdio MCP frames directly so the smoke reflects what the bridge actually emits.
- `pnpm test:docker:upgrade-survivor`: Installs the packed OpenClaw tarball over a dirty old-user fixture, runs package update plus non-interactive doctor without live provider or channel keys, then starts a loopback Gateway and checks that agents, channel config, plugin allowlists, workspace/session files, stale legacy plugin dependency state, startup, and RPC status survive.
- `pnpm test:docker:published-upgrade-survivor`: Installs `openclaw@latest` by default, seeds realistic existing-user files without live provider or channel keys, configures that baseline with a baked `openclaw config set` command recipe, updates that published install to the packed OpenClaw tarball, runs non-interactive doctor, writes `.artifacts/upgrade-survivor/summary.json`, then starts a loopback Gateway and checks that configured intents, workspace/session files, stale plugin config and legacy dependency state, startup, `/healthz`, `/readyz`, and RPC status survive or repair cleanly. Override one baseline with `OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPEC`, expand an exact matrix with `OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPECS` such as `all-since-2026.4.23`, or add scenario fixtures with `OPENCLAW_UPGRADE_SURVIVOR_SCENARIOS=reported-issues`; the reported-issues set includes `configured-plugin-installs` to verify configured external OpenClaw plugins install automatically during upgrade. Package Acceptance exposes those as `published_upgrade_survivor_baseline`, `published_upgrade_survivor_baselines`, and `published_upgrade_survivor_scenarios`.
- `pnpm test:docker:published-upgrade-survivor`: Installs `openclaw@latest` by default, seeds realistic existing-user files without live provider or channel keys, configures that baseline with a baked `openclaw config set` command recipe, updates that published install to the packed OpenClaw tarball, runs non-interactive doctor, writes `.artifacts/upgrade-survivor/summary.json`, then starts a loopback Gateway and checks that configured intents, workspace/session files, stale plugin config and legacy dependency state, startup, `/healthz`, `/readyz`, and RPC status survive or repair cleanly. Override one baseline with `OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPEC`, expand an exact local matrix with `OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPECS` such as `openclaw@2026.5.2 openclaw@2026.4.23 openclaw@2026.4.15`, or add scenario fixtures with `OPENCLAW_UPGRADE_SURVIVOR_SCENARIOS=reported-issues`; the reported-issues set includes `configured-plugin-installs` to verify configured external OpenClaw plugins install automatically during upgrade and `stale-source-plugin-shadow` to keep source-only plugin shadows from breaking startup. Package Acceptance exposes those as `published_upgrade_survivor_baseline`, `published_upgrade_survivor_baselines`, and `published_upgrade_survivor_scenarios`, and resolves meta baseline tokens such as `last-stable-4` or `all-since-2026.4.23` before handing exact package specs to Docker lanes.
- `pnpm test:docker:update-migration`: Runs the published-upgrade survivor harness in the cleanup-heavy `plugin-deps-cleanup` scenario, starting at `openclaw@2026.4.23` by default. The separate `Update Migration` workflow expands this lane with `baselines=all-since-2026.4.23` so every stable published package from `.23` onward updates to the candidate and proves configured-plugin dependency cleanup outside Full Release CI.
- `pnpm test:docker:plugins`: Runs install/update smoke for local path, `file:`, npm registry packages with hoisted dependencies, git moving refs, ClawHub fixtures, marketplace updates, and Claude-bundle enable/inspect.

View File

@@ -117,6 +117,7 @@ inter-session user turns that only have provenance metadata.
- Image sanitization only.
- 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.
- Preserve replayable OpenAI Responses reasoning item payloads, including encrypted empty-summary items, so manual/WebSocket replay keeps required `rs_*` state paired with assistant output items.
- Native ChatGPT Codex Responses follows Codex wire parity by replaying prior Responses reasoning/message/function payloads without prior item IDs while preserving session `prompt_cache_key`.
- No tool call id sanitization.
- Tool result pairing repair may move real matched outputs and synthesize Codex-style `aborted` outputs for missing tool calls.
- No turn validation or reordering.

View File

@@ -56,6 +56,7 @@ On shutdown, OpenClaw restores the previous proxy environment and resets cached
- `proxy.enabled` / `proxy.proxyUrl`: outbound forward-proxy routing for OpenClaw runtime egress. This page documents that feature.
- `gateway.auth.mode: "trusted-proxy"`: inbound identity-aware reverse-proxy authentication for Gateway access. See [Trusted proxy auth](/gateway/trusted-proxy-auth).
- `openclaw proxy`: local debug proxy and capture inspector for development and support. See [openclaw proxy](/cli/proxy).
- `tools.web.fetch.useTrustedEnvProxy`: opt-in for `web_fetch` to let an operator-controlled HTTP(S) env proxy resolve DNS while keeping default strict DNS pinning and hostname policy. See [Web fetch](/tools/web-fetch#trusted-env-proxy).
- Channel or provider-specific proxy settings: owner-specific overrides for a particular transport. Prefer the managed network proxy when the goal is central egress control across the runtime.
## Configuration

View File

@@ -86,6 +86,29 @@ When a run id is available, recent tool-call history is evaluated only within th
- disable only the detector causing issues
- reduce `historySize` for less strict historical context
## Post-compaction guard
When the runner completes an auto-compaction-retry (after a context-overflow), it arms a short-window guard that watches the next few tool calls. If the agent emits the _same_ `(toolName, args, result)` triple multiple times within that window, the guard concludes that compaction did not break the loop and aborts the run with a `compaction_loop_persisted` error.
This is a separate code path from the global `tools.loopDetection` detectors. It is independently configurable:
```json5
{
tools: {
loopDetection: {
enabled: true, // existing master switch; set false to disable loop guards
postCompactionGuard: {
windowSize: 3, // default: 3
},
},
},
}
```
- `windowSize`: number of post-compaction tool calls during which the guard stays armed _and_ the count of identical (tool, args, result) triples that triggers an abort.
The guard never aborts when results are changing, only when results are byte-identical across the window. It is intentionally narrow: it fires only in the immediate aftermath of a compaction-retry.
## Logs and expected behavior
When a loop is detected, OpenClaw reports a loop event and blocks or dampens the next tool-cycle depending on severity.

View File

@@ -80,19 +80,22 @@ reply model.
## Async vs synchronous
| Capability | Mode | Why |
| --------------- | ------------ | ------------------------------------------------------------------ |
| Image | Synchronous | Provider responses return in seconds; completes inline with reply. |
| Text-to-speech | Synchronous | Provider responses return in seconds; attached to the reply audio. |
| Video | Asynchronous | Provider processing takes 30 s to several minutes. |
| Music (shared) | Asynchronous | Same provider-processing characteristic as video. |
| Music (ComfyUI) | Synchronous | Local workflow runs inline against the configured ComfyUI server. |
| Capability | Mode | Why |
| --------------- | ------------ | ---------------------------------------------------------------------------------------------------- |
| Image | Synchronous | Provider responses return in seconds; completes inline with reply. |
| Text-to-speech | Synchronous | Provider responses return in seconds; attached to the reply audio. |
| Video | Asynchronous | Provider processing takes 30 s to several minutes; slow queues can run up to the configured timeout. |
| Music (shared) | Asynchronous | Same provider-processing characteristic as video. |
| Music (ComfyUI) | Synchronous | Local workflow runs inline against the configured ComfyUI server. |
For async tools, OpenClaw submits the request to the provider, returns a task
id immediately, and tracks the job in the task ledger. The agent continues
responding to other messages while the job runs. When the provider finishes,
OpenClaw wakes the agent so it can post the finished media back into the
original channel.
OpenClaw wakes the agent with the generated media paths so it can tell the
user and, when required by source-delivery policy, relay the result through
the message tool. For message-tool-only group/channel routes, OpenClaw treats
missing message-tool delivery evidence as a failed completion attempt and sends
the generated media fallback directly to the original channel.
## Speech-to-text and Voice Call

View File

@@ -14,8 +14,12 @@ MiniMax, and workflow-configured ComfyUI today.
For session-backed agent runs, OpenClaw starts music generation as a
background task, tracks it in the task ledger, then wakes the agent again
when the track is ready so the agent can post the finished audio back into
the original channel.
when the track is ready so the agent can tell the user and attach the
finished audio. In group/channel chats that use message-tool-only visible
delivery, the agent relays the result through the message tool. If the
completion agent writes only a private final reply, OpenClaw falls back to a
direct channel send with the generated media. The completion wake explicitly
warns the agent that normal final replies are private in those routes.
<Note>
The built-in shared tool only appears when at least one music-generation

View File

@@ -260,14 +260,15 @@ Looking for third-party plugins? See [Community Plugins](/plugins/community).
}
```
| Field | Description |
| ---------------- | --------------------------------------------------------- |
| `enabled` | Master toggle (default: `true`) |
| `allow` | Plugin allowlist (optional) |
| `deny` | Plugin denylist (optional; deny wins) |
| `load.paths` | Extra plugin files/directories |
| `slots` | Exclusive slot selectors (e.g. `memory`, `contextEngine`) |
| `entries.\<id\>` | Per-plugin toggles + config |
| Field | Description |
| ------------------ | --------------------------------------------------------- |
| `enabled` | Master toggle (default: `true`) |
| `allow` | Plugin allowlist (optional) |
| `bundledDiscovery` | Bundled plugin discovery mode (`allowlist` by default) |
| `deny` | Plugin denylist (optional; deny wins) |
| `load.paths` | Extra plugin files/directories |
| `slots` | Exclusive slot selectors (e.g. `memory`, `contextEngine`) |
| `entries.\<id\>` | Per-plugin toggles + config |
`plugins.allow` is exclusive. When it is non-empty, only listed plugins can load
or expose tools, even if `tools.allow` contains `"*"` or a specific plugin-owned
@@ -275,6 +276,13 @@ tool name. If a tool allowlist references plugin tools, add the owning plugin id
to `plugins.allow` or remove `plugins.allow`; `openclaw doctor` warns about this
shape.
`plugins.bundledDiscovery` defaults to `"allowlist"` for new configs, so a
restrictive `plugins.allow` inventory also blocks omitted bundled provider
plugins, including runtime web-search provider discovery. Doctor stamps older
restrictive allowlist configs with `"compat"` during migration so upgrades keep
legacy bundled provider behavior until the operator opts into the stricter mode.
An empty `plugins.allow` is still treated as unset/open.
Config changes made through `/plugins enable` or `/plugins disable` trigger an
in-process Gateway plugin reload. New agent turns rebuild their tool list from
the refreshed plugin registry. Source-changing operations such as install,

View File

@@ -152,7 +152,7 @@ Current source-of-truth:
- `/help` shows the short help summary.
- `/commands` shows the generated command catalog.
- `/tools [compact|verbose]` shows what the current agent can use right now.
- `/status` shows execution/runtime status, including `Execution`/`Runtime` labels and provider usage/quota when available.
- `/status` shows execution/runtime status, Gateway and system uptime, plus provider usage/quota when available.
- `/diagnostics [note]` is the owner-only support-report flow for Gateway bugs and Codex harness runs. It asks for explicit exec approval every time before running `openclaw gateway diagnostics export --json`; do not approve diagnostics with an allow-all rule. After approval, it sends a pasteable report with the local bundle path, manifest summary, privacy notes, and relevant session ids. In group chats, the approval prompt and report go to the owner privately. When the active session uses the OpenAI Codex harness, the same approval also sends relevant Codex feedback to OpenAI servers and the completed reply lists the OpenClaw session ids, Codex thread ids, and `codex resume <thread-id>` commands. See [Diagnostics Export](/gateway/diagnostics).
- `/crestodian <request>` runs the Crestodian setup and repair helper from an owner DM.
- `/tasks` lists active/recent background tasks for the current session.

View File

@@ -26,7 +26,8 @@ title: "Thinking levels"
- Anthropic Claude Opus 4.7 does not default to adaptive thinking. Its API effort default remains provider-owned unless you explicitly set a thinking level.
- Anthropic Claude Opus 4.7 maps `/think xhigh` to adaptive thinking plus `output_config.effort: "xhigh"`, because `/think` is a thinking directive and `xhigh` is the Opus 4.7 effort setting.
- Anthropic Claude Opus 4.7 also exposes `/think max`; it maps to the same provider-owned max effort path.
- DeepSeek V4 models expose `/think xhigh|max`; both map to DeepSeek `reasoning_effort: "max"` while lower non-off levels map to `high`.
- Direct DeepSeek V4 models expose `/think xhigh|max`; both map to DeepSeek `reasoning_effort: "max"` while lower non-off levels map to `high`.
- OpenRouter-routed DeepSeek V4 models expose `/think xhigh` and send OpenRouter-supported `reasoning_effort` values. Stored `max` overrides fall back to `xhigh`.
- Ollama thinking-capable models expose `/think low|medium|high|max`; `max` maps to native `think: "high"` because Ollama's native API accepts `low`, `medium`, and `high` effort strings.
- OpenAI GPT models map `/think` through model-specific Responses API effort support. `/think off` sends `reasoning.effort: "none"` only when the target model supports it; otherwise OpenClaw omits the disabled reasoning payload instead of sending an unsupported value.
- Custom OpenAI-compatible catalog entries can opt into `/think xhigh` by setting `models.providers.<provider>.models[].compat.supportedReasoningEfforts` to include `"xhigh"`. This uses the same compat metadata that maps outbound OpenAI reasoning effort payloads, so menus, session validation, agent CLI, and `llm-task` agree with transport behavior.
@@ -54,6 +55,7 @@ title: "Thinking levels"
## Application by agent
- **Embedded Pi**: the resolved level is passed to the in-process Pi agent runtime.
- **Claude CLI backend**: non-off levels are passed to Claude Code as `--effort` when using `claude-cli`; see [CLI backends](/gateway/cli-backends).
## Fast mode (/fast)

View File

@@ -60,9 +60,11 @@ Video generation is asynchronous. When the agent calls `video_generate` in a
session:
1. OpenClaw submits the request to the provider and immediately returns a task id.
2. The provider processes the job in the background (typically 30 seconds to 5 minutes depending on the provider and resolution).
2. The provider processes the job in the background (typically 30 seconds to several minutes depending on the provider and resolution; slow queue-backed providers can run up to the configured timeout).
3. When the video is ready, OpenClaw wakes the same session with an internal completion event.
4. The agent posts the finished video back into the original conversation.
4. The agent tells the user and attaches the finished video. In group/channel
chats that use message-tool-only visible delivery, the agent relays the
result through the message tool instead of OpenClaw posting it directly.
While a job is in flight, duplicate `video_generate` calls in the same
session return the current task status instead of starting another
@@ -82,12 +84,12 @@ rejects an oversized file.
### Task lifecycle
| State | Meaning |
| ----------- | ------------------------------------------------------------------------------------------------ |
| `queued` | Task created, waiting for the provider to accept it. |
| `running` | Provider is processing (typically 30 seconds to 5 minutes depending on provider and resolution). |
| `succeeded` | Video ready; the agent wakes and posts it to the conversation. |
| `failed` | Provider error or timeout; the agent wakes with error details. |
| State | Meaning |
| ----------- | ------------------------------------------------------------------------------------------------------ |
| `queued` | Task created, waiting for the provider to accept it. |
| `running` | Provider is processing (typically 30 seconds to several minutes depending on provider and resolution). |
| `succeeded` | Video ready; the agent wakes and posts it to the conversation. |
| `failed` | Provider error or timeout; the agent wakes with error details. |
Check status from the CLI:
@@ -196,9 +198,9 @@ role or use `first_frame` for single-image image-to-video.
### Style controls
<ParamField path="aspectRatio" type="string">
`1:1`, `2:3`, `3:2`, `3:4`, `4:3`, `4:5`, `5:4`, `9:16`, `16:9`, `21:9`, or `adaptive`.
Aspect-ratio hint such as `1:1`, `16:9`, `9:16`, `adaptive`, or a provider-specific value. OpenClaw normalizes or ignores unsupported values per provider.
</ParamField>
<ParamField path="resolution" type="string">`480P`, `720P`, `768P`, or `1080P`.</ParamField>
<ParamField path="resolution" type="string">Resolution hint such as `480P`, `720P`, `768P`, `1080P`, `4K`, or a provider-specific value. OpenClaw normalizes or ignores unsupported values per provider.</ParamField>
<ParamField path="durationSeconds" type="number">
Target duration in seconds (rounded to nearest provider-supported value).
</ParamField>
@@ -221,7 +223,7 @@ dimensions). Providers that do not declare it surface the value via
</ParamField>
<ParamField path="model" type="string">Provider/model override (e.g. `runway/gen4.5`).</ParamField>
<ParamField path="filename" type="string">Output filename hint.</ParamField>
<ParamField path="timeoutMs" type="number">Optional provider request timeout in milliseconds.</ParamField>
<ParamField path="timeoutMs" type="number">Optional provider operation timeout in milliseconds.</ParamField>
<ParamField path="providerOptions" type="object">
Provider-specific options as a JSON object (e.g. `{"seed": 42, "draft": true}`).
Providers that declare a typed schema validate the keys and types; unknown
@@ -375,16 +377,22 @@ only the explicit `model`, `primary`, and `fallbacks` entries.
image-to-video through the configured graph.
</Accordion>
<Accordion title="fal">
Uses a queue-backed flow for long-running jobs. Most fal video models
Uses a queue-backed flow for long-running jobs. OpenClaw waits up to 20
minutes by default before treating an in-progress fal queue job as timed
out. Most fal video models
accept a single image reference. Seedance 2.0 reference-to-video
models accept up to 9 images, 3 videos, and 3 audio references, with
at most 12 total reference files.
</Accordion>
<Accordion title="Google (Gemini / Veo)">
Supports one image or one video reference.
Supports one image or one video reference. Generated-audio requests are
ignored with a warning on the Gemini API path because that API rejects
the `generateAudio` parameter for current Veo video generation.
</Accordion>
<Accordion title="MiniMax">
Single image reference only.
Single image reference only. MiniMax accepts `768P` and `1080P`
resolutions; requests such as `720P` are normalized to the closest
supported value before submission.
</Accordion>
<Accordion title="OpenAI">
Only `size` override is forwarded. Other style overrides

View File

@@ -154,7 +154,7 @@ Imported themes are stored only in the current browser profile. They are not wri
- Re-sending with the same `idempotencyKey` returns `{ status: "in_flight" }` while running, and `{ status: "ok" }` after completion.
- `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`.
- When rendering `chat.history`, the Control UI 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` or the heartbeat acknowledgement token `HEARTBEAT_OK`.
- 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.
- Live `chat` events are delivery state, while `chat.history` is rebuilt from the durable session transcript. After tool-final events the Control UI reloads history and merges only a small optimistic tail; the transcript boundary is documented in [WebChat](/web/webchat).
- `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).

View File

@@ -39,6 +39,10 @@ Prefer localhost, Tailscale Serve, or an SSH tunnel.
- After onboarding, the CLI auto-opens the dashboard and prints a clean (non-tokenized) link.
- Re-open anytime: `openclaw dashboard` (copies link, opens browser if possible, shows SSH hint if headless).
- If clipboard and browser delivery fail, `openclaw dashboard` still prints the
clean URL and tells you to use the token from `OPENCLAW_GATEWAY_TOKEN` or
`gateway.auth.token` as the URL fragment key `token`; it does not print token
values in logs.
- If the UI prompts for shared-secret auth, paste the configured token or
password into Control UI settings.

View File

@@ -8,8 +8,8 @@
},
"type": "module",
"dependencies": {
"@agentclientprotocol/claude-agent-acp": "0.31.4",
"@zed-industries/codex-acp": "0.12.0",
"@agentclientprotocol/claude-agent-acp": "0.32.0",
"@zed-industries/codex-acp": "0.13.0",
"acpx": "0.6.1"
},
"devDependencies": {

View File

@@ -211,8 +211,8 @@ ${ACPX_CMD} codex sessions close oc-codex-<conversationId>
Defaults are:
- `openclaw -> openclaw acp`
- `claude -> npx -y @agentclientprotocol/claude-agent-acp@^0.31.0`
- `codex -> bundled @zed-industries/codex-acp@0.12.0 through OpenClaw's isolated CODEX_HOME wrapper`
- `claude -> bundled @agentclientprotocol/claude-agent-acp@0.32.0`
- `codex -> bundled @zed-industries/codex-acp@0.13.0 through OpenClaw's isolated CODEX_HOME wrapper`
- `copilot -> copilot --acp --stdio`
- `cursor -> cursor-agent acp`
- `droid -> droid exec --output-format acp`

View File

@@ -163,7 +163,7 @@ describe("prepareAcpxCodexAuthConfig", () => {
});
const wrapper = await fs.readFile(generated.wrapperPath, "utf8");
expect(wrapper).toContain('"@zed-industries/codex-acp@^0.12.0"');
expect(wrapper).toContain('"@zed-industries/codex-acp@0.13.0"');
expect(wrapper).toContain('"--", "codex-acp"');
expect(wrapper).not.toContain("@zed-industries/codex-acp@^0.11.1");
});
@@ -184,7 +184,7 @@ describe("prepareAcpxCodexAuthConfig", () => {
});
const wrapper = await fs.readFile(generated.wrapperPath, "utf8");
expect(wrapper).toContain('"@agentclientprotocol/claude-agent-acp@0.31.4"');
expect(wrapper).toContain('"@agentclientprotocol/claude-agent-acp@0.32.0"');
expect(wrapper).toContain('"--", "claude-agent-acp"');
expect(wrapper).not.toContain("@agentclientprotocol/claude-agent-acp@^0.31.0");
expect(wrapper).not.toContain("@agentclientprotocol/claude-agent-acp@0.31.0");

View File

@@ -1,13 +1,13 @@
import fsSync from "node:fs";
import fs from "node:fs/promises";
import { createRequire } from "node:module";
import path from "node:path";
import { resolveAcpxPluginRoot } from "./config.js";
import type { ResolvedAcpxPluginConfig } from "./config.js";
const CODEX_ACP_PACKAGE = "@zed-industries/codex-acp";
const CODEX_ACP_PACKAGE_RANGE = "^0.12.0";
const CODEX_ACP_BIN = "codex-acp";
const CLAUDE_ACP_PACKAGE = "@agentclientprotocol/claude-agent-acp";
const CLAUDE_ACP_PACKAGE_VERSION = "0.31.4";
const CLAUDE_ACP_BIN = "claude-agent-acp";
const RUN_CONFIGURED_COMMAND_SENTINEL = "--openclaw-run-configured";
const requireFromHere = createRequire(import.meta.url);
@@ -15,8 +15,25 @@ const requireFromHere = createRequire(import.meta.url);
type PackageManifest = {
name?: unknown;
bin?: unknown;
dependencies?: Record<string, unknown>;
};
function readSelfManifest(): PackageManifest {
const manifestPath = path.join(resolveAcpxPluginRoot(import.meta.url), "package.json");
return JSON.parse(fsSync.readFileSync(manifestPath, "utf8")) as PackageManifest;
}
function readManifestDependencyVersion(packageName: string): string {
const version = readSelfManifest().dependencies?.[packageName];
if (typeof version !== "string" || version.trim() === "") {
throw new Error(`Missing ${packageName} dependency version in @openclaw/acpx manifest`);
}
return version;
}
const CODEX_ACP_PACKAGE_VERSION = readManifestDependencyVersion(CODEX_ACP_PACKAGE);
const CLAUDE_ACP_PACKAGE_VERSION = readManifestDependencyVersion(CLAUDE_ACP_PACKAGE);
function quoteCommandPart(value: string): string {
return JSON.stringify(value);
}
@@ -205,7 +222,7 @@ child.on("exit", (code, signal) => {
function buildCodexAcpWrapperScript(installedBinPath?: string): string {
return buildAdapterWrapperScript({
displayName: "Codex",
packageSpec: `${CODEX_ACP_PACKAGE}@${CODEX_ACP_PACKAGE_RANGE}`,
packageSpec: `${CODEX_ACP_PACKAGE}@${CODEX_ACP_PACKAGE_VERSION}`,
binName: CODEX_ACP_BIN,
installedBinPath,
envSetup: `const codexHome = fileURLToPath(new URL("./codex-home/", import.meta.url));

View File

@@ -119,6 +119,13 @@ describe("embedded acpx plugin config", () => {
expect(server.args?.length).toBeGreaterThan(0);
});
it("resolves the plugin root from shared dist chunk paths", () => {
const moduleUrl = new URL("../../../dist/extensions/acpx/service-shared.js", import.meta.url)
.href;
expect(resolveAcpxPluginRoot(moduleUrl)).toBe(path.resolve("extensions/acpx"));
});
it("keeps the runtime json schema in sync with the manifest config schema", () => {
const pluginRoot = resolveAcpxPluginRoot();
const manifest = JSON.parse(

View File

@@ -13,8 +13,8 @@ describe("acpx package manifest", () => {
) as AcpxPackageManifest;
expect(packageJson.dependencies?.acpx).toBeDefined();
expect(packageJson.dependencies?.["@zed-industries/codex-acp"]).toBe("0.12.0");
expect(packageJson.dependencies?.["@agentclientprotocol/claude-agent-acp"]).toBe("0.31.4");
expect(packageJson.dependencies?.["@zed-industries/codex-acp"]).toBe("0.13.0");
expect(packageJson.dependencies?.["@agentclientprotocol/claude-agent-acp"]).toBe("0.32.0");
expect(packageJson.devDependencies?.["@agentclientprotocol/claude-agent-acp"]).toBeUndefined();
});
});

View File

@@ -9,7 +9,7 @@ type TestSessionStore = {
const DOCUMENTED_OPENCLAW_BRIDGE_COMMAND =
"env OPENCLAW_HIDE_BANNER=1 OPENCLAW_SUPPRESS_NOTES=1 openclaw acp --url ws://127.0.0.1:18789 --token-file ~/.openclaw/gateway.token --session agent:main:main";
const CODEX_ACP_COMMAND = "npx @zed-industries/codex-acp@^0.12.0";
const CODEX_ACP_COMMAND = "npx @zed-industries/codex-acp@0.13.0";
const CODEX_ACP_WRAPPER_COMMAND = `node "/tmp/openclaw/acpx/codex-acp-wrapper.mjs"`;
function makeRuntime(
@@ -226,7 +226,7 @@ describe("AcpxRuntime fresh reset wrapper", () => {
reasoningEffort: "medium",
}),
).toBe(
"npx @zed-industries/codex-acp@^0.12.0 -c model=gpt-5.4 -c model_reasoning_effort=medium",
"npx @zed-industries/codex-acp@0.13.0 -c model=gpt-5.4 -c model_reasoning_effort=medium",
);
expect(__testing.isCodexAcpCommand("openclaw acp")).toBe(false);
});

View File

@@ -125,6 +125,23 @@ describe("active-memory plugin", () => {
"utf8",
);
};
const makeMemoryToolAllowlistError = (
reason: string,
sources = "runtime toolsAllow: memory_recall, memory_search, memory_get",
) =>
new Error(
`No callable tools remain after resolving explicit tool allowlist ` +
`(${sources}); ${reason}. ` +
`Fix the allowlist or enable the plugin that registers the requested tool.`,
);
const hasDebugLine = (needle: string) =>
vi
.mocked(api.logger.debug)
.mock.calls.some((call: unknown[]) => String(call[0]).includes(needle));
const hasWarnLine = (needle: string) =>
vi
.mocked(api.logger.warn)
.mock.calls.some((call: unknown[]) => String(call[0]).includes(needle));
beforeEach(async () => {
vi.clearAllMocks();
@@ -1074,9 +1091,12 @@ describe("active-memory plugin", () => {
"Your job is to search memory and return only the most relevant memory context for that model.",
);
expect(runParams?.prompt).toContain(
"You receive conversation context, including the user's latest message.",
"You receive a bounded search query plus conversation context, including the user's latest message.",
);
expect(runParams?.prompt).toContain("Use only the available memory tools.");
expect(runParams?.prompt).toContain(
"Use the bounded search query as the memory_search or memory_recall query.",
);
expect(runParams?.prompt).toContain("Prefer memory_recall when available.");
expect(runParams?.prompt).toContain(
"If memory_recall is unavailable, use memory_search and memory_get.",
@@ -1643,6 +1663,133 @@ describe("active-memory plugin", () => {
expect(result).toBeUndefined();
});
it("skips the recall subagent when no registered memory tools match", async () => {
const sessionKey = "agent:main:missing-memory-tools";
hoisted.sessionStore[sessionKey] = {
sessionId: "s-missing-memory-tools",
updatedAt: 0,
};
const error = makeMemoryToolAllowlistError("no registered tools matched");
expect(__testing.isMissingRegisteredMemoryToolsError(error)).toBe(true);
runEmbeddedPiAgent.mockRejectedValueOnce(error);
const result = await hooks.before_prompt_build(
{ prompt: "what wings should i order? missing memory tools", messages: [] },
{ agentId: "main", trigger: "user", sessionKey, messageProvider: "webchat" },
);
expect(result).toBeUndefined();
expect(hasDebugLine("no memory tools registered")).toBe(true);
expect(hasWarnLine("No callable tools remain")).toBe(false);
const lines = getActiveMemoryLines(sessionKey);
expect(lines).toEqual([expect.stringContaining("🧩 Active Memory: status=empty")]);
expect(lines.join("\n")).not.toContain("status=unavailable");
});
it("skips missing memory tools when the allowlist error includes inherited sources", async () => {
const sessionKey = "agent:main:missing-memory-tools-with-policy-source";
hoisted.sessionStore[sessionKey] = {
sessionId: "s-missing-memory-tools-with-policy-source",
updatedAt: 0,
};
const error = makeMemoryToolAllowlistError(
"no registered tools matched",
"tools.allow: *, lobster; runtime toolsAllow: memory_recall, memory_search, memory_get",
);
expect(__testing.isMissingRegisteredMemoryToolsError(error)).toBe(true);
runEmbeddedPiAgent.mockRejectedValueOnce(error);
const result = await hooks.before_prompt_build(
{ prompt: "what wings should i order? missing memory tools with policy", messages: [] },
{ agentId: "main", trigger: "user", sessionKey, messageProvider: "webchat" },
);
expect(result).toBeUndefined();
expect(hasDebugLine("no memory tools registered")).toBe(true);
expect(hasWarnLine("No callable tools remain")).toBe(false);
expect(getActiveMemoryLines(sessionKey)).toEqual([
expect.stringContaining("🧩 Active Memory: status=empty"),
]);
});
it("keeps memory-tool allowlist errors visible when upstream policy can filter memory tools", async () => {
const sessionKey = "agent:main:memory-tools-filtered-by-policy";
hoisted.sessionStore[sessionKey] = {
sessionId: "s-memory-tools-filtered-by-policy",
updatedAt: 0,
};
const error = makeMemoryToolAllowlistError(
"no registered tools matched",
"tools.allow: read, exec; runtime toolsAllow: memory_recall, memory_search, memory_get",
);
expect(__testing.isMissingRegisteredMemoryToolsError(error)).toBe(false);
runEmbeddedPiAgent.mockRejectedValueOnce(error);
const result = await hooks.before_prompt_build(
{ prompt: "what wings should i order? memory tools filtered by policy", messages: [] },
{ agentId: "main", trigger: "user", sessionKey, messageProvider: "webchat" },
);
expect(result).toBeUndefined();
expect(hasDebugLine("no memory tools registered")).toBe(false);
expect(hasWarnLine("No callable tools remain")).toBe(true);
expect(getActiveMemoryLines(sessionKey)).toEqual([
expect.stringContaining("🧩 Active Memory: status=unavailable"),
]);
});
it.each([
["disabled tools", "tools are disabled for this run"],
["models without tool support", "the selected model does not support tools"],
])("keeps allowlist errors for %s visible", async (_label, reason) => {
const sessionKey = `agent:main:${reason.replace(/\W+/g, "-")}`;
hoisted.sessionStore[sessionKey] = {
sessionId: `s-${reason.replace(/\W+/g, "-")}`,
updatedAt: 0,
};
const error = makeMemoryToolAllowlistError(reason);
expect(__testing.isMissingRegisteredMemoryToolsError(error)).toBe(false);
runEmbeddedPiAgent.mockRejectedValueOnce(error);
const result = await hooks.before_prompt_build(
{ prompt: `what wings should i order? ${reason}`, messages: [] },
{ agentId: "main", trigger: "user", sessionKey, messageProvider: "webchat" },
);
expect(result).toBeUndefined();
expect(hasDebugLine("no memory tools registered")).toBe(false);
expect(hasWarnLine(reason)).toBe(true);
expect(getActiveMemoryLines(sessionKey)).toEqual([
expect.stringContaining("🧩 Active Memory: status=unavailable"),
]);
});
it("does not skip missing memory-tool allowlist errors after abort", async () => {
const sessionKey = "agent:main:missing-memory-tools-after-abort";
hoisted.sessionStore[sessionKey] = {
sessionId: "s-missing-memory-tools-after-abort",
updatedAt: 0,
};
runEmbeddedPiAgent.mockImplementationOnce(async (params: { abortSignal?: AbortSignal }) => {
Object.defineProperty(params.abortSignal as AbortSignal, "aborted", {
configurable: true,
value: true,
});
throw makeMemoryToolAllowlistError("no registered tools matched");
});
const result = await hooks.before_prompt_build(
{ prompt: "what wings should i order? missing memory tools after abort", messages: [] },
{ agentId: "main", trigger: "user", sessionKey, messageProvider: "webchat" },
);
expect(result).toBeUndefined();
expect(hasDebugLine("no memory tools registered")).toBe(false);
expect(getActiveMemoryLines(sessionKey)).toEqual([
expect.stringContaining("🧩 Active Memory: status=timeout"),
]);
});
it("returns partial transcript text on timeout when the subagent has already written assistant output", async () => {
__testing.setMinimumTimeoutMsForTests(1);
__testing.setSetupGraceTimeoutMsForTests(0);
@@ -2753,6 +2900,33 @@ describe("active-memory plugin", () => {
});
});
it("skips colon-containing session-store channels for embedded recall (#77396)", async () => {
hoisted.sessionStore["agent:main:qqbot:direct:12345"] = {
sessionId: "session-a",
updatedAt: 25,
channel: "c2c:10D4F7C2",
origin: {
provider: "qqbot",
},
};
await hooks.before_prompt_build(
{ prompt: "what wings should i order? scoped stored channel", messages: [] },
{
agentId: "main",
trigger: "user",
sessionKey: "agent:main:qqbot:direct:12345",
messageProvider: "qqbot",
channelId: "qqbot",
},
);
expect(runEmbeddedPiAgent.mock.calls.at(-1)?.[0]).toMatchObject({
messageChannel: "qqbot",
messageProvider: "qqbot",
});
});
it("preserves an explicit real channel hint over a stale stored wrapper channel", async () => {
hoisted.sessionStore["agent:main:telegram:direct:12345"] = {
sessionId: "session-a",
@@ -2867,10 +3041,54 @@ describe("active-memory plugin", () => {
);
const prompt = runEmbeddedPiAgent.mock.calls.at(-1)?.[0]?.prompt;
expect(prompt).toContain("Bounded memory search query:\nwhat should i grab on the way?");
expect(prompt).toContain("Conversation context:\nwhat should i grab on the way?");
expect(prompt).not.toContain("Recent conversation tail:");
});
it("sends a bounded latest-message query instead of channel metadata to memory search", async () => {
api.pluginConfig = {
agents: ["main"],
queryMode: "recent",
};
plugin.register(api as unknown as OpenClawPluginApi);
await hooks.before_prompt_build(
{
prompt: [
"Conversation info:",
"Sender: discord:user-123",
"Untrusted Discord message body",
"---",
"do you remember my flight preferences?",
].join("\n"),
messages: [
{ role: "user", content: "i have a flight tomorrow" },
{ role: "assistant", content: "got it" },
],
},
{
agentId: "main",
trigger: "user",
sessionKey: "agent:main:main",
messageProvider: "webchat",
},
);
const prompt = runEmbeddedPiAgent.mock.calls.at(-1)?.[0]?.prompt;
expect(prompt).toContain(
"Bounded memory search query:\ndo you remember my flight preferences?",
);
expect(prompt).toContain(
"Do not use channel metadata, provider metadata, debug output, or the full conversation context as the memory tool query.",
);
expect(prompt).toContain("Conversation context:");
expect(prompt).toContain("Conversation info:");
expect(prompt).not.toContain("Bounded memory search query:\nConversation info:");
expect(prompt).not.toContain("Bounded memory search query:\nSender:");
expect(prompt).not.toContain("Bounded memory search query:\nUntrusted Discord message body");
});
it("supports full mode by sending the whole conversation", async () => {
api.pluginConfig = {
agents: ["main"],
@@ -3209,7 +3427,6 @@ describe("active-memory plugin", () => {
`^${escapeRegExp(expectedDir)}${escapeRegExp(path.sep)}active-memory-[a-z0-9]+-[a-f0-9]{8}\\.jsonl$`,
),
);
expect(rmSpy).not.toHaveBeenCalled();
expect(
vi
.mocked(api.logger.info)
@@ -3217,6 +3434,7 @@ describe("active-memory plugin", () => {
String(call[0]).includes(`transcript=${expectedDir}${path.sep}`),
),
).toBe(true);
expect(rmSpy.mock.calls.some(([target]) => String(target).startsWith(expectedDir))).toBe(false);
});
it("falls back to the default transcript directory when transcriptDir is unsafe", async () => {

View File

@@ -41,11 +41,13 @@ const DEFAULT_QMD_SEARCH_MODE = "search" as const;
const DEFAULT_TRANSCRIPT_DIR = "active-memory";
const DEFAULT_CIRCUIT_BREAKER_MAX_TIMEOUTS = 3;
const DEFAULT_CIRCUIT_BREAKER_COOLDOWN_MS = 60_000;
const ACTIVE_MEMORY_TOOL_ALLOWLIST = ["memory_recall", "memory_search", "memory_get"] as const;
const TOGGLE_STATE_FILE = "session-toggles.json";
const DEFAULT_PARTIAL_TRANSCRIPT_MAX_CHARS = 32_000;
const DEFAULT_TRANSCRIPT_READ_MAX_LINES = 2_000;
const DEFAULT_TRANSCRIPT_READ_MAX_BYTES = 50 * 1024 * 1024;
const TIMEOUT_PARTIAL_DATA_GRACE_MS = 50;
const TIMEOUT_PARTIAL_DATA_GRACE_MS = 500;
const MAX_ACTIVE_MEMORY_SEARCH_QUERY_CHARS = 480;
const TERMINAL_MEMORY_SEARCH_POLL_INTERVAL_MS = 25;
const NO_RECALL_VALUES = new Set([
@@ -493,6 +495,38 @@ function normalizeOptionalString(value: unknown): string | undefined {
return typeof value === "string" && value.trim() ? value.trim() : undefined;
}
function isMissingRegisteredMemoryToolsError(error: unknown): boolean {
if (!(error instanceof Error)) {
return false;
}
const message = error.message.trim();
const prefix = "No callable tools remain after resolving explicit tool allowlist (";
const suffix =
"); no registered tools matched. Fix the allowlist or enable the plugin that registers the requested tool.";
if (!message.startsWith(prefix) || !message.endsWith(suffix)) {
return false;
}
const sources = message.slice(prefix.length, -suffix.length);
const runtimeSource = `runtime toolsAllow: ${ACTIVE_MEMORY_TOOL_ALLOWLIST.join(", ")}`;
const sourceParts = sources
.split(";")
.map((source) => source.trim())
.filter(Boolean);
if (!sourceParts.includes(runtimeSource)) {
return false;
}
return sourceParts.every((source) => {
if (source === runtimeSource) {
return true;
}
const entries = source
.slice(source.indexOf(":") + 1)
.split(",")
.map((entry) => entry.trim());
return entries.includes("*");
});
}
function resolveRecallRunChannelContext(params: {
api: OpenClawPluginApi;
agentId: string;
@@ -560,9 +594,17 @@ function resolveRecallRunChannelContext(params: {
store,
sessionKey: resolvedSessionKey,
}).existing;
const strongEntryChannel =
const rawStrongEntryChannel =
normalizeOptionalString(sessionEntry?.lastChannel) ??
normalizeOptionalString(sessionEntry?.channel);
// Channel IDs containing ":" are scoped conversation IDs (e.g. QQ c2c
// "c2c:10D4F7C2..."), not runnable channel names. The same guard that
// applies to explicit channelId (#76704) must also apply to channels
// read from the session store (#77396).
const strongEntryChannel =
rawStrongEntryChannel && !rawStrongEntryChannel.includes(":")
? rawStrongEntryChannel
: undefined;
const weakEntryChannel = normalizeOptionalString(sessionEntry?.origin?.provider);
return resolveReturnValue({
resolvedChannel: strongEntryChannel ?? weakEntryChannel,
@@ -932,13 +974,16 @@ function buildPromptStyleLines(style: ActiveMemoryPromptStyle): string[] {
function buildRecallPrompt(params: {
config: ResolvedActiveRecallPluginConfig;
query: string;
searchQuery: string;
}): string {
const defaultInstructions = [
"You are a memory search agent.",
"Another model is preparing the final user-facing answer.",
"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.",
"You receive a bounded search query plus conversation context, including the user's latest message.",
"Use only the available memory tools.",
"Use the bounded search query as the memory_search or memory_recall query.",
"Do not use channel metadata, provider metadata, debug output, or the full conversation context as the memory tool query.",
"Prefer memory_recall when available.",
"If memory_recall is unavailable, use memory_search and memory_get.",
"When searching for preference or habit recall, use a permissive recall limit or memory_search threshold before deciding that no useful memory exists.",
@@ -990,7 +1035,11 @@ function buildRecallPrompt(params: {
]
.filter((section) => section.length > 0)
.join("\n\n");
return `${instructionBlock}\n\nConversation context:\n${params.query}`;
return [
instructionBlock,
`Bounded memory search query:\n${params.searchQuery}`,
`Conversation context:\n${params.query}`,
].join("\n\n");
}
function isEnabledForAgent(
@@ -2048,6 +2097,83 @@ function buildQuery(params: {
].join("\n");
}
function stripExternalUntrustedBlocks(text: string): string {
return text.replace(
/<<<EXTERNAL_UNTRUSTED_CONTENT\b[^>]*>>>[\s\S]*?<<<END_EXTERNAL_UNTRUSTED_CONTENT\b[^>]*>>>/g,
" ",
);
}
function stripJsonFences(text: string): string {
return text.replace(/```(?:json)?\s*[\s\S]*?```/gi, " ");
}
function stripActiveMemoryXmlBlocks(text: string): string {
return text.replace(/<active_memory_plugin>[\s\S]*?<\/active_memory_plugin>/gi, " ");
}
function normalizeSearchQueryText(text: string): string {
return text
.split("\n")
.map((line) => line.trim())
.filter((line) => {
if (!line) {
return false;
}
if (/^(conversation info|sender|untrusted context)\b/i.test(line)) {
return false;
}
if (/^(source: external|---|untrusted discord message body)$/i.test(line)) {
return false;
}
if (/^⚠️?\s*Agent couldn't generate a response/i.test(line)) {
return false;
}
if (/^Please try again\.?$/i.test(line)) {
return false;
}
return true;
})
.join(" ")
.replace(/\s+/g, " ")
.trim();
}
function clampSearchQuery(text: string): string {
const normalized = text.replace(/\s+/g, " ").trim();
return normalized.length > MAX_ACTIVE_MEMORY_SEARCH_QUERY_CHARS
? normalized.slice(0, MAX_ACTIVE_MEMORY_SEARCH_QUERY_CHARS).trim()
: normalized;
}
function buildSearchQuery(params: {
latestUserMessage: string;
recentTurns?: ActiveRecallRecentTurn[];
}): string {
const latest = clampSearchQuery(
normalizeSearchQueryText(
stripActiveMemoryXmlBlocks(
stripJsonFences(stripExternalUntrustedBlocks(params.latestUserMessage)),
),
),
);
if (latest.length >= 12 || !params.recentTurns?.length) {
return latest || clampSearchQuery(params.latestUserMessage);
}
const previousUser = [...params.recentTurns]
.toReversed()
.find((turn) => turn.role === "user" && turn.text.trim() !== params.latestUserMessage.trim());
if (!previousUser) {
return latest || clampSearchQuery(params.latestUserMessage);
}
const context = clampSearchQuery(
normalizeSearchQueryText(stripRecalledContextNoise(previousUser.text)),
)
.slice(0, 120)
.trim();
return clampSearchQuery(context ? `${context} ${latest}` : latest);
}
function extractTextContent(content: unknown): string {
if (typeof content === "string") {
return content;
@@ -2216,6 +2342,7 @@ async function runRecallSubagent(params: {
messageProvider?: string;
channelId?: string;
query: string;
searchQuery: string;
currentModelProviderId?: string;
currentModelId?: string;
modelRef?: { provider: string; model: string };
@@ -2270,6 +2397,7 @@ async function runRecallSubagent(params: {
const prompt = buildRecallPrompt({
config: params.config,
query: params.query,
searchQuery: params.searchQuery,
});
const { messageChannel, messageProvider } = resolveRecallRunChannelContext({
api: params.api,
@@ -2299,7 +2427,7 @@ async function runRecallSubagent(params: {
timeoutMs: embeddedTimeoutMs,
runId: subagentSessionId,
trigger: "manual",
toolsAllow: ["memory_recall", "memory_search", "memory_get"],
toolsAllow: [...ACTIVE_MEMORY_TOOL_ALLOWLIST],
disableMessageTool: true,
allowGatewaySubagentBinding: true,
bootstrapContextMode: "lightweight",
@@ -2342,6 +2470,12 @@ async function runRecallSubagent(params: {
const searchDebug = partialReply ? await readActiveMemorySearchDebug(sessionFile) : undefined;
attachPartialTimeoutData(error, partialReply, searchDebug);
}
if (!params.abortSignal?.aborted && isMissingRegisteredMemoryToolsError(error)) {
params.api.logger.debug?.(
`active-memory: no memory tools registered (memory-core or memory-lancedb required); skipping sub-agent`,
);
return { rawReply: "NONE" };
}
throw error;
} finally {
if (tempDir) {
@@ -2359,6 +2493,7 @@ async function maybeResolveActiveRecall(params: {
messageProvider?: string;
channelId?: string;
query: string;
searchQuery: string;
currentModelProviderId?: string;
currentModelId?: string;
}): Promise<ActiveRecallResult> {
@@ -2436,7 +2571,9 @@ async function maybeResolveActiveRecall(params: {
if (params.config.logging) {
params.api.logger.info?.(
`${logPrefix} start timeoutMs=${String(params.config.timeoutMs)} queryChars=${String(params.query.length)}`,
`${logPrefix} start timeoutMs=${String(params.config.timeoutMs)} queryChars=${String(
params.query.length,
)} searchQueryChars=${String(params.searchQuery.length)}`,
);
}
@@ -2805,11 +2942,16 @@ export default definePluginEntry({
});
return undefined;
}
const recentTurns = extractRecentTurns(event.messages);
const query = buildQuery({
latestUserMessage: event.prompt,
recentTurns: extractRecentTurns(event.messages),
recentTurns,
config,
});
const searchQuery = buildSearchQuery({
latestUserMessage: event.prompt,
recentTurns,
});
const result = await maybeResolveActiveRecall({
api,
config,
@@ -2819,6 +2961,7 @@ export default definePluginEntry({
messageProvider: ctx.messageProvider,
channelId: ctx.channelId,
query,
searchQuery,
currentModelProviderId: ctx.modelProviderId,
currentModelId: ctx.modelId,
});
@@ -2855,6 +2998,7 @@ const testing = {
buildPromptPrefix,
getCachedResult,
isCircuitBreakerOpen,
isMissingRegisteredMemoryToolsError,
normalizePluginConfig,
readActiveMemorySearchDebug,
readPartialAssistantText,

View File

@@ -5,9 +5,9 @@
"description": "OpenClaw Amazon Bedrock Mantle (OpenAI-compatible) provider plugin",
"type": "module",
"dependencies": {
"@anthropic-ai/sdk": "0.92.0",
"@anthropic-ai/sdk": "0.93.0",
"@aws/bedrock-token-generator": "^1.1.0",
"@mariozechner/pi-ai": "0.71.1"
"@mariozechner/pi-ai": "0.73.0"
},
"devDependencies": {
"@openclaw/plugin-sdk": "workspace:*"

View File

@@ -5,8 +5,8 @@
"description": "OpenClaw Amazon Bedrock provider plugin",
"type": "module",
"dependencies": {
"@aws-sdk/client-bedrock": "3.1041.0",
"@aws-sdk/client-bedrock-runtime": "3.1041.0",
"@aws-sdk/client-bedrock": "3.1042.0",
"@aws-sdk/client-bedrock-runtime": "3.1042.0",
"@aws-sdk/credential-provider-node": "3.972.39"
},
"devDependencies": {

View File

@@ -6,8 +6,8 @@
"type": "module",
"dependencies": {
"@anthropic-ai/vertex-sdk": "^0.16.0",
"@mariozechner/pi-agent-core": "0.71.1",
"@mariozechner/pi-ai": "0.71.1"
"@mariozechner/pi-agent-core": "0.73.0",
"@mariozechner/pi-ai": "0.73.0"
},
"devDependencies": {
"@openclaw/plugin-sdk": "workspace:*"

View File

@@ -10,6 +10,7 @@ import {
CLAUDE_CLI_MODEL_ALIASES,
CLAUDE_CLI_SESSION_ID_FIELDS,
normalizeClaudeBackendConfig,
resolveClaudeCliExecutionArgs,
} from "./cli-shared.js";
export function buildAnthropicCliBackend(): CliBackendPlugin {
@@ -76,5 +77,6 @@ export function buildAnthropicCliBackend(): CliBackendPlugin {
serialize: true,
},
normalizeConfig: normalizeClaudeBackendConfig,
resolveExecutionArgs: resolveClaudeCliExecutionArgs,
};
}

View File

@@ -6,6 +6,7 @@ import {
normalizeClaudePermissionArgs,
normalizeClaudeSettingSourcesArgs,
resolveClaudePermissionMode,
resolveClaudeCliExecutionArgs,
} from "./cli-shared.js";
describe("normalizeClaudePermissionArgs", () => {
@@ -75,6 +76,67 @@ describe("normalizeClaudeSettingSourcesArgs", () => {
});
});
describe("resolveClaudeCliExecutionArgs", () => {
it("omits effort args when thinking is off", () => {
expect(
resolveClaudeCliExecutionArgs({
workspaceDir: "/tmp",
provider: "claude-cli",
modelId: "claude-sonnet-4-6",
thinkingLevel: "off",
useResume: false,
baseArgs: ["-p", "--output-format", "stream-json"],
}),
).toEqual(["-p", "--output-format", "stream-json"]);
});
it("maps OpenClaw thinking levels to Claude effort args", () => {
expect(
resolveClaudeCliExecutionArgs({
workspaceDir: "/tmp",
provider: "claude-cli",
modelId: "claude-opus-4-7",
thinkingLevel: "minimal",
useResume: false,
baseArgs: ["-p"],
}),
).toEqual(["-p", "--effort", "low"]);
expect(
resolveClaudeCliExecutionArgs({
workspaceDir: "/tmp",
provider: "claude-cli",
modelId: "claude-opus-4-7",
thinkingLevel: "adaptive",
useResume: false,
baseArgs: ["-p"],
}),
).toEqual(["-p", "--effort", "medium"]);
expect(
resolveClaudeCliExecutionArgs({
workspaceDir: "/tmp",
provider: "claude-cli",
modelId: "claude-opus-4-7",
thinkingLevel: "xhigh",
useResume: true,
baseArgs: ["-p", "--resume", "{sessionId}"],
}),
).toEqual(["-p", "--resume", "{sessionId}", "--effort", "xhigh"]);
});
it("replaces static effort args when a session thinking level is active", () => {
expect(
resolveClaudeCliExecutionArgs({
workspaceDir: "/tmp",
provider: "claude-cli",
modelId: "claude-opus-4-7",
thinkingLevel: "max",
useResume: false,
baseArgs: ["-p", "--effort", "low", "--effort=high"],
}),
).toEqual(["-p", "--effort", "max"]);
});
});
describe("normalizeClaudeBackendConfig", () => {
it("normalizes both args and resumeArgs for custom overrides", () => {
const normalized = normalizeClaudeBackendConfig({
@@ -196,6 +258,7 @@ describe("normalizeClaudeBackendConfig", () => {
expect(normalized?.resumeArgs).toContain("--permission-mode");
expect(normalized?.resumeArgs).toContain("bypassPermissions");
expect(normalized?.liveSession).toBe("claude-stdio");
expect(backend.resolveExecutionArgs).toBe(resolveClaudeCliExecutionArgs);
});
it("leaves claude cli subscription-managed, restricts setting sources, and clears inherited env overrides", () => {

View File

@@ -1,6 +1,7 @@
import type {
CliBackendConfig,
CliBackendNormalizeConfigContext,
CliBackendResolveExecutionArgsContext,
} from "openclaw/plugin-sdk/cli-backend";
import { normalizeOptionalLowercaseString } from "openclaw/plugin-sdk/text-runtime";
import { CLAUDE_CLI_BACKEND_ID } from "./cli-constants.js";
@@ -60,9 +61,12 @@ export const CLAUDE_CLI_CLEAR_ENV = [
const CLAUDE_LEGACY_SKIP_PERMISSIONS_ARG = "--dangerously-skip-permissions";
const CLAUDE_PERMISSION_MODE_ARG = "--permission-mode";
const CLAUDE_SETTING_SOURCES_ARG = "--setting-sources";
const CLAUDE_EFFORT_ARG = "--effort";
const CLAUDE_SAFE_SETTING_SOURCES = "user";
const CLAUDE_BYPASS_PERMISSION_MODE = "bypassPermissions";
type ClaudeCliEffort = "low" | "medium" | "high" | "xhigh" | "max";
export function isClaudeCliProvider(providerId: string): boolean {
return normalizeOptionalLowercaseString(providerId) === CLAUDE_CLI_BACKEND_ID;
}
@@ -168,6 +172,60 @@ export function normalizeClaudeSettingSourcesArgs(args?: string[]): string[] | u
return normalized;
}
export function mapClaudeCliThinkingLevelToEffort(
thinkingLevel?: string | null,
): ClaudeCliEffort | undefined {
switch (normalizeOptionalLowercaseString(thinkingLevel)) {
case "minimal":
case "low":
return "low";
case "adaptive":
case "medium":
return "medium";
case "high":
return "high";
case "xhigh":
return "xhigh";
case "max":
return "max";
default:
return undefined;
}
}
function stripClaudeEffortArgs(args: readonly string[]): string[] {
const normalized: string[] = [];
for (let i = 0; i < args.length; i += 1) {
const arg = args[i] ?? "";
if (arg === CLAUDE_EFFORT_ARG) {
const maybeValue = args[i + 1];
if (
typeof maybeValue === "string" &&
maybeValue.trim().length > 0 &&
!maybeValue.startsWith("-")
) {
i += 1;
}
continue;
}
if (arg.startsWith(`${CLAUDE_EFFORT_ARG}=`)) {
continue;
}
normalized.push(arg);
}
return normalized;
}
export function resolveClaudeCliExecutionArgs(
context: CliBackendResolveExecutionArgsContext,
): string[] {
const effort = mapClaudeCliThinkingLevelToEffort(context.thinkingLevel);
if (!effort) {
return [...context.baseArgs];
}
return [...stripClaudeEffortArgs(context.baseArgs), CLAUDE_EFFORT_ARG, effort];
}
export function normalizeClaudeBackendConfig(
config: CliBackendConfig,
context?: CliBackendNormalizeConfigContext,

View File

@@ -5,7 +5,7 @@
"description": "OpenClaw Anthropic provider plugin",
"type": "module",
"dependencies": {
"@mariozechner/pi-ai": "0.71.1"
"@mariozechner/pi-ai": "0.73.0"
},
"devDependencies": {
"@openclaw/plugin-sdk": "workspace:*"

View File

@@ -15,8 +15,8 @@ describe("bonjour package manifest", () => {
fs.readFileSync(new URL("../../package.json", import.meta.url), "utf8"),
) as PackageManifest;
expect(pluginPackageJson.dependencies?.["@homebridge/ciao"]).toBe("^1.3.7");
expect(rootPackageJson.dependencies?.["@homebridge/ciao"]).toBe("^1.3.7");
expect(pluginPackageJson.dependencies?.["@homebridge/ciao"]).toBe("^1.3.8");
expect(rootPackageJson.dependencies?.["@homebridge/ciao"]).toBe("^1.3.8");
expect(pluginPackageJson.devDependencies?.["@homebridge/ciao"]).toBeUndefined();
});
});

View File

@@ -4,7 +4,7 @@
"description": "OpenClaw Bonjour/mDNS gateway discovery",
"type": "module",
"dependencies": {
"@homebridge/ciao": "^1.3.7"
"@homebridge/ciao": "^1.3.8"
},
"devDependencies": {
"@openclaw/plugin-sdk": "workspace:*"

View File

@@ -14,7 +14,7 @@
},
"devDependencies": {
"@openclaw/plugin-sdk": "workspace:*",
"undici": "8.1.0"
"undici": "8.2.0"
},
"openclaw": {
"extensions": [

View File

@@ -695,6 +695,7 @@ export function registerBrowserAgentActRoutes(
res,
ctx,
targetId,
enforceCurrentUrlAllowed: true,
run: async ({ profileCtx, cdpUrl, tab, resolveTabUrl }) => {
if (getBrowserProfileCapabilities(profileCtx.profile).usesChromeMcp) {
return jsonError(res, 501, EXISTING_SESSION_LIMITS.responseBody);

View File

@@ -29,6 +29,7 @@ export function registerBrowserAgentDebugRoutes(
ctx,
targetId,
feature: "console messages",
enforceCurrentUrlAllowed: true,
run: async ({ cdpUrl, tab, pw, resolveTabUrl }) => {
const messages = await pw.getConsoleMessagesViaPlaywright({
cdpUrl,
@@ -54,6 +55,7 @@ export function registerBrowserAgentDebugRoutes(
ctx,
targetId,
feature: "page errors",
enforceCurrentUrlAllowed: true,
run: async ({ cdpUrl, tab, pw, resolveTabUrl }) => {
const result = await pw.getPageErrorsViaPlaywright({
cdpUrl,
@@ -80,6 +82,7 @@ export function registerBrowserAgentDebugRoutes(
ctx,
targetId,
feature: "network requests",
enforceCurrentUrlAllowed: true,
run: async ({ cdpUrl, tab, pw, resolveTabUrl }) => {
const result = await pw.getNetworkRequestsViaPlaywright({
cdpUrl,
@@ -109,6 +112,7 @@ export function registerBrowserAgentDebugRoutes(
ctx,
targetId,
feature: "trace start",
enforceCurrentUrlAllowed: true,
run: async ({ cdpUrl, tab, pw, resolveTabUrl }) => {
await pw.traceStartViaPlaywright({
cdpUrl,
@@ -137,6 +141,7 @@ export function registerBrowserAgentDebugRoutes(
ctx,
targetId,
feature: "trace stop",
enforceCurrentUrlAllowed: true,
run: async ({ cdpUrl, tab, pw, resolveTabUrl }) => {
const id = crypto.randomUUID();
const tracePath = await resolveWritableOutputPathOrRespond({

View File

@@ -1,10 +1,13 @@
import { describe, expect, it } from "vitest";
import { describe, expect, it, vi } from "vitest";
import type { BrowserRouteContext, ProfileContext } from "../server-context.js";
import {
readBody,
resolveSafeRouteTabUrl,
resolveTargetIdFromBody,
resolveTargetIdFromQuery,
withRouteTabContext,
} from "./agent.shared.js";
import { createBrowserRouteResponse } from "./test-helpers.js";
import type { BrowserRequest } from "./types.js";
function requestWithBody(body: unknown): BrowserRequest {
@@ -36,6 +39,31 @@ function profileContext(tabs: Array<{ targetId: string; url: string }>) {
};
}
function routeContextForTab(url: string): BrowserRouteContext {
const profileCtx = {
profile: {
cdpUrl: "http://127.0.0.1:9222",
name: "default",
},
ensureTabAvailable: vi.fn(async () => ({
targetId: "tab-1",
title: "Tab",
url,
type: "page",
})),
} as unknown as ProfileContext;
return {
forProfile: () => profileCtx,
state: () => ({
resolved: {
ssrfPolicy: {},
},
}),
mapTabError: () => null,
} as unknown as BrowserRouteContext;
}
describe("browser route shared helpers", () => {
describe("readBody", () => {
it("returns object bodies", () => {
@@ -100,4 +128,44 @@ describe("browser route shared helpers", () => {
).resolves.toBeUndefined();
});
});
describe("withRouteTabContext", () => {
it("does not enforce current-tab URL policy unless requested", async () => {
const response = createBrowserRouteResponse();
const run = vi.fn(async () => {
response.res.json({ ok: true });
});
await withRouteTabContext({
req: requestWithBody({}),
res: response.res,
ctx: routeContextForTab("http://127.0.0.1:8080/admin"),
run,
});
expect(run).toHaveBeenCalledOnce();
expect(response.body).toEqual({ ok: true });
});
it("blocks guarded routes before running on a disallowed current tab", async () => {
const response = createBrowserRouteResponse();
const run = vi.fn(async () => {
response.res.json({ ok: true });
});
await withRouteTabContext({
req: requestWithBody({}),
res: response.res,
ctx: routeContextForTab("http://127.0.0.1:8080/admin"),
enforceCurrentUrlAllowed: true,
run,
});
expect(run).not.toHaveBeenCalled();
expect(response.statusCode).toBe(400);
expect(response.body).toMatchObject({ error: expect.any(String) });
const body = response.body as { error?: unknown };
expect(body.error).not.toBe("");
});
});
});

View File

@@ -107,6 +107,11 @@ type RouteWithTabParams<T> = {
res: BrowserResponse;
ctx: BrowserRouteContext;
targetId?: string;
/**
* Set for routes that read from or return data scoped to the selected tab.
* Leave false only for routes that navigate, activate, close, or otherwise manage the tab.
*/
enforceCurrentUrlAllowed?: boolean;
run: (ctx: RouteTabContext) => Promise<T>;
};
@@ -119,6 +124,17 @@ export async function withRouteTabContext<T>(
}
try {
const tab = await profileCtx.ensureTabAvailable(params.targetId);
if (params.enforceCurrentUrlAllowed) {
await assertBrowserNavigationResultAllowed({
url: tab.url,
...withBrowserNavigationPolicy(params.ctx.state().resolved.ssrfPolicy, {
browserProxyMode: resolveBrowserNavigationProxyMode({
resolved: params.ctx.state().resolved,
profile: profileCtx.profile,
}),
}),
});
}
return await params.run({
profileCtx,
tab,
@@ -137,6 +153,10 @@ export async function withRouteTabContext<T>(
}
}
/**
* Response-only URL redaction. This swallows policy failures and must not be used as
* an execution gate; use enforceCurrentUrlAllowed on the route helper instead.
*/
export async function resolveSafeRouteTabUrl(params: {
ctx: BrowserRouteContext;
profileCtx: ProfileContext;
@@ -171,6 +191,11 @@ type RouteWithPwParams<T> = {
ctx: BrowserRouteContext;
targetId?: string;
feature: string;
/**
* Set for routes that read from or return data scoped to the selected tab.
* Leave false only for routes that navigate, activate, close, or otherwise manage the tab.
*/
enforceCurrentUrlAllowed?: boolean;
run: (ctx: RouteTabPwContext) => Promise<T>;
};
@@ -182,6 +207,7 @@ export async function withPlaywrightRouteContext<T>(
res: params.res,
ctx: params.ctx,
targetId: params.targetId,
enforceCurrentUrlAllowed: params.enforceCurrentUrlAllowed,
run: async ({ profileCtx, tab, cdpUrl, resolveTabUrl }) => {
const pw = await requirePwAi(params.res, params.feature);
if (!pw) {

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